From 5f13c12de1f6168340ed74a62d704c05684b7dc6 Mon Sep 17 00:00:00 2001 From: SanaeFox <36219542+Hoshinonyaruko@users.noreply.github.com> Date: Fri, 10 May 2024 11:04:13 +0800 Subject: [PATCH] Beta401 (#401) * beta399 * beta400 * beta401 --- Processor/ProcessGroupMsgReceive.go | 6 +- Processor/ProcessGroupMsgReject.go | 6 +- config/config.go | 222 ++++++++++++++++++++-------- go.mod | 3 +- go.sum | 2 + main.go | 45 +++++- 6 files changed, 214 insertions(+), 70 deletions(-) diff --git a/Processor/ProcessGroupMsgReceive.go b/Processor/ProcessGroupMsgReceive.go index b1c6425c..3df37683 100644 --- a/Processor/ProcessGroupMsgReceive.go +++ b/Processor/ProcessGroupMsgReceive.go @@ -65,6 +65,8 @@ func (p *Processors) ProcessGroupMsgRecive(data *dto.GroupMsgReceiveEvent) error mylog.Printf("Error storing ID: %v", err) return nil } + // 修复不开启idmap-pro问题 + LongGroupID64 = GroupID64 } var selfid64 int64 if config.GetUseUin() { @@ -76,7 +78,7 @@ func (p *Processors) ProcessGroupMsgRecive(data *dto.GroupMsgReceiveEvent) error if !config.GetGlobalGroupMsgRejectReciveEventToMessage() { notice := &OnebotGroupReceiveNotice{ GroupID: GroupID64, - NoticeType: "interaction", + NoticeType: "group_receive", PostType: "notice", SelfID: selfid64, SubType: "create", @@ -150,7 +152,7 @@ func (p *Processors) ProcessGroupMsgRecive(data *dto.GroupMsgReceiveEvent) error } //增强配置 if !config.GetNativeOb11() { - groupMsg.RealMessageType = "interaction" + groupMsg.RealMessageType = "group_receive" groupMsg.IsBindedUserId = IsBindedUserId groupMsg.IsBindedGroupId = IsBindedGroupId groupMsg.RealGroupID = data.GroupOpenID diff --git a/Processor/ProcessGroupMsgReject.go b/Processor/ProcessGroupMsgReject.go index e222152c..ae53d668 100644 --- a/Processor/ProcessGroupMsgReject.go +++ b/Processor/ProcessGroupMsgReject.go @@ -66,6 +66,8 @@ func (p *Processors) ProcessGroupMsgReject(data *dto.GroupMsgRejectEvent) error mylog.Printf("Error storing ID: %v", err) return nil } + // 修复不开启idmap-pro问题 + LongGroupID64 = GroupID64 } var selfid64 int64 if config.GetUseUin() { @@ -77,7 +79,7 @@ func (p *Processors) ProcessGroupMsgReject(data *dto.GroupMsgRejectEvent) error if !config.GetGlobalGroupMsgRejectReciveEventToMessage() { notice := &OnebotGroupRejectNotice{ GroupID: GroupID64, - NoticeType: "interaction", + NoticeType: "group_reject", PostType: "notice", SelfID: selfid64, SubType: "create", @@ -151,7 +153,7 @@ func (p *Processors) ProcessGroupMsgReject(data *dto.GroupMsgRejectEvent) error } //增强配置 if !config.GetNativeOb11() { - groupMsg.RealMessageType = "interaction" + groupMsg.RealMessageType = "group_reject" groupMsg.IsBindedUserId = IsBindedUserId groupMsg.IsBindedGroupId = IsBindedGroupId groupMsg.RealGroupID = data.GroupOpenID diff --git a/config/config.go b/config/config.go index 05488837..e3bc138f 100644 --- a/config/config.go +++ b/config/config.go @@ -35,47 +35,141 @@ type CommentBlock struct { Offset int // 注释与目标键之间的行数 } +// 不支持配置热重载的配置项 +var restartRequiredFields = []string{ + "WsAddress", "WsToken", "ReconnectTimes", "HeartBeatInterval", "LaunchReconnectTimes", + "AppID", "Uin", "Token", "ClientSecret", "ShardCount", "ShardID", "UseUin", + "TextIntent", + "ServerDir", "Port", "BackupPort", "Lotus", "LotusPassword", "LotusWithoutIdmaps", + "WsServerPath", "EnableWsServer", "WsServerToken", + "IdentifyFile", "IdentifyAppids", "Crt", "Key", + "DeveloperLog", "LogLevel", "SaveLogs", + "DisableWebui", "Username", "Password", + "Title", // 继续检查和增加 +} + // LoadConfig 从文件中加载配置并初始化单例配置 -func LoadConfig(path string) (*Config, error) { +func LoadConfig(path string, fastload bool) (*Config, error) { mu.Lock() defer mu.Unlock() - // 如果单例已经被初始化了,直接返回 - if instance != nil { - return instance, nil - } - configData, err := os.ReadFile(path) if err != nil { return nil, err } - //todo remove it 破坏性变更的擦屁股代码 - var ischange bool - configData, ischange = replaceVisualPrefixsLine(configData) - if ischange { - err = os.WriteFile(path, configData, 0644) - if err != nil { - // 处理写入错误 - return nil, err - } - } - //mylog.Printf("dev_ischange:%v", ischange) + + // 检查并替换视觉前缀行,如果有必要,后期会注释 + // var isChange bool + // configData, isChange = replaceVisualPrefixsLine(configData) + // if isChange { + // // 如果配置文件已修改,重新写入修正后的数据 + // if err = os.WriteFile(path, configData, 0644); err != nil { + // return nil, err // 处理写入错误 + // } + // } + + // 尝试解析配置数据 conf := &Config{} - err = yaml.Unmarshal(configData, conf) - if err != nil { + if err = yaml.Unmarshal(configData, conf); err != nil { return nil, err } - // 确保配置完整性 - if err := ensureConfigComplete(path); err != nil { - return nil, err + if !fastload { + // 确保本地配置文件的完整性,添加新的字段 + if err = ensureConfigComplete(path); err != nil { + return nil, err + } + } else { + if isValidConfig(conf) { + //log.Printf("instance.Settings:%v", instance.Settings) + // 用现有的instance比对即将覆盖赋值的conf,用[]string返回配置发生了变化的配置项 + changedFields := compareConfigChanges("Settings", instance.Settings, conf.Settings) + // 根据changedFields进行进一步的操作,在不支持热重载的字段实现自动重启 + if len(changedFields) > 0 { + log.Printf("配置已变更的字段:%v", changedFields) + checkForRestart(changedFields) // 检查变更字段是否需要重启 + } + } //conf为空时不对比 + } + + // 更新单例实例,即使它已经存在 更新前检查是否有效,vscode对文件的更新行为会触发2次文件变动 + // 第一次会让configData为空,迅速的第二次才是正常有值的configData + if isValidConfig(conf) { + instance = conf } - // 设置单例实例 - instance = conf return instance, nil } +func isValidConfig(conf *Config) bool { + // 确认config不为空且必要字段已设置 + return conf != nil && conf.Version != 0 +} + +// 去除Settings前缀 +func stripSettingsPrefix(fieldName string) string { + return strings.TrimPrefix(fieldName, "Settings.") +} + +// compareConfigChanges 检查并返回发生变化的配置字段,处理嵌套结构体 +func compareConfigChanges(prefix string, oldConfig interface{}, newConfig interface{}) []string { + var changedFields []string + + oldVal := reflect.ValueOf(oldConfig) + newVal := reflect.ValueOf(newConfig) + + // 解引用指针 + if oldVal.Kind() == reflect.Ptr { + oldVal = oldVal.Elem() + } + if newVal.Kind() == reflect.Ptr { + newVal = newVal.Elem() + } + + // 遍历所有字段 + for i := 0; i < oldVal.NumField(); i++ { + oldField := oldVal.Field(i) + newField := newVal.Field(i) + fieldType := oldVal.Type().Field(i) + fieldName := fieldType.Name + + fullFieldName := fieldName + if prefix != "" { + fullFieldName = fmt.Sprintf("%s.%s", prefix, fieldName) + } + + // 对于结构体字段递归比较 + if oldField.Kind() == reflect.Struct || newField.Kind() == reflect.Struct { + subChanges := compareConfigChanges(fullFieldName, oldField.Interface(), newField.Interface()) + changedFields = append(changedFields, subChanges...) + } else { + // 打印将要比较的字段和它们的值 + //fmt.Printf("Comparing field: %s\nOld value: %v\nNew value: %v\n", fullFieldName, oldField.Interface(), newField.Interface()) + if !reflect.DeepEqual(oldField.Interface(), newField.Interface()) { + //fmt.Println("-> Field changed") + // 去除Settings前缀后添加到变更字段列表 + changedField := stripSettingsPrefix(fullFieldName) + changedFields = append(changedFields, changedField) + } + } + } + + return changedFields +} + +// 检查是否需要重启 +func checkForRestart(changedFields []string) { + for _, field := range changedFields { + for _, restartField := range restartRequiredFields { + if field == restartField { + fmt.Println("Configuration change requires restart:", field) + sys.RestartApplication() // 调用重启函数 + return + } + } + } +} + func CreateAndWriteConfigTemp() error { // 读取config.yml configFile, err := os.ReadFile("config.yml") @@ -1426,46 +1520,46 @@ func GetQrSize() int { return instance.Settings.QrSize } -func replaceVisualPrefixsLine(configData []byte) ([]byte, bool) { - // 定义新的 visual_prefixs 部分 - newVisualPrefixs := ` visual_prefixs : #虚拟前缀 与white_prefixs配合使用 处理流程自动忽略该前缀 remove_prefix remove_at 需为true时生效 - - prefix: "" #虚拟前缀开头 例 你有3个指令 帮助 测试 查询 将 prefix 设置为 工具类 后 则可通过 工具类 帮助 触发机器人 - whiteList: [""] #开关状态取决于 white_prefix_mode 为每一个二级指令头设计独立的白名单 - No_White_Response : "" - - prefix: "" - whiteList: [""] - No_White_Response : "" - - prefix: "" - whiteList: [""] - No_White_Response : "" ` - - // 将 byte 数组转换为字符串 - configStr := string(configData) - - // 按行分割 configStr - lines := strings.Split(configStr, "\n") - - // 创建一个新的字符串构建器 - var newConfigData strings.Builder - - // 标记是否进行了替换 - replaced := false - - // 遍历所有行 - for _, line := range lines { - // 检查是否是 visual_prefixs 开头的行 - if strings.HasPrefix(strings.TrimSpace(line), "visual_prefixs : [") { - // 替换为新的 visual_prefixs 部分 - newConfigData.WriteString(newVisualPrefixs + "\n") - replaced = true - continue // 跳过原有行 - } - newConfigData.WriteString(line + "\n") - } - - // 返回新配置和是否发生了替换的标记 - return []byte(newConfigData.String()), replaced -} +// func replaceVisualPrefixsLine(configData []byte) ([]byte, bool) { +// // 定义新的 visual_prefixs 部分 +// newVisualPrefixs := ` visual_prefixs : #虚拟前缀 与white_prefixs配合使用 处理流程自动忽略该前缀 remove_prefix remove_at 需为true时生效 +// - prefix: "" #虚拟前缀开头 例 你有3个指令 帮助 测试 查询 将 prefix 设置为 工具类 后 则可通过 工具类 帮助 触发机器人 +// whiteList: [""] #开关状态取决于 white_prefix_mode 为每一个二级指令头设计独立的白名单 +// No_White_Response : "" +// - prefix: "" +// whiteList: [""] +// No_White_Response : "" +// - prefix: "" +// whiteList: [""] +// No_White_Response : "" ` + +// // 将 byte 数组转换为字符串 +// configStr := string(configData) + +// // 按行分割 configStr +// lines := strings.Split(configStr, "\n") + +// // 创建一个新的字符串构建器 +// var newConfigData strings.Builder + +// // 标记是否进行了替换 +// replaced := false + +// // 遍历所有行 +// for _, line := range lines { +// // 检查是否是 visual_prefixs 开头的行 +// if strings.HasPrefix(strings.TrimSpace(line), "visual_prefixs : [") { +// // 替换为新的 visual_prefixs 部分 +// newConfigData.WriteString(newVisualPrefixs + "\n") +// replaced = true +// continue // 跳过原有行 +// } +// newConfigData.WriteString(line + "\n") +// } + +// // 返回新配置和是否发生了替换的标记 +// return []byte(newConfigData.String()), replaced +// } // 获取GetWhiteBypassRevers的值 func GetWhiteBypassRevers() bool { diff --git a/go.mod b/go.mod index c41d3ff0..df8ad2a7 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/aliyun/aliyun-oss-go-sdk v3.0.1+incompatible github.com/baidubce/bce-sdk-go v0.9.161 github.com/fatih/color v1.15.0 + github.com/fsnotify/fsnotify v1.7.0 github.com/gin-gonic/gin v1.9.1 github.com/google/uuid v1.4.0 github.com/gorilla/websocket v1.4.2 @@ -69,6 +70,6 @@ require ( golang.org/x/net v0.10.0 golang.org/x/sys v0.13.0 golang.org/x/text v0.9.0 // indirect - google.golang.org/protobuf v1.32.0 + google.golang.org/protobuf v1.32.0 // indirect mvdan.cc/xurls v1.1.0 ) diff --git a/go.sum b/go.sum index 401b1c22..7ba6f44c 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBD github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= diff --git a/main.go b/main.go index 85636ad7..2e6d82c5 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "time" "github.com/fatih/color" + "github.com/fsnotify/fsnotify" "github.com/hoshinonyaruko/gensokyo/Processor" "github.com/hoshinonyaruko/gensokyo/acnode" "github.com/hoshinonyaruko/gensokyo/botstats" @@ -93,10 +94,14 @@ func main() { // 主逻辑 // 加载配置 - conf, err := config.LoadConfig("config.yml") + conf, err := config.LoadConfig("config.yml", false) if err != nil { log.Fatalf("error: %v", err) } + + // 配置热重载 + go setupConfigWatcher("config.yml") + sys.SetTitle(conf.Settings.Title) webuiURL := config.ComposeWebUIURL(conf.Settings.Lotus) // 调用函数获取URL webuiURLv2 := config.ComposeWebUIURLv2(conf.Settings.Lotus) // 调用函数获取URL @@ -716,3 +721,41 @@ func allEmpty(addresses []string) bool { } return true } + +func setupConfigWatcher(configFilePath string) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatalf("Error setting up watcher: %v", err) + } + + // 添加一个100毫秒的Debouncing + //fileLoader := &config.ConfigFileLoader{EventDelay: 100 * time.Millisecond} + + // Start the goroutine to handle file system events. + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return // Exit if channel is closed. + } + if event.Op&fsnotify.Write == fsnotify.Write { + fmt.Println("检测到配置文件变动:", event.Name) + //fileLoader.LoadConfigF(configFilePath) + config.LoadConfig(configFilePath, true) + } + case err, ok := <-watcher.Errors: + if !ok { + return // Exit if channel is closed. + } + log.Println("Watcher error:", err) + } + } + }() + + // Add the config file to the list of watched files. + err = watcher.Add(configFilePath) + if err != nil { + log.Fatalf("Error adding watcher: %v", err) + } +}