From d88f08479dd7ab155b2e28654e3f2f1ce4673cba Mon Sep 17 00:00:00 2001 From: eleksir Date: Sun, 5 Nov 2023 16:00:05 +0300 Subject: [PATCH] Move read config stuff to separate file --- Makefile | 5 +- lib.go | 261 ---------------------------------------------- read_config.go | 273 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+), 263 deletions(-) create mode 100644 read_config.go diff --git a/Makefile b/Makefile index ec466bb..514d997 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ #!/usr/bin/env gmake -f -BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -pgo=auto -FILELIST=collection.go types.go globals.go lib.go event_parser.go buny.go commands.go main.go norm_string.go bf.go +BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath +FILELIST= bf.go buny.go commands.go collection.go event_parser.go globals.go lib.go main.go norm_string.go \ + read_config.go types.go BINARY=buny-jabber-bot diff --git a/lib.go b/lib.go index cfca695..0a1de2f 100644 --- a/lib.go +++ b/lib.go @@ -68,267 +68,6 @@ func sigHandler() { } } -// readConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге -// нет. -func readConfig() error { //nolint:gocognit,gocyclo - var ( - err error - executablePath string - configLoaded = false - ) - - executablePath, err = os.Executable() - - if err != nil { - err := fmt.Errorf("unable to get current executable path: %w", err) - - return err - } - - configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) - - locations := []string{ - "~/.bunyPresense-jabber-bot.json", - "~/bunyPresense-jabber-bot.json", - "/etc/bunyPresense-jabber-bot.json", - configJSONPath, - } - - for _, location := range locations { - fileInfo, err := os.Stat(location) - - // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём. - if err != nil { - continue - } - - // Конфиг-файл длинноват для конфига, попробуем следующего кандидата. - if fileInfo.Size() > 65535 { - log.Warnf("Config file %s is too long for config, skipping", location) - - continue - } - - buf, err := os.ReadFile(location) - - // Не удалось прочитать, попробуем следующего кандидата. - if err != nil { - log.Warnf("Skip reading config file %s: %s", location, err) - - continue - } - - // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. - // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку - // сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется. - var ( - sampleConfig myConfig - tmp map[string]interface{} - ) - - err = hjson.Unmarshal(buf, &tmp) - - // Не удалось распарсить - попробуем следующего кандидата. - if err != nil { - log.Warnf("Skip parsing config file %s: %s", location, err) - - continue - } - - tmpJSON, err := json.Marshal(tmp) - - // Не удалось преобразовать 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 - } - - // Значения для Jabber-клиента - if sampleConfig.Jabber.Server == "" { - log.Error("Jabber server is not defined in config, using localhost") - sampleConfig.Jabber.Server = "localhost" //nolint:wsl - } - - if sampleConfig.Jabber.Port == 0 { - sampleConfig.Jabber.Port = 5222 - - if sampleConfig.Jabber.Ssl { - if !sampleConfig.Jabber.StartTLS { - sampleConfig.Jabber.Port = 5223 - - log.Info("Jabber port is not defined in config, using 5223") - } else { - log.Info("Jabber port is not defined in config, using 5222") - } - } - } - - if !sampleConfig.Jabber.Ssl { - sampleConfig.Jabber.StartTLS = false - } - - if !sampleConfig.Jabber.Ssl || !sampleConfig.Jabber.StartTLS { - sampleConfig.Jabber.SslVerify = false - } - - // sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан - - if sampleConfig.Jabber.ConnectionTimeout == 0 { - sampleConfig.Jabber.ConnectionTimeout = 10 - - log.Info("Jabber server connection_timeout not defined in config, using 10 seconds") - } - - if sampleConfig.Jabber.ReconnectDelay == 0 { - sampleConfig.Jabber.ReconnectDelay = 3 - - log.Info("Jabber server reconnect_delay not defined in config, using 3 seconds") - } - - if sampleConfig.Jabber.ServerPingDelay == 0 { - sampleConfig.Jabber.ServerPingDelay = 60 - - log.Info("Jabber server_ping_delay not defined in config, using 60 seconds") - } - - if sampleConfig.Jabber.MucPingDelay == 0 { - sampleConfig.Jabber.MucPingDelay = 900 - - log.Info("Jabber muc_ping_delay not defined in config, using 900 seconds") - } - - if sampleConfig.Jabber.MucRejoinDelay == 0 { - sampleConfig.Jabber.MucRejoinDelay = 3 - - log.Info("Jabber muc_rejoin_delay not defined in config, using 3 seconds") - } - - if sampleConfig.Jabber.PingSplayDelay == 0 { - sampleConfig.Jabber.PingSplayDelay = 3 - - log.Info("Jabber ping_splay_delay not defined in config, using 3 seconds") - } - - if sampleConfig.Jabber.Nick == "" { - return errors.New("jabber nick is not defined in config, quitting") //nolint:goerr113 - } - - if sampleConfig.Jabber.Resource == "" { - sampleConfig.Jabber.Resource = "bunyPresense bot" - - log.Info("Jabber resource not defined in config, using bunyPresense bot") - } - - if sampleConfig.Jabber.User == "" { - sampleConfig.Jabber.User = fmt.Sprintf("%s@%s", sampleConfig.Jabber.Nick, sampleConfig.Jabber.Server) - - log.Infof("Jabber user not defined in config, guessing, it can be %s", sampleConfig.Jabber.User) - } - - // Если sampleConfig.Jabber.Password не задан, то авторизации не будет - - // Если не задано ни одного мастера, то бот сам себе мастер - if len(sampleConfig.Jabber.BotMasters) == 0 { - sampleConfig.Jabber.BotMasters[0] = sampleConfig.Jabber.User - } - - // Нам бот нужен в каких-то чат-румах, а не "просто так" - if len(sampleConfig.Jabber.Channels) < 1 { - return errors.New("no jabber channels/rooms defined in config, quitting") //nolint:goerr113 - } - - for _, channel := range sampleConfig.Jabber.Channels { - if channel.Name == "" { - return errors.New("no \"name\" entry in jabber channel config") - } - - // channel.Password может быть пустым, тогда пароля нет - // channel.Bayes.Enabled будет false, если не проставлен - - if channel.Bayes.MinLength < 40 { - channel.Bayes.MinLength = 40 - } - - if channel.Bayes.MinWords < 8 { - channel.Bayes.MinWords = 8 - } - - switch channel.Bayes.DefaultAction { - case "kick": - case "ban": - case "devoice": - default: - channel.Bayes.DefaultAction = "log" - } - - // channel.AllCaps.Enabled будет false, если не указан - if channel.AllCaps.MinLength < 10 { - channel.AllCaps.MinLength = 10 - } - - switch channel.AllCaps.DefaultAction { - case "kick": - case "ban": - case "devoice": - default: - channel.AllCaps.DefaultAction = "log" - } - } - - // Если список фраз с которыми стартует бот пустой, вносим в него 1 запись с пустой строкой - if len(sampleConfig.Jabber.StartupStatus) == 0 { - sampleConfig.Jabber.StartupStatus[0] = "" - } - - // Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой - if len(sampleConfig.Jabber.RuntimeStatus.Text) == 0 { - sampleConfig.Jabber.RuntimeStatus.Text[0] = "" - } - - // Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0 - // Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0 - - // Если sampleConfig.Jabber.BanDelay не задан, то он равен 0 - // Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false - - // Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой - if len(sampleConfig.Jabber.BanPhrases) == 0 { - sampleConfig.Jabber.BanPhrases[0] = "" - } - - if sampleConfig.CSign == "" { - sampleConfig.CSign = "!" - } - - if sampleConfig.Loglevel == "" { - sampleConfig.Loglevel = "info" - - log.Info("loglevel not defined in config, using info") - } - - // sampleConfig.Log = "" if not set - - config = sampleConfig - configLoaded = true - log.Infof("Using %s as config file", location) //nolint:wsl - - break - } - - if !configLoaded { - return errors.New("config was not loaded!") //nolint:goerr113 - } - - return err //nolint:wrapcheck -} - // readWhitelist читает и валидирует белые списки пользователей. func readWhitelist() error { var ( diff --git a/read_config.go b/read_config.go new file mode 100644 index 0000000..ad05bb1 --- /dev/null +++ b/read_config.go @@ -0,0 +1,273 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/hjson/hjson-go" + log "github.com/sirupsen/logrus" +) + +// readConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге +// нет. +func readConfig() error { //nolint:gocognit,gocyclo + var ( + err error + executablePath string + configLoaded = false + ) + + executablePath, err = os.Executable() + + if err != nil { + err := fmt.Errorf("unable to get current executable path: %w", err) + + return err + } + + configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) + + locations := []string{ + "~/.bunyPresense-jabber-bot.json", + "~/bunyPresense-jabber-bot.json", + "/etc/bunyPresense-jabber-bot.json", + configJSONPath, + } + + for _, location := range locations { + fileInfo, err := os.Stat(location) + + // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём. + if err != nil { + continue + } + + // Конфиг-файл длинноват для конфига, попробуем следующего кандидата. + if fileInfo.Size() > 2777216 { + log.Warnf("Config file %s is too long for config, skipping", location) + + continue + } + + buf, err := os.ReadFile(location) + + // Не удалось прочитать, попробуем следующего кандидата. + if err != nil { + log.Warnf("Skip reading config file %s: %s", location, err) + + continue + } + + // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. + // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку + // сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется. + var ( + sampleConfig myConfig + tmp map[string]interface{} + ) + + err = hjson.Unmarshal(buf, &tmp) + + // Не удалось распарсить - попробуем следующего кандидата. + if err != nil { + log.Warnf("Skip parsing config file %s: %s", location, err) + + continue + } + + tmpJSON, err := json.Marshal(tmp) + + // Не удалось преобразовать 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 + } + + // Значения для Jabber-клиента + if sampleConfig.Jabber.Server == "" { + log.Error("Jabber server is not defined in config, using localhost") + sampleConfig.Jabber.Server = "localhost" //nolint:wsl + } + + if sampleConfig.Jabber.Port == 0 { + sampleConfig.Jabber.Port = 5222 + + if sampleConfig.Jabber.Ssl { + if !sampleConfig.Jabber.StartTLS { + sampleConfig.Jabber.Port = 5223 + + log.Info("Jabber port is not defined in config, using 5223") + } else { + log.Info("Jabber port is not defined in config, using 5222") + } + } + } + + if !sampleConfig.Jabber.Ssl { + sampleConfig.Jabber.StartTLS = false + } + + if !sampleConfig.Jabber.Ssl || !sampleConfig.Jabber.StartTLS { + sampleConfig.Jabber.SslVerify = false + } + + // sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан + + if sampleConfig.Jabber.ConnectionTimeout == 0 { + sampleConfig.Jabber.ConnectionTimeout = 10 + + log.Info("Jabber server connection_timeout not defined in config, using 10 seconds") + } + + if sampleConfig.Jabber.ReconnectDelay == 0 { + sampleConfig.Jabber.ReconnectDelay = 3 + + log.Info("Jabber server reconnect_delay not defined in config, using 3 seconds") + } + + if sampleConfig.Jabber.ServerPingDelay == 0 { + sampleConfig.Jabber.ServerPingDelay = 60 + + log.Info("Jabber server_ping_delay not defined in config, using 60 seconds") + } + + if sampleConfig.Jabber.MucPingDelay == 0 { + sampleConfig.Jabber.MucPingDelay = 900 + + log.Info("Jabber muc_ping_delay not defined in config, using 900 seconds") + } + + if sampleConfig.Jabber.MucRejoinDelay == 0 { + sampleConfig.Jabber.MucRejoinDelay = 3 + + log.Info("Jabber muc_rejoin_delay not defined in config, using 3 seconds") + } + + if sampleConfig.Jabber.PingSplayDelay == 0 { + sampleConfig.Jabber.PingSplayDelay = 3 + + log.Info("Jabber ping_splay_delay not defined in config, using 3 seconds") + } + + if sampleConfig.Jabber.Nick == "" { + return errors.New("jabber nick is not defined in config, quitting") //nolint:goerr113 + } + + if sampleConfig.Jabber.Resource == "" { + sampleConfig.Jabber.Resource = "bunyPresense bot" + + log.Info("Jabber resource not defined in config, using bunyPresense bot") + } + + if sampleConfig.Jabber.User == "" { + sampleConfig.Jabber.User = fmt.Sprintf("%s@%s", sampleConfig.Jabber.Nick, sampleConfig.Jabber.Server) + + log.Infof("Jabber user not defined in config, guessing, it can be %s", sampleConfig.Jabber.User) + } + + // Если sampleConfig.Jabber.Password не задан, то авторизации не будет + + // Если не задано ни одного мастера, то бот сам себе мастер + if len(sampleConfig.Jabber.BotMasters) == 0 { + sampleConfig.Jabber.BotMasters[0] = sampleConfig.Jabber.User + } + + // Нам бот нужен в каких-то чат-румах, а не "просто так" + if len(sampleConfig.Jabber.Channels) < 1 { + return errors.New("no jabber channels/rooms defined in config, quitting") //nolint:goerr113 + } + + for _, channel := range sampleConfig.Jabber.Channels { + if channel.Name == "" { + return errors.New("no \"name\" entry in jabber channel config") + } + + // channel.Password может быть пустым, тогда пароля нет + // channel.Bayes.Enabled будет false, если не проставлен + + if channel.Bayes.MinLength < 40 { + channel.Bayes.MinLength = 40 + } + + if channel.Bayes.MinWords < 8 { + channel.Bayes.MinWords = 8 + } + + switch channel.Bayes.DefaultAction { + case "kick": + case "ban": + case "devoice": + default: + channel.Bayes.DefaultAction = "log" + } + + // channel.AllCaps.Enabled будет false, если не указан + if channel.AllCaps.MinLength < 10 { + channel.AllCaps.MinLength = 10 + } + + switch channel.AllCaps.DefaultAction { + case "kick": + case "ban": + case "devoice": + default: + channel.AllCaps.DefaultAction = "log" + } + } + + // Если список фраз с которыми стартует бот пустой, вносим в него 1 запись с пустой строкой + if len(sampleConfig.Jabber.StartupStatus) == 0 { + sampleConfig.Jabber.StartupStatus[0] = "" + } + + // Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой + if len(sampleConfig.Jabber.RuntimeStatus.Text) == 0 { + sampleConfig.Jabber.RuntimeStatus.Text[0] = "" + } + + // Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0 + // Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0 + + // Если sampleConfig.Jabber.BanDelay не задан, то он равен 0 + // Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false + + // Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой + if len(sampleConfig.Jabber.BanPhrases) == 0 { + sampleConfig.Jabber.BanPhrases[0] = "" + } + + if sampleConfig.CSign == "" { + sampleConfig.CSign = "!" + } + + if sampleConfig.Loglevel == "" { + sampleConfig.Loglevel = "info" + + log.Info("loglevel not defined in config, using info") + } + + // sampleConfig.Log = "" if not set + + config = sampleConfig + configLoaded = true + log.Infof("Using %s as config file", location) //nolint:wsl + + break + } + + if !configLoaded { + return errors.New("config was not loaded!") //nolint:goerr113 + } + + return err //nolint:wrapcheck +}