diff --git a/Makefile b/Makefile index bac1b3b..3098841 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,87 @@ #!/usr/bin/env gmake -f GOOPTS=CGO_ENABLED=0 -BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath +BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false +MYNAME=aleesa-irc-go +BINARY=${MYNAME} +UNIX_BINARY=${MYNAME} +WINDOWS_BINARY=${MYNAME}.exe +RMCMD=rm -rf + +# На windows имя бинарника может зависеть не только от платформы, но и от выбранной цели, для linux-а суффикс .exe +# не нужен +ifeq ($(OS),Windows_NT) +ifdef GOOS +ifeq ($(GOOS),windows) +BINARY=${WINDOWS_BINARY} +else # not ifeq ($(GOOS),windows) +BINARY=${MYNAME} +endif # ifeq ($(GOOS),windows) +else # not ifdef GOOS +BINARY=${WINDOWS_BINARY} +endif # ifdef GOOS +ifeq ($(SHELL), sh.exe) +RMCMD=DEL /Q /F +endif +endif + +# Явно определяем символ новой строки, чтобы избежать неоднозначности на windows +define IFS + +endef + all: clean build + build: - ${GOOPTS} go build ${BUILDOPTS} -o aleesa-irc-go . +ifeq ($(OS),Windows_NT) +# Looks like on windows gnu make explicitly set SHELL to sh.exe, if it was not set. +ifeq ($(SHELL), sh.exe) +# # Vanilla cmd.exe / powershell. + SET "CGO_ENABLED=0" + go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME} +else ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL)))) +# # git-bash + CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME} +else # not ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL)))) +# # Some other shell. +# # TODO: handle it. + $(info "-- Dunno how to handle this shell: ${SHELL}") +endif # ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL)))) +else # not ($(OS),Windows_NT) + CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME} +endif # ifeq ($(OS),Windows_NT) + clean: - go clean +ifeq ($(OS),Windows_NT) +ifeq ($(SHELL),sh.exe) +# # Vanilla cmd.exe / powershell. + if exist ${WINDOWS_BINARY} ${RMCMD} ${WINDOWS_BINARY} + if exist ${UNIX_BINARY} ${RMCMD} ${UNIX_BINARY} +else # not ifeq ($(SHELL),sh.exe) + ${RMCMD} ./${WINDOWS_BINARY} + ${RMCMD} ./${UNIX_BINARY} +endif # ifeq ($(SHELL),sh.exe) +else # not ifeq ($(OS),Windows_NT) + ${RMCMD} ./${BINARY} +endif + upgrade: - rm -rf vendor - go get -u -t -tool ./... +ifeq ($(OS),Windows_NT) +ifeq ($(SHELL),sh.exe) +# # Vanilla cmd.exe / powershell. + if exist vendor DEL /F /S /Q vendor >nul +else # not ifeq ($(SHELL),sh.exe) +# # git-bash + $(RM) -r vendor +endif # ifeq ($(SHELL),sh.exe) +else # not ifeq ($(OS),Windows_NT) + $(RM) -r vendor +endif + go get -u ./... go mod tidy go mod vendor diff --git a/aleesa-irc-go-lib.go b/internal/irc/aleesa-irc-go-lib.go similarity index 92% rename from aleesa-irc-go-lib.go rename to internal/irc/aleesa-irc-go-lib.go index 23e0211..abb3701 100644 --- a/aleesa-irc-go-lib.go +++ b/internal/irc/aleesa-irc-go-lib.go @@ -1,4 +1,4 @@ -package main +package irc import ( "crypto/tls" @@ -15,24 +15,24 @@ import ( ) // ircClientInit горутинка для работы с протоколом irc. -func ircClientRun() { - for !shutdown { +func IrcClientRun() { + for !Shutdown { // Иницализируем irc-клиента. // TODO: make use of capabilities: https://defs.ircdocs.horse/defs/clientcaps . // TODO: make use of tags caps https://defs.ircdocs.horse/defs/tags . - serverString := fmt.Sprintf("%s:%d", config.Irc.Server, config.Irc.Port) + serverString := fmt.Sprintf("%s:%d", Config.Irc.Server, Config.Irc.Port) log.Debugf("Preparing to connect to %s", serverString) - log.Debugf("Using nick %s and username %s", config.Irc.Nick, config.Irc.User) - ircClient = irc.IRC(config.Irc.Nick, config.Irc.User) - ircClient.RealName = config.Irc.User + log.Debugf("Using nick %s and username %s", Config.Irc.Nick, Config.Irc.User) + ircClient = irc.IRC(Config.Irc.Nick, Config.Irc.User) + ircClient.RealName = Config.Irc.User ircClient.Version = "Aleesa Bot v4.something" - if config.Irc.Ssl { + if Config.Irc.Ssl { log.Debug("Force use ssl for connection") ircClient.UseTLS = true - if !config.Irc.SslVerify { + if !Config.Irc.SslVerify { log.Debug("Skip server certificate validation") ircClient.TLSConfig = &tls.Config{InsecureSkipVerify: true} @@ -43,9 +43,9 @@ func ircClientRun() { log.Debug("Skip ssl for connection") } - if config.Irc.Sasl && config.Irc.Password != "" { - ircClient.SASLLogin = config.Irc.User - ircClient.SASLPassword = config.Irc.Password + if Config.Irc.Sasl && Config.Irc.Password != "" { + ircClient.SASLLogin = Config.Irc.User + ircClient.SASLPassword = Config.Irc.Password ircClient.UseSASL = true } @@ -95,7 +95,7 @@ func ircClientRun() { dstNick := e.Arguments[1] for _, channelWithMode := range channelsWithModes { - for _, mychannel := range config.Irc.Channels { + for _, mychannel := range Config.Irc.Channels { tmp := regexp.MustCompile("#").Split(channelWithMode, 2) channel := tmp[1] modes := tmp[0] @@ -129,7 +129,7 @@ func ircClientRun() { namesString := e.Arguments[3] channel := e.Arguments[2] - for _, mychannel := range config.Irc.Channels { + for _, mychannel := range Config.Irc.Channels { if channel == mychannel { for _, name := range strings.Split(namesString, " ") { mode := name[:1] @@ -179,17 +179,17 @@ func ircClientRun() { log.Info("Skip +B flag, server did not announce modes (yet?), noone will know that I am bot") } - if !config.Irc.Sasl && config.Irc.Password != "" && !nickIsUsed { + 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) + message := fmt.Sprintf("identify %s %s", Config.Irc.Nick, Config.Irc.Password) imChan <- iMsg{ChatID: "NickServ", Text: message} } // TODO: wait until +R flag been set? could be implemented with waiting for channel message. log.Debug("Trying to join to preconfigured channels") - for _, channel := range config.Irc.Channels { + for _, channel := range Config.Irc.Channels { log.Infof("Joining to %s channel", channel) ircClient.Join(channel) } @@ -248,17 +248,17 @@ func ircClientRun() { } } - if !config.Irc.Sasl && config.Irc.Password != "" && !nickIsUsed { + 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) + message := fmt.Sprintf("identify %s %s", Config.Irc.Nick, Config.Irc.Password) imChan <- iMsg{ChatID: "NickServ", Text: message} } // TODO: wait until +R flag been set? could be implemented with waiting for channel message. log.Debug("Trying to join to preconfigured channels") - for _, channel := range config.Irc.Channels { + for _, channel := range Config.Irc.Channels { log.Infof("Joining to %s channel", channel) ircClient.Join(channel) } @@ -279,7 +279,7 @@ func ircClientRun() { <-time.NewTimer(60 * time.Second).C // 1 minute sleep. - ircClient.Nick(config.Irc.Nick) + ircClient.Nick(Config.Irc.Nick) }) ircClient.AddCallback("436", func(e *irc.Event) { @@ -345,7 +345,7 @@ func ircClientRun() { <-time.NewTimer(30 * time.Second).C // Проверяем, а должны ли мы быть заджоенныеми к указанному, каналу, а то вдруг нет? - if slices.Contains(config.Irc.Channels, channel) { + if slices.Contains(Config.Irc.Channels, channel) { ircClient.Join(channel) } }) @@ -362,7 +362,7 @@ func ircClientRun() { <-time.NewTimer(30 * time.Second).C // Проверяем, а должны ли мы быть заджоенными к указанному, каналу, а то вдруг нет? - if slices.Contains(config.Irc.Channels, channel) { + if slices.Contains(Config.Irc.Channels, channel) { ircClient.Join(channel) } }) @@ -379,7 +379,7 @@ func ircClientRun() { // TODO: вынести в настройки? // Проверяем, а должны ли мы быть заджоенныеми к указанному, каналу, а то вдруг нет? - if slices.Contains(config.Irc.Channels, channel) { + if slices.Contains(Config.Irc.Channels, channel) { ircClient.Join(channel) } }) @@ -432,7 +432,7 @@ func ircClientRun() { if e.Arguments[1] == ircClient.GetNick() { // Нас кикнули с канала, и мы теряем информацию о MODE-ах пользователей - userMode.Delete(channel) + UserMode.Delete(channel) // TODO: reason? log.Warnf("%s kicks us from %s", srcFullNick, channel) <-time.NewTimer(10 * time.Second).C @@ -449,15 +449,15 @@ func ircClientRun() { srcNick := e.Nick dstNick := e.Arguments[0] - if e.Nick == config.Irc.Nick { + if e.Nick == Config.Irc.Nick { nickIsUsed = false - if config.Irc.Password != "" { + if Config.Irc.Password != "" { 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) + message := fmt.Sprintf("identify %s %s", Config.Irc.Nick, Config.Irc.Password) imChan <- iMsg{ChatID: "NickServ", Text: message} } } else { @@ -493,7 +493,7 @@ func ircClientRun() { if nick == ircClient.GetNick() { log.Infof("I parted from %s", channel) - userMode.Delete(channel) + UserMode.Delete(channel) } else { log.Infof("%s parted from %s", fullNick, channel) userModeDeleteUser(channel, nick) @@ -591,11 +591,11 @@ func ircClientRun() { // ircSend отправляет сообщение в irc. Якобы с применением ratelimit-ов, но на практике длинные сообщения разделяются // на более короткие и тут ratelimit не срабатывает. Если не повезёт, то сервер может за такое дело и кикнуть. -func ircSend() { +func IrcSend() { for { m := <-imChan - switch config.Irc.RateLimit.Type { + switch Config.Irc.RateLimit.Type { case "simple_delay": log.Debugf("Sending to chat %s message: %s", m.ChatID, m.Text) @@ -605,13 +605,13 @@ func ircSend() { ircClient.Privmsg(m.ChatID, m.Text) } - sleepDelay := time.Duration(config.Irc.RateLimit.SimpleDelay) * time.Millisecond + sleepDelay := time.Duration(Config.Irc.RateLimit.SimpleDelay) * time.Millisecond log.Debugf("Due to delay type simple_delay waiting for %d milliseconds", int(sleepDelay)) <-time.NewTimer(sleepDelay).C case "token_bucket": var currentTimeMs = time.Now().UnixMilli() - var expirationTimeMs = config.Irc.RateLimit.TokenBucket.ExpirationTime * 1000 + var expirationTimeMs = Config.Irc.RateLimit.TokenBucket.ExpirationTime * 1000 if len(msgBucket.Timestamps) > 0 { var newBucket bucket @@ -623,11 +623,11 @@ func ircSend() { } bucketFill := len(newBucket.Timestamps) - log.Debugf("Message bucket filled with %d/%d messages", bucketFill, config.Irc.RateLimit.TokenBucket.Size) + 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 { + if bucketFill >= Config.Irc.RateLimit.TokenBucket.Size { newBucket.IsFull = true } @@ -644,8 +644,8 @@ func ircSend() { sleepPeriod := expirationTimeMs - (currentTimeMs - msgBucket.Timestamps[len(msgBucket.Timestamps)-1]) - if config.Irc.RateLimit.TokenBucket.Limit > 0 { - sleepPeriod /= int64(config.Irc.RateLimit.TokenBucket.Limit) + if Config.Irc.RateLimit.TokenBucket.Limit > 0 { + sleepPeriod /= int64(Config.Irc.RateLimit.TokenBucket.Limit) } log.Debugf("Sleeping for %d milliseconds", sleepPeriod) @@ -675,7 +675,7 @@ func ircSend() { } } -func ircSendUnrestricted() { +func IrcSendUnrestricted() { for { m := <-imChanUnrestricted log.Debugf("Skipping ratelimit and sending to chat %s message: %s", m.ChatID, m.Text) diff --git a/globals.go b/internal/irc/globals.go similarity index 89% rename from globals.go rename to internal/irc/globals.go index 81c2c49..a808a2c 100644 --- a/globals.go +++ b/internal/irc/globals.go @@ -1,4 +1,4 @@ -package main +package irc import ( "context" @@ -12,7 +12,7 @@ import ( ) // Config - это у нас глобальная штука :). -var config myConfig +var Config MyConfig // To break circular message forwarding we must set some sane default, it can be overridden via config. var forwardMax int64 = 5 @@ -22,17 +22,17 @@ var ircClient *irc.Connection var nickIsUsed = false // Объектики клиента-редиски. -var redisClient *redis.Client -var subscriber *redis.PubSub +var RedisClient *redis.Client +var Subscriber *redis.PubSub // Main context. var ctx = context.Background() // Ставится в true, если мы получили сигнал на выключение. -var shutdown = false +var Shutdown = false // Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов. -var sigChan = make(chan os.Signal, 1) +var SigChan = make(chan os.Signal, 1) // Канал, в который пишутся сообщения для отправки в IRC в обычном порядке. var imChan = make(chan iMsg, 10000) @@ -44,7 +44,7 @@ var imChanUnrestricted = make(chan iMsg, 100) var msgBucket bucket // "Базюлька" с MODE-ами пользователей на каналах. -var userMode = anycollection.NewCollection() +var UserMode = anycollection.NewCollection() // "Базюлька" с доступными MODE-ами пользователя. var availableUserModes = boolcollection.NewCollection() diff --git a/irc-user-mode.go b/internal/irc/irc-user-mode.go similarity index 89% rename from irc-user-mode.go rename to internal/irc/irc-user-mode.go index fd6293e..17d2b54 100644 --- a/irc-user-mode.go +++ b/internal/irc/irc-user-mode.go @@ -1,4 +1,4 @@ -package main +package irc import "strings" @@ -10,23 +10,23 @@ import "strings" // Удаляет сведения о MODE-ах пользователя для заданного канала. func userModeDeleteUser(ircChan string, nick string) { - channel, ok := userMode.Get(ircChan) + channel, ok := UserMode.Get(ircChan) if ok { channelData := channel.(map[string]map[string]bool) delete(channelData, nick) if len(channelData) == 0 { - userMode.Delete(ircChan) + UserMode.Delete(ircChan) } else { - userMode.Set(ircChan, channelData) + UserMode.Set(ircChan, channelData) } } } // Удаляет сведения о MODE-ах пользователя для всех каналов, на которых есть бот. func userModePurgeUser(nick string) { - for _, channel := range config.Irc.Channels { + for _, channel := range Config.Irc.Channels { userModeDeleteUser(channel, nick) } } @@ -35,7 +35,7 @@ func userModePurgeUser(nick string) { func userModeUpdateUser(ircChan string, nick string, modes string) { var channelData map[string]map[string]bool - channel, ok := userMode.Get(ircChan) + channel, ok := UserMode.Get(ircChan) if ok { channelData = channel.(map[string]map[string]bool) @@ -60,14 +60,14 @@ func userModeUpdateUser(ircChan string, nick string, modes string) { } } - userMode.Set(ircChan, channelData) + UserMode.Set(ircChan, channelData) } // Возвращает значение MODE-а v для запрошенного ника. func userModeIsVoiced(ircChan string, nick string) bool { var channelData map[string]map[string]bool - channel, ok := userMode.Get(ircChan) + channel, ok := UserMode.Get(ircChan) if ok { channelData = channel.(map[string]map[string]bool) @@ -94,7 +94,7 @@ func userModeIsVoiced(ircChan string, nick string) bool { func userModeIsOped(ircChan string, nick string) bool { var channelData map[string]map[string]bool - channel, ok := userMode.Get(ircChan) + channel, ok := UserMode.Get(ircChan) if ok { channelData = channel.(map[string]map[string]bool) @@ -120,7 +120,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) + channel, ok := UserMode.Get(ircChan) if ok { channelData = channel.(map[string]map[string]bool) diff --git a/msg-parsers.go b/internal/irc/msg-parsers.go similarity index 84% rename from msg-parsers.go rename to internal/irc/msg-parsers.go index c660a7b..bd9d415 100644 --- a/msg-parsers.go +++ b/internal/irc/msg-parsers.go @@ -1,4 +1,4 @@ -package main +package irc import ( "encoding/json" @@ -16,7 +16,7 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s // nick - это выбранный пользователем nick (если он занят, то его "нарисует" сервер) // user - это короткое имя пользователя, под которым его видит сервер // source - это длинное имя пользователя, оно содержит в себе помимо user, ещё и ip с которого пришёл пользователь - if shutdown { + if Shutdown { // Если мы завершаем работу программы, то нам ничего обрабатывать не надо return } @@ -28,72 +28,72 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s } // Ловим команды и обрабатываем их - if (len(msg) > len(config.Csign)) && (msg[:len(config.Csign)] == config.Csign) { + if (len(msg) > len(Config.Csign)) && (msg[:len(Config.Csign)] == Config.Csign) { var outgoingMessage string var message sMsg - message.From = config.Redis.MyChannel + message.From = Config.Redis.MyChannel message.Userid = user // как его видит сервер message.Chatid = channel // чятик, в который написал user message.Threadid = "" // тредиков в irc нету, поэтому это поле отправляем пустым - message.Plugin = config.Redis.MyChannel + message.Plugin = Config.Redis.MyChannel message.Mode = "public" // Предполагается, что на команды бот автоматом отвечает message.Misc.Answer = 1 message.Misc.Fwdcnt = 0 - message.Misc.Csign = config.Csign + message.Misc.Csign = Config.Csign message.Misc.Username = nick - message.Misc.Botnick = config.Irc.Nick + message.Misc.Botnick = Config.Irc.Nick message.Misc.Msgformat = 0 - var cmd = msg[len(config.Csign):] + var cmd = msg[len(Config.Csign):] 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.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("%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 @@ -311,21 +311,21 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s } // Заталкиваем наш json в редиску - if err := redisClient.Publish(ctx, config.Redis.Channel, data).Err(); err != nil { - log.Warnf("Unable to send data to redis channel %s: %s", config.Redis.Channel, err) + if err := RedisClient.Publish(ctx, Config.Redis.Channel, data).Err(); err != nil { + log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err) } else { - log.Debugf("Sent msg to redis channel %s: %s", config.Redis.Channel, string(data)) + log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data)) } } } else { // Это уже просто трёп в чятике var message sMsg - message.From = config.Redis.MyChannel + message.From = Config.Redis.MyChannel message.Userid = user // как его видит сервер message.Chatid = channel // чятик, в который написал user message.Threadid = "" // тредиков в irc нету, поэтому это поле отправляем пустым message.Message = msg - message.Plugin = config.Redis.MyChannel + message.Plugin = Config.Redis.MyChannel message.Mode = "public" // Если во фразе содержится ключевые слова, под это есть костыль в craniac-е - (предполагаем, что) он @@ -352,9 +352,9 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s } message.Misc.Fwdcnt = 0 - message.Misc.Csign = config.Csign + message.Misc.Csign = Config.Csign message.Misc.Username = nick - message.Misc.Botnick = config.Irc.Nick + message.Misc.Botnick = Config.Irc.Nick message.Misc.Msgformat = 0 data, err := json.Marshal(message) @@ -366,17 +366,17 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s } // Заталкиваем наш json в редиску - if err := redisClient.Publish(ctx, config.Redis.Channel, data).Err(); err != nil { - log.Warnf("Unable to send data to redis channel %s: %s", config.Redis.Channel, err) + if err := RedisClient.Publish(ctx, Config.Redis.Channel, data).Err(); err != nil { + log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err) } else { - log.Debugf("Sent msg to redis channel %s: %s", config.Redis.Channel, string(data)) + log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data)) } } } -// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными. -func redisMsgParser(msg string) { - if shutdown { +// RedisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными. +func RedisMsgParser(msg string) { + if Shutdown { // Если мы завершаем работу программы, то нам ничего обрабатывать не надо return } @@ -438,7 +438,7 @@ func redisMsgParser(msg string) { // j.Misc.BotNick тоже можно не передавать, тогда будет записана пустая строка // j.Misc.CSign если нам его не передали, возьмём значение из конфига if exist := j.Misc.Csign; exist == "" { - j.Misc.Csign = config.Csign + j.Misc.Csign = Config.Csign } // j.Misc.FwdCnt если нам его не передали, то будет 0 diff --git a/types.go b/internal/irc/types.go similarity index 98% rename from types.go rename to internal/irc/types.go index 65412f0..a1584c5 100644 --- a/types.go +++ b/internal/irc/types.go @@ -1,7 +1,7 @@ -package main +package irc // Конфиг. -type myConfig struct { +type MyConfig struct { Redis struct { Server string `json:"server,omitempty"` Port int `json:"port,omitempty"` diff --git a/util.go b/internal/irc/util.go similarity index 91% rename from util.go rename to internal/irc/util.go index 33f8e1a..549dcd7 100644 --- a/util.go +++ b/internal/irc/util.go @@ -1,4 +1,4 @@ -package main +package irc import ( "encoding/json" @@ -13,8 +13,8 @@ import ( log "github.com/sirupsen/logrus" ) -// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет. -func readConfig() { +// ReadConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет. +func ReadConfig() { configLoaded := false executablePath, err := os.Executable() @@ -58,7 +58,7 @@ func readConfig() { // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку // сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется. - var sampleConfig myConfig + var sampleConfig MyConfig var tmp map[string]interface{} err = hjson.Unmarshal(buf, &tmp) @@ -190,7 +190,7 @@ func readConfig() { os.Exit(1) } - config = sampleConfig + Config = sampleConfig configLoaded = true log.Infof("Using %s as config file", location) @@ -204,12 +204,12 @@ func readConfig() { } } -// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения. -func sigHandler() { +// SigHandler хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения. +func SigHandler() { var err error for { - var s = <-sigChan + var s = <-SigChan switch s { case syscall.SIGINT: log.Infoln("Got SIGINT, quitting") @@ -224,16 +224,16 @@ func sigHandler() { } // Чтобы не срать в логи ошибками от редиски, проставим shutdown state приложения в true - shutdown = true + Shutdown = true // Отпишемся от всех каналов и закроем коннект к редиске - if err = subscriber.Unsubscribe(ctx); err != nil { + if err = Subscriber.Unsubscribe(ctx); err != nil { log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err) } else { log.Debug("Unsubscribe from all redis channels") } - if err = subscriber.Close(); err != nil { + if err = Subscriber.Close(); err != nil { log.Errorf("Unable to close redis connection cleanly: %s", err) } else { log.Debug("Close redis connection") @@ -243,7 +243,7 @@ func sigHandler() { ircClient.Quit() log.Debug("Close userModeDB") - userMode.Close() + UserMode.Close() log.Debug("Close settingsDB") setting.CloseAll() diff --git a/main.go b/main.go deleted file mode 100644 index d32a825..0000000 --- a/main.go +++ /dev/null @@ -1,106 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "os/signal" - "syscall" - - "aleesa-irc-go/internal/setting" - - "github.com/go-redis/redis/v8" - log "github.com/sirupsen/logrus" -) - -// Производит некоторую инициализацию перед запуском main(). -func init() { - log.SetFormatter(&log.TextFormatter{ - DisableQuote: true, - DisableLevelTruncation: false, - DisableColors: true, - FullTimestamp: true, - TimestampFormat: "2006-01-02 15:04:05", - }) - - readConfig() - - // no panic, no trace - switch config.Loglevel { - case "fatal": - log.SetLevel(log.FatalLevel) - case "error": - log.SetLevel(log.ErrorLevel) - case "warn": - log.SetLevel(log.WarnLevel) - case "info": - log.SetLevel(log.InfoLevel) - case "debug": - log.SetLevel(log.DebugLevel) - default: - log.SetLevel(log.InfoLevel) - } - - // Проинициализируем мапку MODE-ами пользователей произвольным значением и удалим его - var usersModes = map[string]map[string]map[string]bool{ - "user": { - "mode": { - "v": true, - }, - }, - } - - userMode.Set("channel", usersModes) - userMode.Delete("channel") - - setting.DataDir = config.DataDir -} - -// Собственно, какбэ "точка входа" - основная процедура в нашем боте. -func main() { - // Main context - var ctx = context.Background() - - // Откроем лог и скормим его логгеру - if config.Log != "" { - logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) - - if err != nil { - log.Fatalf("Unable to open log file %s: %s", config.Log, err) - } - - log.SetOutput(logfile) - } - - // Иницализируем redis-клиента - redisClient = redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:%d", config.Redis.Server, config.Redis.Port), - }) - - log.Debugf("Lazy connect() to redis at %s:%d", config.Redis.Server, config.Redis.Port) - subscriber = redisClient.Subscribe(ctx, config.Redis.MyChannel) - redisMsgChan := subscriber.Channel() - - go ircClientRun() - go ircSend() - go ircSendUnrestricted() - - // Самое время поставить траппер сигналов - signal.Notify(sigChan, - syscall.SIGINT, - syscall.SIGTERM, - syscall.SIGQUIT) - - go sigHandler() - - // Обработчик событий от редиски - for msg := range redisMsgChan { - if shutdown { - continue - } - - redisMsgParser(msg.Payload) - } -} - -/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */