diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..e8a094c --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,84 @@ +version: "2" +run: + go: "1.21" + modules-download-mode: readonly + tests: false + allow-parallel-runners: true +linters: + default: none + enable: + - asasalint + - asciicheck + - bodyclose + - containedctx + - contextcheck + - decorder + - durationcheck + - errcheck + - errchkjson + - errorlint + - exhaustive + - ginkgolinter + - gocheckcompilerdirectives + - gocritic + - godot + - gosec + - gosmopolitan + - govet + - grouper + - importas + - ineffassign + - ireturn + - makezero + - mirror + - nakedret + - nilerr + - nilnil + - nlreturn + - noctx + - nolintlint + - nonamedreturns + - predeclared + - promlinter + - reassign + - revive + - rowserrcheck + - staticcheck + - unconvert + - unused + - wastedassign + - whitespace + - wsl + settings: + gosec: + excludes: + # Оно не используется для повышения безопасности или каких-то таких секъюрных вещей, оно используется для выборки + # произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью. + - G404 + # Не еби мозги. Сертификаты для локалхоста или для локальной сети могут быть невалидными, если ты сможешь + # выписать валидный серт для локалхоста, тебе миллионы разработчиков поставят золотой памятник. Просто потому + # что эти пидоры-безпасники всрали всё что только можно всрать своими дебильными политиками. + - G402 + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ + - vendor$ \ No newline at end of file diff --git a/aleesa-irc-go-lib.go b/aleesa-irc-go-lib.go index 2fdf786..e34d188 100644 --- a/aleesa-irc-go-lib.go +++ b/aleesa-irc-go-lib.go @@ -11,15 +11,9 @@ import ( irc "github.com/thoj/go-ircevent" ) -// TODO: Implement 005 REPL_BOUNCE -// ircClientInit горутинка для работы с протоколом irc +// ircClientInit горутинка для работы с протоколом irc. func ircClientRun() { - for { - if shutdown { - // Если мы завершаем работу программы, то нам ничего обрабатывать не надо - break - } - + for !shutdown { // Иницализируем irc-клиента serverString := fmt.Sprintf("%s:%d", config.Irc.Server, config.Irc.Port) log.Debugf("Preparing to connect to %s", serverString) @@ -30,10 +24,12 @@ func ircClientRun() { if config.Irc.Ssl { log.Debug("Force use ssl for connection") + ircClient.UseTLS = true if !config.Irc.SslVerify { log.Debug("Skip server certificate validation") + ircClient.TLSConfig = &tls.Config{InsecureSkipVerify: true} } else { log.Debug("Force server certificate validation") @@ -92,18 +88,19 @@ func ircClientRun() { if channel == mychannel { // Для каждого канала формат строго 1 из 4-х: #channel | @#channel | +#channel | @+#channel - switch { - case modes == "@+": + switch modes { + case "@+": userModeUpdateUser(channel, dstNick, "+o") userModeUpdateUser(channel, dstNick, "+v") - case modes == "@": + case "@": userModeUpdateUser(channel, dstNick, "+o") - case modes == "+": + case "+": userModeUpdateUser(channel, dstNick, "+v") default: userModeUpdateUser(channel, dstNick, "-o") userModeUpdateUser(channel, dstNick, "-v") } + break } } @@ -123,6 +120,7 @@ func ircClientRun() { if channel == mychannel { for _, name := range strings.Split(namesString, " ") { mode := name[:1] + var nick string if mode == "@" || mode == "+" { @@ -131,16 +129,17 @@ func ircClientRun() { nick = name } - switch { - case mode == "@": + switch mode { + case "@": userModeUpdateUser(channel, nick, "+o") - case mode == "+": + case "+": userModeUpdateUser(channel, nick, "+v") default: userModeUpdateUser(channel, nick, "-o") userModeUpdateUser(channel, nick, "-v") } } + break } } @@ -173,8 +172,9 @@ func ircClientRun() { if !config.Irc.Sasl && config.Irc.Password != "" && !nickIsUsed { log.Info("Identifying via NickServ") + message := fmt.Sprintf("identify %s %s", config.Irc.Nick, config.Irc.Password) - imChan <- iMsg{ChatId: "NickServ", Text: message} + imChan <- iMsg{ChatID: "NickServ", Text: message} } // TODO: wait until +R flag been set? could be implemented with waiting for channel message. @@ -241,8 +241,9 @@ func ircClientRun() { if !config.Irc.Sasl && config.Irc.Password != "" && !nickIsUsed { log.Info("Identifying via NickServ") + message := fmt.Sprintf("identify %s %s", config.Irc.Nick, config.Irc.Password) - imChan <- iMsg{ChatId: "NickServ", Text: message} + imChan <- iMsg{ChatID: "NickServ", Text: message} } // TODO: wait until +R flag been set? could be implemented with waiting for channel message. @@ -264,8 +265,11 @@ func ircClientRun() { ircClient.AddCallback("433", func(e *irc.Event) { log.Errorf("433 ERR_NICKNAMEINUSE, %s", e.Raw) + nickIsUsed = true - time.Sleep(60 * time.Second) + + <-time.NewTimer(60 * time.Second).C // 1 minute sleep. + ircClient.Nick(config.Irc.Nick) }) @@ -321,6 +325,7 @@ func ircClientRun() { for _, ircChan := range config.Irc.Channels { if ircChan == channel { ircClient.Join(channel) + break } } @@ -341,6 +346,7 @@ func ircClientRun() { for _, ircChan := range config.Irc.Channels { if ircChan == channel { ircClient.Join(channel) + break } } @@ -359,6 +365,7 @@ func ircClientRun() { for _, ircChan := range config.Irc.Channels { if ircChan == channel { ircClient.Join(channel) + break } } @@ -423,8 +430,9 @@ func ircClientRun() { log.Warn("I regain my nick") } else { log.Warn("I regain my nick, trying to identify myself via NickServ") + message := fmt.Sprintf("identify %s %s", config.Irc.Nick, config.Irc.Password) - imChan <- iMsg{ChatId: "NickServ", Text: message} + imChan <- iMsg{ChatID: "NickServ", Text: message} } } else { log.Infof("%s renames themself to %s", srcNick, dstNick) @@ -485,6 +493,7 @@ func ircClientRun() { channel := e.Arguments[0] fullSrcNick := e.Source srcNick := e.Nick + var dstNick string if len(e.Arguments) >= 3 { // кого-то по-MODE-или на канале @@ -497,6 +506,7 @@ func ircClientRun() { } } else { // Установка mode-а при заходе на сервер dstNick = e.Arguments[0] + log.Infof("Server set my mode to %s", mode) } @@ -545,8 +555,9 @@ func ircClientRun() { log.Debugf("Make an actual connection to irc") if err := ircClient.Connect(serverString); err != nil { - log.Fatalf("Unable to prepare irc connection: %s", err) - time.Sleep(3 * time.Second) + log.Errorf("Unable to prepare irc connection: %s", err) + <-time.NewTimer(3 * time.Second).C // Sleep for 3 seconds. + continue } @@ -560,21 +571,22 @@ func ircSend() { for { m := <-imChan - switch { - case config.Irc.RateLimit.Type == "simple_delay": - log.Debugf("Sending to chat %s message: %s", m.ChatId, m.Text) + switch config.Irc.RateLimit.Type { + case "simple_delay": + log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text) if m.Text[0:4] == "/me " { - ircClient.Action(m.ChatId, m.Text) + ircClient.Action(m.ChatID, m.Text) } else { - ircClient.Privmsg(m.ChatId, m.Text) + ircClient.Privmsg(m.ChatID, m.Text) } sleepDelay := time.Duration(config.Irc.RateLimit.SimpleDelay) * time.Millisecond log.Debugf("Due to delay type simple_delay waiting for %d milliseconds", int(sleepDelay)) time.Sleep(sleepDelay) - case config.Irc.RateLimit.Type == "token_bucket": + case "token_bucket": var currentTimeMs = time.Now().UnixMilli() + var expirationTimeMs = config.Irc.RateLimit.TokenBucket.ExpirationTime * 1000 if len(msgBucket.Timestamps) > 0 { @@ -588,6 +600,7 @@ func ircSend() { bucketFill := len(newBucket.Timestamps) log.Debugf("Message bucket filled with %d/%d messages", bucketFill, config.Irc.RateLimit.TokenBucket.Size) + newBucket.Timestamps = append(newBucket.Timestamps, currentTimeMs) if bucketFill >= config.Irc.RateLimit.TokenBucket.Size { @@ -598,15 +611,17 @@ func ircSend() { } else { // Проставим время отправки сообщения log.Debug("Message bucket is empty") + msgBucket.Timestamps = append(msgBucket.Timestamps, currentTimeMs) } if msgBucket.IsFull { log.Debug("Message bucket overflowed, hitting ratelimit") + sleepPeriod := expirationTimeMs - (currentTimeMs - msgBucket.Timestamps[len(msgBucket.Timestamps)-1]) if config.Irc.RateLimit.TokenBucket.Limit > 0 { - sleepPeriod = sleepPeriod / int64(config.Irc.RateLimit.TokenBucket.Limit) + sleepPeriod /= int64(config.Irc.RateLimit.TokenBucket.Limit) } log.Debugf("Sleeping for %d milliseconds", sleepPeriod) @@ -616,20 +631,20 @@ func ircSend() { msgBucket.Timestamps[len(msgBucket.Timestamps)-1] = currentTimeMs } - log.Debugf("Sending to chat %s message: %s", m.ChatId, m.Text) + log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text) if len(m.Text) > 4 && m.Text[0:4] == "/me " { - ircClient.Action(m.ChatId, m.Text[4:]) + ircClient.Action(m.ChatID, m.Text[4:]) } else { - ircClient.Privmsg(m.ChatId, m.Text) + ircClient.Privmsg(m.ChatID, m.Text) } default: - log.Debugf("Sending to chat %s message: %s", m.ChatId, m.Text) + log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text) if len(m.Text) > 4 && m.Text[0:4] == "/me " { - ircClient.Action(m.ChatId, m.Text[4:]) + ircClient.Action(m.ChatID, m.Text[4:]) } else { - ircClient.Privmsg(m.ChatId, m.Text) + ircClient.Privmsg(m.ChatID, m.Text) } } } @@ -638,12 +653,12 @@ func ircSend() { func ircSendUnrestricted() { for { m := <-imChanUnrestricted - log.Debugf("Skipping ratelimit and sending to chat %s message: %s", m.ChatId, m.Text) + log.Debugf("Skipping ratelimit and sending to chat %s message: %s", m.ChatID, m.Text) if len(m.Text) > 4 && m.Text[0:4] == "/me " { - ircClient.Action(m.ChatId, m.Text[4:]) + ircClient.Action(m.ChatID, m.Text[4:]) } else { - ircClient.Privmsg(m.ChatId, m.Text) + ircClient.Privmsg(m.ChatID, m.Text) } } } diff --git a/globals.go b/globals.go index 4b97883..8fe5c92 100644 --- a/globals.go +++ b/globals.go @@ -6,54 +6,53 @@ import ( "aleesa-irc-go/internal/anycollection" "aleesa-irc-go/internal/boolcollection" - "github.com/cockroachdb/pebble" "github.com/go-redis/redis/v8" irc "github.com/thoj/go-ircevent" ) -// Config - это у нас глобальная штука :) +// Config - это у нас глобальная штука :). var config myConfig -// To break circular message forwarding we must set some sane default, it can be overridden via config +// To break circular message forwarding we must set some sane default, it can be overridden via config. var forwardMax int64 = 5 -// Объектики irc-клиента +// Объектики irc-клиента. var ircClient *irc.Connection var nickIsUsed = false -// Объектики клиента-редиски +// Объектики клиента-редиски. var redisClient *redis.Client var subscriber *redis.PubSub -// Main context +// Main context. var ctx = context.Background() -// Ставится в true, если мы получили сигнал на выключение +// Ставится в true, если мы получили сигнал на выключение. var shutdown = false -// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов +// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов. var sigChan = make(chan os.Signal, 1) -// Канал, в который пишутся сообщения для отправки в IRC в обычном порядке +// Канал, в который пишутся сообщения для отправки в IRC в обычном порядке. var imChan = make(chan iMsg, 10000) -// Канал, в который пишутся сообщения для отправки в IRC, если ограничений нет (у бота +o или +v на канале) +// Канал, в который пишутся сообщения для отправки в IRC, если ограничений нет (у бота +o или +v на канале). var imChanUnrestricted = make(chan iMsg, 100) -// "Ведёрко" с timestamp-ами последних отправленных сообщений +// "Ведёрко" с timestamp-ами последних отправленных сообщений. var msgBucket bucket -// Мапка с открытыми дескрипторами баз с настройками +// Мапка с открытыми дескрипторами баз с настройками. var settingsDB = make(map[string]*pebble.DB) -// "Базюлька" с MODE-ами пользователей на каналах +// "Базюлька" с MODE-ами пользователей на каналах. var userMode = anycollection.NewCollection() -// "Базюлька" с доступными MODE-ами пользователя +// "Базюлька" с доступными MODE-ами пользователя. var availableUserModes = boolcollection.NewCollection() -// "Базюлька" c с доступными MODE-ами каналов +// "Базюлька" c с доступными MODE-ами каналов. var availableChanModes = boolcollection.NewCollection() /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/irc-user-mode.go b/irc-user-mode.go index f6af50b..fd6293e 100644 --- a/irc-user-mode.go +++ b/irc-user-mode.go @@ -8,7 +8,7 @@ import "strings" активируют мягкий ratelimit на стороне сервера, без них, сервер просто рвёт соединение по превышению лимитов */ -// Удаляет сведения о MODE-ах пользователя для заданного канала +// Удаляет сведения о MODE-ах пользователя для заданного канала. func userModeDeleteUser(ircChan string, nick string) { channel, ok := userMode.Get(ircChan) @@ -24,16 +24,17 @@ func userModeDeleteUser(ircChan string, nick string) { } } -// Удаляет сведения о MODE-ах пользователя для всех каналов, на которых есть бот +// Удаляет сведения о MODE-ах пользователя для всех каналов, на которых есть бот. func userModePurgeUser(nick string) { for _, channel := range config.Irc.Channels { userModeDeleteUser(channel, nick) } } -// Обновляет или создаёт запись о mode-ах пользователей +// Обновляет или создаёт запись о mode-ах пользователей. func userModeUpdateUser(ircChan string, nick string, modes string) { var channelData map[string]map[string]bool + channel, ok := userMode.Get(ircChan) if ok { @@ -48,12 +49,12 @@ func userModeUpdateUser(ircChan string, nick string, modes string) { channelData[nick] = make(map[string]bool) } - switch { - case modes[:1] == "+": + switch modes[:1] { + case "+": for _, mode := range strings.Split(modes[1:], "") { channelData[nick][mode] = true } - case modes[:1] == "-": + case "-": for _, mode := range strings.Split(modes[1:], "") { channelData[nick][mode] = false } @@ -62,9 +63,10 @@ func userModeUpdateUser(ircChan string, nick string, modes string) { userMode.Set(ircChan, channelData) } -// Возвращает значение MODE-а v для запрошенного ника +// Возвращает значение MODE-а v для запрошенного ника. func userModeIsVoiced(ircChan string, nick string) bool { var channelData map[string]map[string]bool + channel, ok := userMode.Get(ircChan) if ok { @@ -88,9 +90,10 @@ func userModeIsVoiced(ircChan string, nick string) bool { return false } -// Возвращает значение MODE-а o для запрошенного ника +// Возвращает значение MODE-а o для запрошенного ника. func userModeIsOped(ircChan string, nick string) bool { var channelData map[string]map[string]bool + channel, ok := userMode.Get(ircChan) if ok { @@ -116,6 +119,7 @@ func userModeIsOped(ircChan string, nick string) bool { func userModeIsHere(ircChan string, nick string) bool { var channelData map[string]map[string]bool + channel, ok := userMode.Get(ircChan) if ok { diff --git a/main.go b/main.go index c0a214a..a5352c2 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,7 @@ import ( log "github.com/sirupsen/logrus" ) -// Производит некоторую инициализацию перед запуском main() +// Производит некоторую инициализацию перед запуском main(). func init() { log.SetFormatter(&log.TextFormatter{ DisableQuote: true, @@ -52,7 +52,7 @@ func init() { userMode.Delete("channel") } -// Собственно, какбэ "точка входа" - основная процедура в нашем боте +// Собственно, какбэ "точка входа" - основная процедура в нашем боте. func main() { // Main context var ctx = context.Background() diff --git a/msg-parsers.go b/msg-parsers.go index 6c6bf01..207af17 100644 --- a/msg-parsers.go +++ b/msg-parsers.go @@ -9,8 +9,8 @@ import ( log "github.com/sirupsen/logrus" ) -// ircMsgParser парсит сообщения, прилетевшие из IRC-ки -func ircMsgParser(channel string, nick string, user string, source string, msg string) { +// ircMsgParser парсит сообщения, прилетевшие из IRC-ки. +func ircMsgParser(channel string, nick string, user string, source string, msg string) { //nolint: revive // nick - это выбранный пользователем nick (если он занят, то его "нарисует" сервер) // user - это короткое имя пользователя, под которым его видит сервер // source - это длинное имя пользователя, оно содержит в себе помимо user, ещё и ip с которого пришёл пользователь @@ -48,50 +48,50 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s switch { case cmd == "help" || msg == "помощь": - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%shelp | %sпомощь - это сообщение", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sanek | %sанек | %sанекдот - рандомный анекдот с anekdot.ru", config.Csign, config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sbuni - комикс-стрип hapi buni", config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sbunny - кролик", config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%srabbit | %sкролик - кролик", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%scat | %sкис - кошечка", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sdice | %sroll | %sкости - бросить кости", config.Csign, config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sdig | %sкопать - заняться археологией", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sdrink | %sпраздник - какой сегодня праздник?", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sfish | %sfisher - порыбачить", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sрыба | %sрыбка | %sрыбалка - порыбачить", config.Csign, config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sf | %sф - рандомная фраза из сборника цитат fortune_mod", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sfortune | %sфортунка - рандомная фраза из сборника цитат fortune_mod", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sfox | %sлис - лисичка", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sfriday | %sпятница - а не пятница ли сегодня?", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sfrog | %sлягушка - лягушка", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%shorse | %sлошадь | %sлошадка - лошадка", config.Csign, config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%skarma фраза - посмотреть карму фразы", config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sкарма фраза - посмотреть карму фразы", config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintln("фраза++ | фраза-- - повысить или понизить карму фразы")} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%slat | %sлат - сгенерировать фразу из крылатого латинского выражения", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%smonkeyuser - комикс-стрип MonkeyUser", config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sowl | %sсова - сова", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sping | %sпинг - попинговать бота", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sproverb | %sпословица - рандомная русская пословица", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%ssnail | %sулитка - улитка", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%ssome_brew - выдать соответствующий напиток, бармен может налить rum, ром, vodka, водку, tequila, текила, whisky, виски, absinthe, абсент", config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sver | %sversion - написать что-то про версию ПО", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sверсия - написать что-то про версию ПО", config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sw <город> | %sп <город> - погода в городе", config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sxkcd - комикс-стрип с xkcb.ru", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%shelp | %sпомощь - это сообщение", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sanek | %sанек | %sанекдот - рандомный анекдот с anekdot.ru", config.Csign, config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sbuni - комикс-стрип hapi buni", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sbunny - кролик", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%srabbit | %sкролик - кролик", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%scat | %sкис - кошечка", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sdice | %sroll | %sкости - бросить кости", config.Csign, config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sdig | %sкопать - заняться археологией", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sdrink | %sпраздник - какой сегодня праздник?", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfish | %sfisher - порыбачить", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sрыба | %sрыбка | %sрыбалка - порыбачить", config.Csign, config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sf | %sф - рандомная фраза из сборника цитат fortune_mod", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfortune | %sфортунка - рандомная фраза из сборника цитат fortune_mod", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfox | %sлис - лисичка", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfriday | %sпятница - а не пятница ли сегодня?", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sfrog | %sлягушка - лягушка", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%shorse | %sлошадь | %sлошадка - лошадка", config.Csign, config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%skarma фраза - посмотреть карму фразы", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sкарма фраза - посмотреть карму фразы", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintln("фраза++ | фраза-- - повысить или понизить карму фразы")} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%slat | %sлат - сгенерировать фразу из крылатого латинского выражения", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%smonkeyuser - комикс-стрип MonkeyUser", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sowl | %sсова - сова", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sping | %sпинг - попинговать бота", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sproverb | %sпословица - рандомная русская пословица", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%ssnail | %sулитка - улитка", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%ssome_brew - выдать соответствующий напиток, бармен может налить rum, ром, vodka, водку, tequila, текила, whisky, виски, absinthe, абсент", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sver | %sversion - написать что-то про версию ПО", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sверсия - написать что-то про версию ПО", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sw <город> | %sп <город> - погода в городе", config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sxkcd - комикс-стрип с xkcb.ru", config.Csign)} if userModeIsOped(channel, nick) { - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sadmin - настройки некоторых плагинов бота для канала", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin - настройки некоторых плагинов бота для канала", config.Csign)} } return case cmd == "admin": if userModeIsOped(channel, nick) { - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sadmin oboobs # - где 1 - вкл, 0 - выкл плагина oboobs", config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sadmin oboobs показываем ли сисечки по просьбе участников чата (команды %stits, %stities, %sboobs, %sboobies, %sсиси, %sсисечки)", config.Csign, config.Csign, config.Csign, config.Csign, config.Csign, config.Csign, config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sadmin obutts # - где 1 - вкл, 0 - выкл плагина obutts", config.Csign)} - imChan <- iMsg{ChatId: nick, Text: fmt.Sprintf("%sadmin obutts показываем ли попки по просьбе участников чата (команды %sass, %sbutt, %sbooty, %sпопа, %sпопка)", config.Csign, config.Csign, config.Csign, config.Csign, config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin oboobs # - где 1 - вкл, 0 - выкл плагина oboobs", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin oboobs показываем ли сисечки по просьбе участников чата (команды %stits, %stities, %sboobs, %sboobies, %sсиси, %sсисечки)", config.Csign, config.Csign, config.Csign, config.Csign, config.Csign, config.Csign, config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin obutts # - где 1 - вкл, 0 - выкл плагина obutts", config.Csign)} + imChan <- iMsg{ChatID: nick, Text: fmt.Sprintf("%sadmin obutts показываем ли попки по просьбе участников чата (команды %sass, %sbutt, %sbooty, %sпопа, %sпопка)", config.Csign, config.Csign, config.Csign, config.Csign, config.Csign, config.Csign)} } return @@ -100,16 +100,16 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s if userModeIsOped(channel, nick) { value := getSetting(channel, "oboobs") - switch { - case value == "": + switch value { + case "": _ = saveSetting(channel, "oboobs", "0") - imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"} - case value == "0": - imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"} - case value == "1": - imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs включен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"} + case "0": + imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"} + case "1": + imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs включен"} default: - imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"} } } @@ -120,9 +120,9 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s err := saveSetting(channel, "oboobs", "1") if err != nil { - imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs всё ещё выключен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs всё ещё выключен"} } else { - imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs включен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs включен"} } } @@ -131,7 +131,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s case cmd == "admin oboobs 0": if userModeIsOped(channel, nick) { _ = saveSetting(channel, "oboobs", "0") - imChan <- iMsg{ChatId: nick, Text: "Плагин oboobs выключен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин oboobs выключен"} } return @@ -140,16 +140,16 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s if userModeIsOped(channel, nick) { value := getSetting(channel, "obutts") - switch { - case value == "": + switch value { + case "": _ = saveSetting(channel, "obutts", "0") - imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"} - case value == "0": - imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"} - case value == "1": - imChan <- iMsg{ChatId: nick, Text: "Плагин obutts включен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"} + case "0": + imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"} + case "1": + imChan <- iMsg{ChatID: nick, Text: "Плагин obutts включен"} default: - imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"} } } @@ -160,9 +160,9 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s err := saveSetting(channel, "obutts", "1") if err != nil { - imChan <- iMsg{ChatId: nick, Text: "Плагин obutts всё ещё выключен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин obutts всё ещё выключен"} } else { - imChan <- iMsg{ChatId: nick, Text: "Плагин obutts включен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин obutts включен"} } } @@ -171,7 +171,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s case cmd == "admin obutts 0": if userModeIsOped(channel, nick) { _ = saveSetting(channel, "obutts", "0") - imChan <- iMsg{ChatId: nick, Text: "Плагин obutts выключен"} + imChan <- iMsg{ChatID: nick, Text: "Плагин obutts выключен"} } return @@ -192,6 +192,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s if cmd == command { done = true outgoingMessage = msg + break } } @@ -206,6 +207,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s if cmd == command { done = true outgoingMessage = msg + break } } @@ -226,7 +228,8 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s message.Misc.Username = strings.TrimSpace(pile[1]) } else { msg = fmt.Sprintf("Я тут не вижу участника с ником %s", userNick) - imChan <- iMsg{ChatId: channel, Text: msg} + imChan <- iMsg{ChatID: channel, Text: msg} + return } } // в противном случае юзер просто поставил пробел на конце команды, интерпретируем это как @@ -234,6 +237,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s outgoingMessage = msg done = true + break } } @@ -250,6 +254,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s if cmdLen > len(command) && cmd[0:len(command)] == command { done = true outgoingMessage = msg + break } } @@ -267,6 +272,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s if cmd[:cmdLen] == command { done = true outgoingMessage = msg + break } } @@ -283,6 +289,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s if cmd[:cmdLen] == command { // done = true outgoingMessage = msg + break } } @@ -297,6 +304,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s if err != nil { log.Warnf("Unable to to serialize message for redis: %s", err) + return } @@ -351,6 +359,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s if err != nil { log.Warnf("Unable to to serialize message for redis: %s", err) + return } @@ -363,7 +372,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s } } -// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными +// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными. func redisMsgParser(msg string) { if shutdown { // Если мы завершаем работу программы, то нам ничего обрабатывать не надо @@ -376,43 +385,51 @@ func redisMsgParser(msg string) { if err := json.Unmarshal([]byte(msg), &j); err != nil { log.Warnf("Unable to to parse message from redis channel: %s", err) + return } // Validate our j if exist := j.From; exist == "" { log.Warnf("Incorrect msg from redis, no from field: %s", msg) + return } if exist := j.Chatid; exist == "" { log.Warnf("Incorrect msg from redis, no chatid field: %s", msg) + return } if exist := j.Userid; exist == "" { log.Warnf("Incorrect msg from redis, no userid field: %s", msg) + return } if exist := j.Message; exist == "" { log.Warnf("Incorrect msg from redis, no message field: %s", msg) + return } if exist := j.Plugin; exist == "" { log.Warnf("Incorrect msg from redis, no plugin field: %s", msg) + return } if exist := j.Mode; exist == "" { log.Warnf("Incorrect msg from redis, no mode field: %s", msg) + return } // j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет if j.Misc.Answer == 0 { log.Debug("Field Misc->Answer = 0, skipping message") + return } @@ -436,9 +453,9 @@ func redisMsgParser(msg string) { for _, message := range lines { if userModeIsOped(j.Chatid, ircClient.GetNick()) || userModeIsVoiced(j.Chatid, ircClient.GetNick()) { - imChanUnrestricted <- iMsg{ChatId: j.Chatid, Text: message} + imChanUnrestricted <- iMsg{ChatID: j.Chatid, Text: message} } else { - imChan <- iMsg{ChatId: j.Chatid, Text: message} + imChan <- iMsg{ChatID: j.Chatid, Text: message} } } } diff --git a/settings-db-util.go b/settings-db-util.go index 58763c9..00562fe 100644 --- a/settings-db-util.go +++ b/settings-db-util.go @@ -11,9 +11,10 @@ import ( log "github.com/sirupsen/logrus" ) -// StoreKV сохраняет в указанной бд ключ и значение +// StoreKV сохраняет в указанной бд ключ и значение. func StoreKV(db *pebble.DB, key string, value string) error { var kArray = []byte(key) + var vArray = []byte(value) err := db.Set(kArray, vArray, pebble.Sync) @@ -21,11 +22,12 @@ func StoreKV(db *pebble.DB, key string, value string) error { return err } -// FetchV достаёт значение по ключу +// FetchV достаёт значение по ключу. func FetchV(db *pebble.DB, key string) (string, error) { var kArray = []byte(key) var vArray []byte + var valueString = "" vArray, closer, err := db.Get(kArray) @@ -40,7 +42,7 @@ func FetchV(db *pebble.DB, key string) (string, error) { return valueString, err } -// Достанем настройку из БД с настройками +// Достанем настройку из БД с настройками. func getSetting(chatID string, setting string) string { var err error @@ -58,6 +60,7 @@ func getSetting(chatID string, setting string) string { if err != nil { log.Errorf("Unable to open settings db, %s: %s\n", database, err) + return "" } } @@ -66,13 +69,14 @@ func getSetting(chatID string, setting string) string { // Если из базы ничего не вынулось, по каким-то причинам, то просто вернём пустую строку if err != nil { - if errors.Is(err, pebble.ErrNotFound) { + switch { + case errors.Is(err, pebble.ErrNotFound): log.Debugf("Unable to get value for %s: no record found in db %s", setting, database) - } else if errors.Is(err, fs.ErrNotExist) { + case errors.Is(err, fs.ErrNotExist): log.Debugf("Unable to get value for %s: db dir %s does not exist", setting, database) - } else if errors.Is(err, oserror.ErrNotExist) { + case errors.Is(err, oserror.ErrNotExist): log.Debugf("Unable to get value for %s: db dir %s does not exist", setting, database) - } else { + default: log.Errorf("Unable to get value for %s in db dir %s: %s", setting, database, err) } @@ -82,10 +86,12 @@ func getSetting(chatID string, setting string) string { return value } -// Сохраним настройку в БД с настройками +// Сохраним настройку в БД с настройками. func saveSetting(chatID string, setting string, value string) error { var chatHash = sha256.Sum256([]byte(chatID)) + var database = fmt.Sprintf("settings_db/%x", chatHash) + var err error // Если БД не открыта, откроем её @@ -100,6 +106,7 @@ func saveSetting(chatID string, setting string, value string) error { if err != nil { log.Errorf("Unable to open settings db, %s: %s\n", database, err) + return err } } diff --git a/types.go b/types.go index 92347db..65412f0 100644 --- a/types.go +++ b/types.go @@ -1,6 +1,6 @@ package main -// Конфиг +// Конфиг. type myConfig struct { Redis struct { Server string `json:"server,omitempty"` @@ -35,7 +35,7 @@ type myConfig struct { DataDir string `json:"data_dir,omitempty"` } -// Входящее сообщение из pubsub-канала redis-ки +// Входящее сообщение из pubsub-канала redis-ки. type rMsg struct { From string `json:"from,omitempty"` Chatid string `json:"chatid,omitempty"` @@ -55,7 +55,7 @@ type rMsg struct { } `json:"Misc"` } -// Исходящее сообщение в pubsub-канал redis-ки +// Исходящее сообщение в pubsub-канал redis-ки. type sMsg struct { From string `json:"from"` Chatid string `json:"chatid"` @@ -75,14 +75,13 @@ type sMsg struct { } `json:"misc"` } -// Сообщение в irc -// TODO: Имплементировать тип сообщения - privmsg или action +// TODO: Имплементировать тип сообщения - privmsg или action. type iMsg struct { - ChatId string + ChatID string Text string } -// "Ведёрко" для реализации ограничителя скорости отправки исходящих сообщений - Token Bucket +// "Ведёрко" для реализации ограничителя скорости отправки исходящих сообщений - Token Bucket. type bucket struct { Timestamps []int64 IsFull bool diff --git a/util.go b/util.go index 35d9ee9..2adc3df 100644 --- a/util.go +++ b/util.go @@ -11,7 +11,7 @@ import ( log "github.com/sirupsen/logrus" ) -// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет +// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет. func readConfig() { configLoaded := false executablePath, err := os.Executable() @@ -40,6 +40,7 @@ func readConfig() { // Конфиг-файл длинноват для конфига, попробуем следующего кандидата if fileInfo.Size() > 65535 { log.Warnf("Config file %s is too long for config, skipping", location) + continue } @@ -48,6 +49,7 @@ func readConfig() { // Не удалось прочитать, попробуем следующего кандидата if err != nil { log.Warnf("Skip reading config file %s: %s", location, err) + continue } @@ -55,12 +57,14 @@ func readConfig() { // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку // сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется. var sampleConfig myConfig + var tmp map[string]interface{} err = hjson.Unmarshal(buf, &tmp) // Не удалось распарсить - попробуем следующего кандидата if err != nil { log.Warnf("Skip parsing config file %s: %s", location, err) + continue } @@ -69,17 +73,20 @@ func readConfig() { // Не удалось преобразовать map-ку в json if err != nil { log.Warnf("Skip parsing config file %s: %s", location, err) + continue } if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil { log.Warnf("Skip parsing config file %s: %s", location, err) + continue } // Валидируем значения из конфига if sampleConfig.Redis.Server == "" { sampleConfig.Redis.Server = "localhost" + log.Infof("Redis server is not defined in config, using localhost") } @@ -100,11 +107,13 @@ func readConfig() { // Значения для IRC-клиента if sampleConfig.Irc.Server == "" { sampleConfig.Irc.Server = "localhost" + log.Errorf("Irc server is not defined in config, using localhost") } if sampleConfig.Irc.Port == 0 { sampleConfig.Irc.Port = 6667 + log.Infof("Irc port is not defined in config, using 6667") } @@ -181,7 +190,9 @@ func readConfig() { config = sampleConfig configLoaded = true + log.Infof("Using %s as config file", location) + break } @@ -191,7 +202,7 @@ func readConfig() { } } -// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения +// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения. func sigHandler() { var err error