Skip to content

Commit

Permalink
Add support for ignoring IRC hosts from Discord (#82)
Browse files Browse the repository at this point in the history
This should add a configuration option that allows for using nearly IRC style hostmasks to "ban" matching users in IRC from sending messages towards Discord. This would be useful for ignoring bots in channels.

Tested, works, documentation and configuration example added.

Co-authored-by: Qais Patankar <[email protected]>
  • Loading branch information
llmII and qaisjp authored Feb 16, 2021
1 parent 00353c4 commit 9b49a56
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 32 deletions.
45 changes: 23 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,28 +66,29 @@ The binary takes three flags:

The config file is a yaml formatted file with the following fields:

| name | requires restart | default | optional | description |
| ------------------- | ---------------- | ---------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `avatar_url` | No | `https://ui-avatars.com/api/?name=${USERNAME}` | Yes | The URL for the API to use to tell Discord what Avatar to use for a User when the user's avatar cannot be found at Discord already. |
| `discord_token` | Yes | | No | [The bot user token](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token) |
| `irc_server` | Yes | | No | IRC server address |
| `channel_mappings` | No | | No | a dict with irc channel as key (prefixed with `#`) and Discord channel ID as value |
| `guild_id` | No | | No | the Discord guild (server) id |
| `irc_pass` | Yes | | Yes | password for connecting to the IRC server |
| `suffix` | No | `~d` | Yes | appended to each Discord user's nickname when they are connected to IRC. If set to `_d2`, if the name will be `bob_d2` |
| `separator` | No | `_` | Yes | used in fallback situations. If set to `-`, the **fallback name** will be like `bob-7247_d2` (where `7247` is the discord user's discriminator, and `_d2` is the suffix) |
| `irc_listener_name` | Yes | `~d` | The name of the irc listener |
| `puppet_username` | No | username of discord account being puppeted | Yes | username to connect to irc with |
| `webirc_pass` | No | | Yes | optional, but recommended for regular (non-simple) usage. this must be obtained by the IRC sysops |
| `debug` | Yes | false | Yes | debug mode |
| `insecure`, | Yes | false | Yes | TLS will skip verification (but still uses TLS) |
| `no_tls`, | Yes | false | Yes | turns off TLS |
| `webhook_prefix`, | Yes | | No | a prefix for webhooks, so we know which ones to keep and which ones to delete |
| `nickserv_identify` | No | | Yes | on connect this message will be sent: `PRIVMSG nickserv IDENTIFY <value>`, you can provide both a username and password if your ircd supports it |
| `cooldown_duration` | No | 86400 (24 hours) | Yes | time in seconds for a discord user to be offline before it's puppet disconnects from irc |
| `show_joinquit` | No | false | yes | displays JOIN, PART, QUIT, KICK on discord |
| `max_nick_length` | No | 30 | yes | Maximum allowed nick length |
| `connection_limit` | Yes | 0 | Yes | How many connections to IRC (including our listener) to spawn (limit of 0 or less means unlimited) |
| name | requires restart | default | optional | description |
| ----------------------- | ---------------- | ---------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `avatar_url` | No | `https://ui-avatars.com/api/?name=${USERNAME}` | Yes | The URL for the API to use to tell Discord what Avatar to use for a User when the user's avatar cannot be found at Discord already. |
| `discord_token` | Yes | | No | [The bot user token](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token) |
| `irc_server` | Yes | | No | IRC server address |
| `channel_mappings` | No | | No | a dict with irc channel as key (prefixed with `#`) and Discord channel ID as value |
| `guild_id` | No | | No | the Discord guild (server) id |
| `irc_pass` | Yes | | Yes | password for connecting to the IRC server |
| `suffix` | No | `~d` | Yes | appended to each Discord user's nickname when they are connected to IRC. If set to `_d2`, if the name will be `bob_d2` |
| `separator` | No | `_` | Yes | used in fallback situations. If set to `-`, the **fallback name** will be like `bob-7247_d2` (where `7247` is the discord user's discriminator, and `_d2` is the suffix) |
| `irc_listener_name` | Yes | `~d` | The name of the irc listener |
| `puppet_username` | No | username of discord account being puppeted | Yes | username to connect to irc with |
| `webirc_pass` | No | | Yes | optional, but recommended for regular (non-simple) usage. this must be obtained by the IRC sysops |
| `debug` | Yes | false | Yes | debug mode |
| `insecure`, | Yes | false | Yes | TLS will skip verification (but still uses TLS) |
| `no_tls`, | Yes | false | Yes | turns off TLS |
| `webhook_prefix`, | Yes | | No | a prefix for webhooks, so we know which ones to keep and which ones to delete |
| `nickserv_identify` | No | | Yes | on connect this message will be sent: `PRIVMSG nickserv IDENTIFY <value>`, you can provide both a username and password if your ircd supports it |
| `cooldown_duration` | No | 86400 (24 hours) | Yes | time in seconds for a discord user to be offline before it's puppet disconnects from irc |
| `show_joinquit` | No | false | yes | displays JOIN, PART, QUIT, KICK on discord |
| `max_nick_length` | No | 30 | yes | Maximum allowed nick length |
| `ignored_irc_hostmasks` | No | | Yes | A list of IRC users identified by hostmask to not relay to Discord, uses matching syntax as in [glob](https://github.com/gobwas/glob) |
| `connection_limit` | Yes | 0 | Yes | How many connections to IRC (including our listener) to spawn (limit of 0 or less means unlimited) |

**The filename.yaml file is continuously read from and many changes will
automatically update on the bridge. This means you can add or remove channels
Expand Down
4 changes: 3 additions & 1 deletion bridge/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/bwmarrin/discordgo"
"github.com/gobwas/glob"
"github.com/pkg/errors"
irc "github.com/qaisjp/go-ircevent"
log "github.com/sirupsen/logrus"
Expand All @@ -17,7 +18,6 @@ import (
type Config struct {
AvatarURL string
DiscordBotToken, GuildID string
ConnectionLimit int // amount of IRC Connections we can spawn

// Map from Discord to IRC
ChannelMappings map[string]string
Expand All @@ -28,6 +28,8 @@ type Config struct {
WebIRCPass string
NickServIdentify string // string: "[account] password"
PuppetUsername string // Username to connect to IRC with
IRCIgnores []glob.Glob
ConnectionLimit int // number of IRC connections we can spawn

// NoTLS constrols whether to use TLS at all when connecting to the IRC server
NoTLS bool
Expand Down
5 changes: 5 additions & 0 deletions bridge/irc_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ func (i *ircConnection) experimentalNotice(nick string) {
}

func (i *ircConnection) OnPrivateMessage(e *irc.Event) {
// Ignored hostmasks
if i.manager.isIgnoredHostmask(e.Source) {
return
}

// Alert private messages
if string(e.Arguments[0][0]) != "#" {
if e.Message() == "help" {
Expand Down
10 changes: 10 additions & 0 deletions bridge/irc_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ func (i *ircListener) OnJoinQuitCallback(event *irc.Event) {
return
}

// Ignored hostmasks
if i.bridge.ircManager.isIgnoredHostmask(event.Source) {
return
}

who := event.Nick
message := event.Nick
id := " (" + event.User + "@" + event.Host + ") "
Expand Down Expand Up @@ -215,6 +220,11 @@ func (i *ircListener) OnPrivateMessage(e *irc.Event) {
return
}

// Ignored hostmasks
if i.bridge.ircManager.isIgnoredHostmask(e.Source) {
return
}

replacements := []string{}
for _, con := range i.bridge.ircManager.ircConnections {
replacements = append(replacements, con.nick, "<@!"+con.discord.ID+">")
Expand Down
9 changes: 9 additions & 0 deletions bridge/irc_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,15 @@ func (m *IRCManager) RequestChannels(userID string) []Mapping {
return m.bridge.mappings
}

func (m *IRCManager) isIgnoredHostmask(mask string) bool {
for _, ban := range m.bridge.Config.IRCIgnores {
if ban.Match(mask) {
return true
}
}
return false
}

func (m *IRCManager) generateUsername(discordUser DiscordUser) string {
if len(m.bridge.Config.PuppetUsername) > 0 {
return m.bridge.Config.PuppetUsername
Expand Down
9 changes: 8 additions & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ no_tls: false
debug: false
webhook_prefix: "(auto-test)"
# simple: true
# connection_limit: 2 # Would limit this to 2 connections (a listener, and one puppet, the rest relayed)

# Uses matching syntax as in https://github.com/gobwas/glob
# ignored_irc_hostmasks:
# - "bot1!*@*"
# - "*!?bot@*"

# This limits to 2 connections (a listener, and one puppet, the rest relayed in simple mode)
# connection_limit: 2
38 changes: 30 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"github.com/fsnotify/fsnotify"
"github.com/gobwas/glob"
"github.com/pkg/errors"
"github.com/qaisjp/go-discord-irc/bridge"
ircnick "github.com/qaisjp/go-discord-irc/irc/nick"
Expand Down Expand Up @@ -61,14 +62,15 @@ func main() {
log.Fatalln(errors.Wrap(err, "could not read config"))
}

discordBotToken := viper.GetString("discord_token") // Discord Bot User Token
channelMappings := viper.GetStringMapString("channel_mappings") // Discord:IRC mappings in format '#discord1:#irc1,#discord2:#irc2,...'
ircServer := viper.GetString("irc_server") // Server address to use, example `irc.freenode.net:7000`.
ircPassword := viper.GetString("irc_pass") // Optional password for connecting to the IRC server
guildID := viper.GetString("guild_id") // Guild to use
webIRCPass := viper.GetString("webirc_pass") // Password for WEBIRC
identify := viper.GetString("nickserv_identify") // NickServ IDENTIFY for Listener
connectionLimit := viper.GetInt("connection_limit") // Limiter on how many IRC Connections we can spawn
discordBotToken := viper.GetString("discord_token") // Discord Bot User Token
channelMappings := viper.GetStringMapString("channel_mappings") // Discord:IRC mappings in format '#discord1:#irc1,#discord2:#irc2,...'
ircServer := viper.GetString("irc_server") // Server address to use, example `irc.freenode.net:7000`.
ircPassword := viper.GetString("irc_pass") // Optional password for connecting to the IRC server
guildID := viper.GetString("guild_id") // Guild to use
webIRCPass := viper.GetString("webirc_pass") // Password for WEBIRC
identify := viper.GetString("nickserv_identify") // NickServ IDENTIFY for Listener
ircIgnores := viper.GetStringSlice("ignored_irc_hostmasks") // IRC hosts to not relay to Discord
connectionLimit := viper.GetInt("connection_limit") // Limiter on how many IRC Connections we can spawn
//
if !*debugMode {
*debugMode = viper.GetBool("debug")
Expand Down Expand Up @@ -116,6 +118,7 @@ func main() {
log.Warnln("Channel mappings are missing!")
}

matchers := setupHostmaskMatchers(ircIgnores)
SetLogDebug(*debugMode)

dib, err := bridge.New(&bridge.Config{
Expand All @@ -126,6 +129,7 @@ func main() {
IRCServer: ircServer,
IRCServerPass: ircPassword,
ConnectionLimit: connectionLimit,
IRCIgnores: matchers,
PuppetUsername: puppetUsername,
NickServIdentify: identify,
WebIRCPass: webIRCPass,
Expand Down Expand Up @@ -176,6 +180,9 @@ func main() {
dib.SetIRCListenerName(ircUsername)
}

ircIgnores := viper.GetStringSlice("ignored_irc_hostmasks")
dib.Config.IRCIgnores = setupHostmaskMatchers(ircIgnores)

avatarURL := viper.GetString("avatar_url")
dib.Config.AvatarURL = avatarURL

Expand Down Expand Up @@ -211,6 +218,21 @@ func main() {
dib.Close()
}

func setupHostmaskMatchers(hostmasks []string) []glob.Glob {
var matchers []glob.Glob
for _, mask := range hostmasks {
g, err := glob.Compile(mask)
if err != nil {
log.WithField("error", err).WithField("hostmask", mask).Errorln("Failed to compile hostmask ban!")
continue
}

matchers = append(matchers, g)
}

return matchers
}

func SetLogDebug(debug bool) {
logger := log.StandardLogger()
if debug {
Expand Down

0 comments on commit 9b49a56

Please sign in to comment.