-
Notifications
You must be signed in to change notification settings - Fork 32
/
main.go
310 lines (268 loc) · 10.6 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
package main
import (
"flag"
"os"
"os/signal"
"path/filepath"
"reflect"
"strings"
"syscall"
"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"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
func main() {
config := flag.String("config", "", "Config file to read configuration stuff from")
simple := flag.Bool("simple", false, "When in simple mode, the bridge will only spawn one IRC connection for listening and speaking")
debugMode := flag.Bool("debug", false, "Debug mode? (false = use value from settings)")
notls := flag.Bool("no-tls", false, "Avoids using TLS att all when connecting to IRC server ")
insecure := flag.Bool("insecure", false, "Skip TLS certificate verification? (INSECURE MODE) (false = use value from settings)")
// Secret devmode
devMode := flag.Bool("dev", false, "")
debugPresence := flag.Bool("debug-presence", false, "Include presence in debug output")
flag.Parse()
bridge.DevMode = *devMode
if *config == "" {
log.Fatalln("--config argument is required!")
return
}
if *simple {
log.Println("Running in simple mode.")
}
viper := viper.New()
ext := filepath.Ext(*config)
configName := strings.TrimSuffix(filepath.Base(*config), ext)
configType := ext[1:]
configPath := filepath.Dir(*config)
viper.SetConfigName(configName)
viper.SetConfigType(configType)
viper.AddConfigPath(configPath)
log.WithFields(log.Fields{
"ConfigName": configName,
"ConfigType": configType,
"ConfigPath": configPath,
}).Infoln("Loading configuration...")
err := viper.ReadInConfig()
if err != nil {
log.Fatalln(errors.Wrap(err, "could not read config"))
}
if viper.GetString("nickserv_identify") != "" {
log.Fatalln("Please see https://github.com/qaisjp/go-discord-irc/blob/master/config.yml for an example config. `nickserv_identify` is deprecated and superseded by `irc_puppet_prejoin_commands`.")
return
}
discriminator := viper.GetString("irc_server_name") // unique per IRC network connected to, keeps PMs working
if discriminator == "" {
log.Fatalln("'irc_server_name' config option is required and cannot be empty")
return
}
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
ircListenerPrejoinCommands := viper.GetStringSlice("irc_listener_prejoin_commands") // Commands for each connection to send before joining channels
guildID := viper.GetString("guild_id") // Guild to use
webIRCPass := viper.GetString("webirc_pass") // Password for WEBIRC
ircIgnores := viper.GetStringSlice("ignored_irc_hostmasks") // IRC hosts to not relay to Discord
rawDiscordIgnores := viper.GetStringSlice("ignored_discord_ids") // Ignore these Discord users on IRC
rawDiscordAllowed := viper.GetStringSlice("allowed_discord_ids")
rawIRCFilter := viper.GetStringSlice("irc_message_filter") // Ignore lines containing matched text from IRC
rawDiscordFilter := viper.GetStringSlice("discord_message_filter") // Ignore lines containing matched text from Discord
connectionLimit := viper.GetInt("connection_limit") // Limiter on how many IRC Connections we can spawn
//
if !*debugMode {
*debugMode = viper.GetBool("debug")
}
//
if !*notls {
*notls = viper.GetBool("no_tls")
}
if !*insecure {
*insecure = viper.GetBool("insecure")
}
//
viper.SetDefault("irc_puppet_prejoin_commands", []string{"MODE ${NICK} +D"})
ircPuppetPrejoinCommands := viper.GetStringSlice("irc_puppet_prejoin_commands") // Commands for each connection to send before joining channels
//
viper.SetDefault("avatar_url", "https://robohash.org/${USERNAME}.png?set=set4")
avatarURL := viper.GetString("avatar_url")
//
viper.SetDefault("irc_listener_name", "~d")
ircUsername := viper.GetString("irc_listener_name") // Name for IRC-side bot, for listening to messages.
// Name to Connect to IRC puppet account with
viper.SetDefault("puppet_username", "")
puppetUsername := viper.GetString("puppet_username")
//
viper.SetDefault("suffix", "~d")
suffix := viper.GetString("suffix") // The suffix to append to IRC connections (not in use when simple mode is on)
//
viper.SetDefault("separator", "~")
separator := viper.GetString("separator")
//
viper.SetDefault("cooldown_duration", int64((time.Hour * 24).Seconds()))
cooldownDuration := viper.GetInt64("cooldown_duration")
//
viper.SetDefault("show_joinquit", false)
showJoinQuit := viper.GetBool("show_joinquit")
// Maximum length of user nicks aloud
viper.SetDefault("max_nick_length", ircnick.MAXLENGTH)
maxNickLength := viper.GetInt("max_nick_length")
if webIRCPass == "" {
log.Warnln("webirc_pass is empty")
}
// Validate mappings
if len(channelMappings) == 0 {
log.Warnln("Channel mappings are missing!")
}
matchers := setupHostmaskMatchers(ircIgnores)
discordFilter := setupFilter(rawDiscordFilter)
ircFilter := setupFilter(rawIRCFilter)
SetLogDebug(*debugMode)
// Check for nil, as nil means we don't use this list
var discordAllowed map[string]struct{}
if rawDiscordAllowed != nil {
log.Println("allowed_discord_ids is set, so only specific Discord users will be bridged")
discordAllowed = stringSliceToMap(rawDiscordAllowed)
}
dib, err := bridge.New(&bridge.Config{
AvatarURL: avatarURL,
Discriminator: discriminator,
DiscordBotToken: discordBotToken,
GuildID: guildID,
IRCListenerName: ircUsername,
IRCServer: ircServer,
IRCServerPass: ircPassword,
IRCPuppetPrejoinCommands: ircPuppetPrejoinCommands,
IRCListenerPrejoinCommands: ircListenerPrejoinCommands,
ConnectionLimit: connectionLimit,
IRCIgnores: matchers,
IRCFilteredMessages: ircFilter,
DiscordIgnores: stringSliceToMap(rawDiscordIgnores),
DiscordAllowed: discordAllowed,
DiscordFilteredMessages: discordFilter,
PuppetUsername: puppetUsername,
WebIRCPass: webIRCPass,
NoTLS: *notls,
InsecureSkipVerify: *insecure,
Suffix: suffix,
Separator: separator,
SimpleMode: *simple,
ChannelMappings: channelMappings,
CooldownDuration: time.Second * time.Duration(cooldownDuration),
ShowJoinQuit: showJoinQuit,
MaxNickLength: maxNickLength,
Debug: *debugMode,
DebugPresence: *debugPresence,
})
if err != nil {
log.WithField("error", err).Fatalln("Go-Discord-IRC failed to initialise.")
return
}
log.Infoln("Cooldown duration for IRC puppets is", dib.Config.CooldownDuration)
// Create new signal receiver
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
// Open the bot
err = dib.Open()
if err != nil {
log.WithField("error", err).Fatalln("Go-Discord-IRC failed to start.")
return
}
// Inform the user that things are happening!
log.Infoln("Go-Discord-IRC is now running. Press Ctrl-C to exit.")
// Start watching for live changes...
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
log.Println("Configuration file has changed!")
if newUsername := viper.GetString("irc_listener_name"); ircUsername != newUsername {
log.Printf("Changed irc_listener_name from '%s' to '%s'", ircUsername, newUsername)
// Listener name has changed
ircUsername = newUsername
dib.SetIRCListenerName(ircUsername)
}
ircIgnores := viper.GetStringSlice("ignored_irc_hostmasks")
dib.Config.IRCIgnores = setupHostmaskMatchers(ircIgnores)
rawIRCFilter := viper.GetStringSlice("irc_message_filter")
rawDiscordFilter := viper.GetStringSlice("discord_message_filter")
dib.Config.DiscordFilteredMessages = setupFilter(rawDiscordFilter)
dib.Config.IRCFilteredMessages = setupFilter(rawIRCFilter)
avatarURL := viper.GetString("avatar_url")
dib.Config.AvatarURL = avatarURL
if debug := viper.GetBool("debug"); *debugMode != debug {
log.Printf("Debug changed from %+v to %+v", *debugMode, debug)
*debugMode = debug
dib.SetDebugMode(debug)
SetLogDebug(debug)
}
rawDiscordIgnores := viper.GetStringSlice("ignored_discord_ids")
dib.Config.DiscordIgnores = stringSliceToMap(rawDiscordIgnores)
rawDiscordAllowed := viper.GetStringSlice("allowed_discord_ids")
if rawDiscordAllowed == nil {
dib.Config.DiscordAllowed = nil
} else {
dib.Config.DiscordAllowed = stringSliceToMap(rawDiscordAllowed)
}
chans := viper.GetStringMapString("channel_mappings")
equalChans := reflect.DeepEqual(chans, channelMappings)
if !equalChans {
log.Println("Channel mappings updated!")
if len(chans) == 0 {
log.Println("Channel mappings are missing! Not applying changes in case this was an accident.")
} else {
if err := dib.SetChannelMappings(chans); err != nil {
log.WithField("error", err).Errorln("could not set channel mappings")
} else {
channelMappings = chans
}
}
}
})
// Watch for a shutdown signal
<-sc
log.Infoln("Shutting down Go-Discord-IRC...")
// Cleanly close down the bridge.
dib.Close()
}
func stringSliceToMap(list []string) map[string]struct{} {
m := make(map[string]struct{}, len(list))
for _, v := range list {
m[v] = struct{}{}
}
return m
}
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 setupFilter(filters []string) []glob.Glob {
var matchers []glob.Glob
for _, filter := range filters {
g, err := glob.Compile(filter)
if err != nil {
log.WithField("error", err).WithField("filter", filter).Errorln("Failed to compile message filter!")
continue
}
matchers = append(matchers, g)
}
return matchers
}
func SetLogDebug(debug bool) {
logger := log.StandardLogger()
if debug {
logger.SetLevel(log.DebugLevel)
} else {
logger.SetLevel(log.InfoLevel)
}
}