package configs import ( "context" "fmt" "os" "path/filepath" "runtime" "trbot/utils/consts" "trbot/utils/yaml" "unicode" "github.com/joho/godotenv" "github.com/rs/zerolog" ) func InitBot(ctx context.Context) error { var initFuncs = []func(ctx context.Context)error{ readConfig, readBotToken, readEnvironment, } godotenv.Load() for _, initfunc := range initFuncs { err := initfunc(ctx) if err != nil { return err } } return nil } // 从 yaml 文件读取配置文件 func readConfig(ctx context.Context) error { logger := zerolog.Ctx(ctx) // 先检查一下环境变量里有没有指定配置目录 configPathToFile := os.Getenv("CONFIG_PATH_TO_FILE") configDirectory := os.Getenv("CONFIG_DIRECTORY") if configPathToFile != "" { // 检查配置文件是否存在 if _, err := os.Stat(configPathToFile); err != nil { if os.IsNotExist(err) { // 如果配置文件不存在,就以默认配置的方式创建一份 logger.Warn(). Str("configPathToFile", configPathToFile). Msg("The config file does not exist. creating...") err = yaml.SaveYAML(configPathToFile, CreateDefaultConfig()) if err != nil { logger.Error(). Err(err). Msg("Create default config failed") return err } else { logger.Warn(). Str("configPathToFile", configPathToFile). Msg("The config file is created, please fill the bot token and restart") // 创建完成目录就跳到下方读取配置文件 // 默认配置文件没 bot token 的错误就留后面处理 } } else { // 读取配置文件时的其他错误 logger.Error(). Err(err). Str("configPathToFile", configPathToFile). Msg("Read config file failed") return err } } // 读取配置文件 ConfigPath = configPathToFile logger.Info(). Msg("Read config success from `CONFIG_PATH_TO_FILE` environment") return yaml.LoadYAML(configPathToFile, &BotConfig) } else if configDirectory != "" { // 检查目录是否存在 if _, err := os.Stat(configDirectory); err != nil { if os.IsNotExist(err) { // 目录不存在则创建 logger.Warn(). Str("configDirectory", configDirectory). Msg("Config directory does not exist, creating...") err = os.MkdirAll(configDirectory, 0755) if err != nil { logger.Error(). Err(err). Str("configDirectory", configDirectory). Msg("Create config directory failed") return err } // 如果不出错,到这里会跳到下方的读取配置文件部分 } else { // 读取目录时的其他错误 logger.Error(). Err(err). Str("configDirectory", configDirectory). Msg("Read config directory failed") return err } } // 使用默认的配置文件名,把目标配置文件路径补全 targetConfigPath := filepath.Join(configDirectory, "config.yaml") // 检查目录配置文件是否存在 if _, err := os.Stat(targetConfigPath); err != nil { if os.IsNotExist(err) { // 用户指定目录的话,还是不创建配置文件了,提示用户想要自定义配置文件名的话,需要设定另外一个环境变量 logger.Warn(). Str("configDirectory", configDirectory). Msg("No configuration file named `config.yaml` was found in this directory, If you want to set a specific config file name, set the `CONFIG_PATH_TO_FILE` environment variable") return err } else { // 读取目标配置文件路径时的其他错误 logger.Error(). Err(err). Str("targetConfigPath", targetConfigPath). Msg("Read target config file path failed") return err } } // 读取配置文件 ConfigPath = configPathToFile logger.Info(). Msg("Read config path success from `CONFIG_DIRECTORY` environment") return yaml.LoadYAML(targetConfigPath, &BotConfig) } else { // 没有指定任何环境变量,就读取默认的路径 if _, err := os.Stat(ConfigPath); err != nil { if os.IsNotExist(err) { // 如果配置文件不存在,就以默认配置的方式创建一份 logger.Warn(). Str("defaultConfigPath", ConfigPath). Msg("The default config file does not exist. creating...") err = yaml.SaveYAML(ConfigPath, CreateDefaultConfig()) if err != nil { logger.Error(). Err(err). Str("defaultConfigPath", ConfigPath). Msg("Create default config file failed") return err } else { logger.Warn(). Str("defaultConfigPath", ConfigPath). Msg("Default config file is created, please fill the bot token and restart.") // 创建完成目录就跳到下方读取配置文件 // 默认配置文件没 bot token 的错误就留后面处理 } } else { // 读取配置文件时的其他错误 logger.Error(). Err(err). Str("defaultConfigPath", ConfigPath). Msg("Read default config file failed") return err } } logger.Info(). Str("defaultConfigPath", ConfigPath). Msg("Read config file from default path") return yaml.LoadYAML(ConfigPath, &BotConfig) } } // 查找 bot token func readBotToken(ctx context.Context) error { logger := zerolog.Ctx(ctx) botToken := os.Getenv("BOT_TOKEN") if botToken != "" { BotConfig.BotToken = botToken logger.Info(). Str("botTokenID", showBotID()). Msg("Get token from environment") return nil } // 从 yaml 配置文件中读取 if BotConfig.BotToken != "" { logger.Info(). Str("botTokenID", showBotID()). Msg("Get token from config file") return nil } // 都不存在,提示错误 logger.Warn(). Msg("No bot token in environment, `.env` file and yaml config file, try create a bot from https://t.me/@botfather https://core.telegram.org/bots/tutorial#obtain-your-bot-token and fill it") return fmt.Errorf("no bot token") } func readEnvironment(ctx context.Context) error { logger := zerolog.Ctx(ctx) if os.Getenv("DEBUG") != "" { BotConfig.LogLevel = "debug" logger.Warn(). Msg("The DEBUG environment variable is set") } logLevel := os.Getenv("LOG_LEVEL") if logLevel != "" { BotConfig.LogLevel = logLevel logger.Warn(). Str("logLevel", logLevel). Msg("Get log level from environment") } logFileLevel := os.Getenv("LOG_FILE_LEVEL") if logFileLevel != "" { BotConfig.LogFileLevel = logFileLevel logger.Warn(). Str("logFileLevel", logFileLevel). Msg("Get log file level from environment") } FFmpegPath := os.Getenv("FFMPEG_PATH") if FFmpegPath != "" { BotConfig.FFmpegPath = FFmpegPath logger.Warn(). Str("FFmpegPath", FFmpegPath). Msg("Get FFmpegPath from environment") } return nil } func showBotID() string { var botID string for _, char := range BotConfig.BotToken { if unicode.IsDigit(char) { botID += string(char) } else { break // 遇到非数字字符停止 } } return botID } func IsUseMultiLogWriter(logger *zerolog.Logger) bool { file, err := os.OpenFile(consts.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err == nil { multLogger := zerolog.New(zerolog.MultiLevelWriter( zerolog.ConsoleWriter{Out: os.Stdout}, &zerolog.FilteredLevelWriter{ Writer: zerolog.MultiLevelWriter(file), Level: BotConfig.LevelForZeroLog(true), }, )).With().Timestamp().Logger() *logger = multLogger logger.Info(). Str("logFilePath", consts.LogFilePath). Str("logFileLevel", BotConfig.LogFileLevel). Msg("Use mult log writer") return true } else { logger.Error(). Err(err). Str("logFilePath", consts.LogFilePath). Msg("Failed to open log file, use console log writer only") return false } } func CheckConfig(ctx context.Context) { logger := zerolog.Ctx(ctx) // 部分必要但可以留空的配置 if BotConfig.LogLevel == "" { BotConfig.LogLevel = "info" logger.Warn().Msg("LogLevel is not set, use default value: info") } if BotConfig.LogFileLevel == "" { BotConfig.LogFileLevel = "warn" logger.Warn().Msg("LogFileLevel is not set, use default value: warn") } if BotConfig.InlineDefaultHandler == "" { logger.Info().Msg("Inline default handler is not set, default show all commands") } if BotConfig.InlineSubCommandSymbol == "" { BotConfig.InlineSubCommandSymbol = "+" logger.Info().Msg("Inline sub command symbol is not set, use default value: `+` (plus sign)") } if BotConfig.InlinePaginationSymbol == "" { BotConfig.InlinePaginationSymbol = "-" logger.Info().Msg("Inline pagination symbol is not set, use default value: `-` (minus sign)") } if BotConfig.InlineCategorySymbol == "" { BotConfig.InlineCategorySymbol = "=" logger.Info().Msg("Inline category symbol is not set, use default value: `=` (equal sign)") } if BotConfig.InlineResultsPerPage == 0 { BotConfig.InlineResultsPerPage = 50 logger.Info().Msg("Inline results per page number is not set, set it to 50") } else if BotConfig.InlineResultsPerPage < 1 || BotConfig.InlineResultsPerPage > 50 { logger.Warn(). Int("invalidNumber", BotConfig.InlineResultsPerPage). Msg("Inline results per page number is invalid, set it to 50") BotConfig.InlineResultsPerPage = 50 } // 以下为可有可无的配置,主要是提醒下用户 if len(BotConfig.AdminIDs) != 0 { logger.Info(). Ints64("AdminIDs", BotConfig.AdminIDs). Msg("Admin list is set") } if len(BotConfig.AllowedUpdates) != 0 { logger.Info(). Strs("allowedUpdates", BotConfig.AllowedUpdates). Msg("Allowed updates list is set") } if BotConfig.LogChatID != 0 { logger.Info(). Int64("LogChatID", BotConfig.LogChatID). Msg("Enabled log to chat") } if BotConfig.FFmpegPath != "" { logger.Info(). Str("FFmpegPath", BotConfig.FFmpegPath). Msg("FFmpeg path is set") } logger.Info(). Str("DefaultHandler", BotConfig.InlineDefaultHandler). Str("SubCommandSymbol", BotConfig.InlineSubCommandSymbol). Str("CategorySymbol", BotConfig.InlineCategorySymbol). Str("PaginationSymbol", BotConfig.InlinePaginationSymbol). Int("ResultsPerPage", BotConfig.InlineResultsPerPage). Msg("Inline mode config has been read") } func ShowConst(ctx context.Context) { logger := zerolog.Ctx(ctx) if consts.BuildAt == "" { logger.Warn(). Str("runtime", runtime.Version()). Str("logLevel", BotConfig.LogLevel). Str("error", "Remind: You are using a version without build info"). Msg("trbot") } else { logger.Info(). Str("commit", consts.Commit). Str("branch", consts.Branch). Str("version", consts.Version). Str("buildAt", consts.BuildAt). Str("buildOn", consts.BuildOn). Str("changes", consts.Changes). Str("runtime", runtime.Version()). Str("logLevel", BotConfig.LogLevel). Msg("trbot") } }