Files
trbot/utils/configs/init.go
Hubert Chen 517ea2d5a8 add chat member change log
update.Message:
    NewChatMembers
    LeftChatMember
update.ChatMember
2025-07-29 21:38:37 +08:00

369 lines
10 KiB
Go

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")
}
}