package plugins import ( "context" "fmt" "os" "path/filepath" "strconv" "strings" "time" "trbot/utils" "trbot/utils/configs" "trbot/utils/flaterr" "trbot/utils/handler_params" "trbot/utils/plugin_utils" "trbot/utils/type/contain" "trbot/utils/yaml" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" "github.com/rs/zerolog" ) var KeywordDataList = KeywordData{ Chats: map[int64]KeywordChatList{}, Users: map[int64]KeywordUserList{}, } var KeywordDataErr error var KeywordDataPath string = filepath.Join(configs.YAMLDatabaseDir, "detectkeyword/", configs.YAMLFileName) func init() { plugin_utils.AddInitializer(plugin_utils.Initializer{ Name: "Detect Keyword", Func: func(ctx context.Context, thebot *bot.Bot) error{ err := readKeywordList(ctx) if err != nil { return err } else { plugin_utils.AddHandlerHelpInfo(plugin_utils.HandlerHelp{ Name: "群组关键词检测", Description: "此功能可以检测群组中的每一条信息,当包含设定的关键词时,将会向用户发送提醒\n\n使用方法:\n首先将机器人添加至想要监听关键词的群组中,发送 /setkeyword 命令,等待机器人回应后点击下方的 “设定关键词” 按钮即可为自己添加要监听的群组\n\n设定关键词:\n您可以在对应的群组中直接发送 /setkeyword 要设定的关键词 来为该群组设定关键词\n或前往机器人聊天页面,发送 /setkeyword 命令后点击对应的群组或全局关键词按钮,根据提示来添加关键词", ParseMode: models.ParseModeHTML, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ {{ Text: "将此机器人添加到群组或频道", URL: fmt.Sprintf("https://t.me/%s?startgroup=detectkw", configs.BotMe.Username), }}, { { Text: "返回", CallbackData: "help", }, { Text: "关闭", CallbackData: "delete_this_message", }, }, }}, }) } return nil }, }) plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ Name: "Detect Keyword", Loader: readKeywordList, Saver: saveKeywordList, }) plugin_utils.AddSlashCommandHandlers(plugin_utils.SlashCommand{ SlashCommand: "setkeyword", ForChatType: []models.ChatType{ models.ChatTypePrivate, models.ChatTypeGroup, models.ChatTypeSupergroup, models.ChatTypeChannel, }, MessageHandler: addKeywordHandler, }) plugin_utils.AddCallbackQueryHandlers([]plugin_utils.CallbackQuery{ { CallbackDataPrefix: "detectkw_g", CallbackQueryHandler: groupManageCallbackHandler, }, { CallbackDataPrefix: "detectkw_u", CallbackQueryHandler: userManageCallbackHandler, }, }...) plugin_utils.AddSlashStartPrefixCommandHandlers(plugin_utils.SlashStartPrefixHandler{ Name: "detect keyword add chat to user list", PrefixArgument: "detectkw-addgroup", ForChatType: []models.ChatType{models.ChatTypePrivate}, MessageHandler: startPrefixAddGroup, }) plugin_utils.AddSlashStartCommandHandlers(plugin_utils.SlashStartHandler{ Name: "detect keyword add bot to chat", Argument: "detectkw", ForChatType: []models.ChatType{models.ChatTypeGroup, models.ChatTypeSupergroup}, MessageHandler: startBotAddedToGroup, }) } type KeywordData struct { Chats map[int64]KeywordChatList `yaml:"Chats"` Users map[int64]KeywordUserList `yaml:"Users"` } type KeywordChatList struct { ChatID int64 `yaml:"ChatID"` ChatName string `yaml:"ChatName"` ChatUsername string `yaml:"ChatUsername,omitempty"` ChatType models.ChatType `yaml:"ChatType"` AddTime string `yaml:"AddTime"` InitByID int64 `yaml:"InitByID"` IsDisable bool `yaml:"IsDisable,omitempty"` // 根据用户数量决定是否启用 UsersID []int64 `yaml:"UsersID"` } type KeywordUserList struct { UserID int64 `yaml:"UserID"` AddTime string `yaml:"AddTime"` Limit int `yaml:"Limit"` // todo MentionCount int `yaml:"MentionCount,omitempty"` IsNotInit bool `yaml:"IsNotInit,omitempty"` IsDisable bool `yaml:"IsDisable,omitempty"` IsSilentNotice bool `yaml:"IsSilentNotice,omitempty"` IsIncludeSelf bool `yaml:"IsIncludeSelf,omitempty"` IsIncludeBot bool `yaml:"IsIncludeBot,omitempty"` // todo AddingChatID int64 `yaml:"AddingChatID,omitempty"` GlobalKeyword []string `yaml:"GlobalKeyword"` WatchUser []int64 `yaml:"WatchUser,omitempty"` // todo ChatsForUser []ChatForUser `yaml:"ChatForUser"` } func (user KeywordUserList)enabledChatCount() (count int) { for _, v := range user.ChatsForUser { if !v.IsDisable { count++ } } return } func (user KeywordUserList)keywordCount() (count int) { for _, v := range user.ChatsForUser { count += len(v.Keyword) } return } func (user KeywordUserList)userStatus() string { var pendingMessage string if user.IsDisable { pendingMessage = "您已经全局禁用了此功能" } else { pendingMessage = fmt.Sprintf("您添加的群组中有 (%d/%d) 个处于启用状态\n您总共设定了 %d 个关键词", user.enabledChatCount(), len(user.ChatsForUser), user.keywordCount() + len(user.GlobalKeyword), ) } return pendingMessage } func (user KeywordUserList)selectChat(keyword string) models.ReplyMarkup { var buttons [][]models.InlineKeyboardButton buttons = append(buttons, []models.InlineKeyboardButton{{ Text: "🌐 添加为全局关键词", CallbackData: fmt.Sprintf("detectkw_u_add_%d_%s", user.UserID, keyword), }}) for _, chat := range user.ChatsForUser { targetChat := KeywordDataList.Chats[chat.ChatID] buttons = append(buttons, []models.InlineKeyboardButton{{ Text: "👥 " + targetChat.ChatName, CallbackData: fmt.Sprintf("detectkw_u_add_%d_%s", targetChat.ChatID, keyword), }}) } return &models.InlineKeyboardMarkup{ InlineKeyboard: buttons, } } func (user KeywordUserList)buildUserChatList() models.ReplyMarkup { var buttons [][]models.InlineKeyboardButton if !user.IsDisable { buttons = append(buttons, []models.InlineKeyboardButton{{ Text: fmt.Sprintf("全局关键词 %d 个", len(user.GlobalKeyword)), CallbackData: fmt.Sprintf("detectkw_u_chat_%d", user.UserID), }}) for _, chat := range user.ChatsForUser { var subchats []models.InlineKeyboardButton var targetChat = KeywordDataList.Chats[chat.ChatID] subchats = append(subchats, models.InlineKeyboardButton{ Text: fmt.Sprintf("(%d) %s", len(chat.Keyword), targetChat.ChatName), CallbackData: fmt.Sprintf("detectkw_u_chat_%d", targetChat.ChatID), }) if targetChat.IsDisable { subchats = append(subchats, models.InlineKeyboardButton{ Text: "🚫 查看帮助", CallbackData: "detectkw_u_chatdisablebyadmin", }) } else { subchats = append(subchats, models.InlineKeyboardButton{ Text: "🔄 " + utils.TextForTrueOrFalse(chat.IsDisable, "当前已禁用 ❌", "当前已启用 ✅"), CallbackData: fmt.Sprintf("detectkw_u_switch_chat_%d", targetChat.ChatID), }) } buttons = append(buttons, subchats) } buttons = append(buttons, []models.InlineKeyboardButton{{ Text: "🔄 通知偏好:" + utils.TextForTrueOrFalse(user.IsSilentNotice, "🔇 无声通知", "🔉 有声通知"), CallbackData: "detectkw_u_noticeswitch", }}) buttons = append(buttons, []models.InlineKeyboardButton{{ Text: "🔄 检测偏好:" + utils.TextForTrueOrFalse(user.IsIncludeSelf, "不排除自己的消息", "排除自己的消息"), CallbackData: "detectkw_u_selfswitch", }}) } buttons = append(buttons, []models.InlineKeyboardButton{{ Text: "🔄 全局状态:" + utils.TextForTrueOrFalse(user.IsDisable, "已禁用 ❌", "已启用 ✅"), CallbackData: "detectkw_u_globalswitch", }}) return &models.InlineKeyboardMarkup{ InlineKeyboard: buttons, } } func (user KeywordUserList)globalKeywordButtons() [][]models.InlineKeyboardButton { var buttons [][]models.InlineKeyboardButton var tempbutton []models.InlineKeyboardButton for index, keyword := range user.GlobalKeyword { if index % 2 == 0 && index != 0 { buttons = append(buttons, tempbutton) tempbutton = []models.InlineKeyboardButton{} } tempbutton = append(tempbutton, models.InlineKeyboardButton{ Text: keyword, CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", user.UserID, keyword), }) } if len(tempbutton) != 0 { buttons = append(buttons, tempbutton) } return buttons } type ChatForUser struct { ChatID int64 `yaml:"ChatID"` IsDisable bool `yaml:"IsDisable,omitempty"` IsConfirmDelete bool `yaml:"IsConfirmDelete,omitempty"` // todo Keyword []string `yaml:"Keyword"` } func (chat ChatForUser)keywordButtons() [][]models.InlineKeyboardButton { var buttons [][]models.InlineKeyboardButton var tempbutton []models.InlineKeyboardButton for index, keyword := range chat.Keyword { if index % 2 == 0 && index != 0 { buttons = append(buttons, tempbutton) tempbutton = []models.InlineKeyboardButton{} } tempbutton = append(tempbutton, models.InlineKeyboardButton{ Text: keyword, CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", chat.ChatID, keyword), }) } if len(tempbutton) != 0 { buttons = append(buttons, tempbutton) } return buttons } func readKeywordList(ctx context.Context) error { logger := zerolog.Ctx(ctx). With(). Str("pluginName", "DetectKeyword"). Str(utils.GetCurrentFuncName()). Logger() err := yaml.LoadYAML(KeywordDataPath, &KeywordDataList) if err != nil { if os.IsNotExist(err) { logger.Warn(). Err(err). Str("path", KeywordDataPath). Msg("Not found keyword list file. Created new one") // 如果是找不到文件,新建一个 err = yaml.SaveYAML(KeywordDataPath, &KeywordDataList) if err != nil { logger.Error(). Err(err). Str("path", KeywordDataPath). Msg("Failed to create empty keyword list file") KeywordDataErr = fmt.Errorf("failed to create empty keyword list file: %w", err) } } else { logger.Error(). Err(err). Str("path", KeywordDataPath). Msg("Failed to load keyword list file") KeywordDataErr = fmt.Errorf("failed to load keyword list file: %w", err) } } else { KeywordDataErr = nil } buildListenList() return KeywordDataErr } func saveKeywordList(ctx context.Context) error { logger := zerolog.Ctx(ctx). With(). Str("pluginName", "DetectKeyword"). Str(utils.GetCurrentFuncName()). Logger() err := yaml.SaveYAML(KeywordDataPath, &KeywordDataList) if err != nil { logger.Error(). Err(err). Str("path", KeywordDataPath). Msg("Failed to save udonese list") KeywordDataErr = fmt.Errorf("failed to save udonese list: %w", err) } else { KeywordDataErr = nil } return KeywordDataErr } func addKeywordHandler(opts *handler_params.Message) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). Str(utils.GetCurrentFuncName()). Dict(utils.GetChatDict(&opts.Message.Chat)). Dict(utils.GetUserDict(opts.Message.From)). Logger() var handlerErr flaterr.MultErr if opts.Message.Chat.Type == models.ChatTypePrivate { // 与机器人的私聊对话 user := KeywordDataList.Users[opts.Message.From.ID] if user.AddTime == "" { // 初始化用户 user = KeywordUserList{ UserID: opts.Message.From.ID, AddTime: time.Now().Format(time.RFC3339), Limit: 50, } KeywordDataList.Users[opts.Message.From.ID] = user err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Msg("Failed to init user and save keyword list") return handlerErr.Addf("failed to init user and save keyword list: %w", err).Flat() } } // 用户没有添加任何群组 if len(user.ChatsForUser) == 0 { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "您还没有添加任何群组,请在群组中使用 /setkeyword 命令来记录群组\n若发送信息后没有回应,请检查机器人是否在对应群组中", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, }) if err != nil { logger.Error(). Err(err). Str("content", "no group for user notice"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "no group for user notice", err) } } else { if len(opts.Fields) == 1 { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: user.userStatus(), ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ParseMode: models.ParseModeHTML, ReplyMarkup: user.buildUserChatList(), }) if err != nil { logger.Error(). Err(err). Str("content", "user group list keyboard"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "user group list keyboard", err) } } else { // 限制关键词长度 if len(opts.Fields[1]) > 30 { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "抱歉,单个关键词长度不能超过 30 个英文字符", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, }) if err != nil { logger.Error(). Err(err). Int("length", len(opts.Fields[1])). Str("content", "keyword is too long"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "keyword is too long", err) } } else { if user.AddingChatID != 0 { keyword := strings.ToLower(opts.Fields[1]) var pendingMessage string var button models.ReplyMarkup var isKeywordExist bool // 判断是全局关键词还是群组关键词 if user.AddingChatID == user.UserID { // 全局关键词 for _, k := range user.GlobalKeyword { if k == keyword { isKeywordExist = true break } } if !isKeywordExist { logger.Debug(). Str("globalKeyword", keyword). Msg("User add a global keyword") user.GlobalKeyword = append(user.GlobalKeyword, keyword) KeywordDataList.Users[user.UserID] = user err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Str("globalKeyword", keyword). Msg("Failed to add global keyword and save keyword list") return handlerErr.Addf("failed to add global keyword and save keyword list: %w", err).Flat() } pendingMessage = fmt.Sprintf("已添加全局关键词 [ %s ], 您可以继续添加更多全局关键词", keyword) } else { pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在,您可以继续添加其他全局关键词", keyword) } } else { var chatForUser ChatForUser var chatForUserIndex int for i, c := range user.ChatsForUser { if c.ChatID == user.AddingChatID { chatForUser = c chatForUserIndex = i } } targetChat := KeywordDataList.Chats[chatForUser.ChatID] // 群组关键词 for _, k := range chatForUser.Keyword { if k == keyword { isKeywordExist = true break } } if !isKeywordExist { logger.Debug(). Str("keyword", keyword). Msg("User add a keyword to chat") chatForUser.Keyword = append(chatForUser.Keyword, keyword) user.ChatsForUser[chatForUserIndex] = chatForUser KeywordDataList.Users[user.UserID] = user err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Str("keyword", keyword). Msg("Failed to add keyword and save keyword list") return handlerErr.Addf("failed to add keyword and save keyword list: %w", err).Flat() } pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ],您可以继续向此群组添加更多关键词\n", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, keyword) } else { pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", keyword, utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) } } if isKeywordExist { button = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "✅ 完成", CallbackData: "detectkw_u_finish", }}}} } else { button = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{ { Text: "↩️ 撤销操作", CallbackData: fmt.Sprintf("detectkw_u_undo_%d_%s", user.AddingChatID, opts.Fields[1]), }, { Text: "✅ 完成", CallbackData: "detectkw_u_finish", }, }}} } _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: pendingMessage, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ParseMode: models.ParseModeHTML, ReplyMarkup: button, }) if err != nil { logger.Error(). Err(err). Str("content", "keyword added notice"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "keyword added notice", err) } } else { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "您还没有选定要将关键词添加到哪个群组,请在下方挑选一个您已经添加的群组", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ParseMode: models.ParseModeHTML, ReplyMarkup: user.selectChat(opts.Fields[1]), }) if err != nil { logger.Error(). Err(err). Str("content", "keyword adding select keyboard"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "keyword adding select keyboard", err) } } } } } } else { // 在群组中直接使用 /setkeyword 命令 chat := KeywordDataList.Chats[opts.Message.Chat.ID] if chat.IsDisable { // 此功能已被管理员手动禁用 _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "群组管理员已禁用关键词功能,您可以询问管理员以获取更多信息", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ParseMode: models.ParseModeHTML, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "管理此功能(管理员)", CallbackData: "detectkw_g", }}}}, }) if err != nil { logger.Error(). Err(err). Str("content", "disabled by admins"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "disabled by admins", err) } } else { if chat.AddTime == "" { var initByID int64 if opts.Message.From != nil { initByID = opts.Message.From.ID } else if opts.Message.SenderChat != nil { initByID = opts.Message.SenderChat.ID } // 初始化群组 chat = KeywordChatList{ ChatID: opts.Message.Chat.ID, ChatName: opts.Message.Chat.Title, ChatUsername: opts.Message.Chat.Username, ChatType: opts.Message.Chat.Type, AddTime: time.Now().Format(time.RFC3339), InitByID: initByID, } KeywordDataList.Chats[opts.Message.Chat.ID] = chat err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Dict(utils.GetChatDict(&opts.Message.Chat)). Msg("Failed to init chat and save keyword list") return handlerErr.Addf("failed to init chat and save keyword list: %w", err).Flat() } } if len(opts.Fields) == 1 { // 只有一个 /setkeyword 命令 _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "已记录群组,点击下方左侧按钮来设定监听关键词\n若您是群组的管理员,您可以点击右侧的按钮来管理此功能", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{ { Text: "设定关键词", URL: fmt.Sprintf("https://t.me/%s?start=detectkw-addgroup_%d", configs.BotMe.Username, opts.Message.Chat.ID), }, { Text: "管理此功能", CallbackData: "detectkw_g", }, }}}, }) if err != nil { logger.Error(). Err(err). Str("content", "group record link button"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "group record link button", err) } } else if opts.Message.Chat.Type != models.ChatTypeChannel { // 限制关键词长度 if len(opts.Fields[1]) > 30 { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "抱歉,单个关键词长度不能超过 30 个英文字符", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, }) if err != nil { logger.Error(). Err(err). Int("keywordLength", len(opts.Fields[1])). Str("content", "keyword is too long"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "keyword is too long", err) } } else { user := KeywordDataList.Users[opts.Message.From.ID] if user.AddTime == "" { // 初始化用户 user = KeywordUserList{ UserID: opts.Message.From.ID, AddTime: time.Now().Format(time.RFC3339), Limit: 50, IsNotInit: true, } KeywordDataList.Users[opts.Message.From.ID] = user err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Msg("Failed to add a not init user and save keyword list") return handlerErr.Addf("failed to add a not init user and save keyword list: %w", err).Flat() } } var chatForUser ChatForUser var chatForUserIndex int var isChatAdded bool for index, keyword := range user.ChatsForUser { if keyword.ChatID == chat.ChatID { chatForUser = keyword chatForUserIndex = index isChatAdded = true break } } if !isChatAdded { logger.Debug().Msg("User add a chat to listen list by set keyword in group") chatForUser = ChatForUser{ ChatID: chat.ChatID, } user.ChatsForUser = append(user.ChatsForUser, chatForUser) KeywordDataList.Users[user.UserID] = user err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Msg("Failed to add chat to user listen list and save keyword list") return handlerErr.Addf("failed to add chat to user listen list and save keyword list: %w", err).Flat() } } keyword := strings.ToLower(opts.Fields[1]) var pendingMessage string var isKeywordExist bool for _, k := range chatForUser.Keyword { if k == keyword { isKeywordExist = true break } } if isKeywordExist { if user.IsNotInit { pendingMessage = "您已经添加过这个关键词了。请点击下方的按钮来初始化您的账号以使用此功能" } else { pendingMessage = "您已经添加过这个关键词了,您可以继续向此群组添加其他关键词" } } else { logger.Debug(). Str("keyword", keyword). Msg("User add a keyword to chat") chatForUser.Keyword = append(chatForUser.Keyword, keyword) user.ChatsForUser[chatForUserIndex] = chatForUser KeywordDataList.Users[user.UserID] = user err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Str("keyword", keyword). Msg("Failed to add keyword and save keyword list") return handlerErr.Addf("failed to add keyword and save keyword list: %w", err).Flat() } if user.IsNotInit { pendingMessage = fmt.Sprintf("已将 [ %s ] 添加到您的关键词列表\n
若要在检测到关键词时收到提醒,请点击下方的按钮来初始化您的账号
", keyword) } else { pendingMessage = fmt.Sprintf("已将 [ %s ] 添加到您的关键词列表,您可以继续向此群组添加更多关键词", keyword) } } _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: pendingMessage, ParseMode: models.ParseModeHTML, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "管理关键词", URL: fmt.Sprintf("https://t.me/%s?start=detectkw-addgroup_%d", configs.BotMe.Username, chat.ChatID), }}}}, }) if err != nil { logger.Error(). Err(err). Str("content", "keyword added notice"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "keyword added notice", err) } } } } } return handlerErr.Flat() } func addKeywordStateHandler(opts *handler_params.Message) error { if opts.Message == nil { return nil } logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). Str(utils.GetCurrentFuncName()). Dict(utils.GetChatDict(&opts.Message.Chat)). Dict(utils.GetUserDict(opts.Message.From)). Logger() var handlerErr flaterr.MultErr if opts.Message.Text != "" { keyword := strings.ToLower(strings.Fields(opts.Message.Text)[0]) if len(keyword) > 30 { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "抱歉,单个关键词长度不能超过 30 个英文字符", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, }) if err != nil { logger.Error(). Err(err). Int("length", len(keyword)). Str("content", "keyword is too long"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "keyword is too long", err) } } else { user := KeywordDataList.Users[opts.Message.From.ID] var pendingMessage string var button models.ReplyMarkup var isKeywordExist bool if user.AddingChatID == user.UserID { for _, k := range user.GlobalKeyword { if k == keyword { pendingMessage = "关键词已存在" isKeywordExist = true break } } if !isKeywordExist { logger.Debug(). Str("globalKeyword", keyword). Msg("User add a global keyword") user.GlobalKeyword = append(user.GlobalKeyword, keyword) KeywordDataList.Users[user.UserID] = user err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Str("globalKeyword", keyword). Msg("Failed to add global keyword and save keyword list") handlerErr.Addf("failed to add global keyword and save keyword list: %w", err) } pendingMessage = fmt.Sprintf("已添加全局关键词: [ %s ],您可以继续添加更多全局关键词", keyword) } else { pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在,您可以继续添加其他全局关键词", keyword) } } else { var chatForUser ChatForUser var chatForUserIndex int for i, c := range user.ChatsForUser { if c.ChatID == user.AddingChatID { chatForUser = c chatForUserIndex = i } } targetChat := KeywordDataList.Chats[chatForUser.ChatID] // 群组关键词 for _, k := range chatForUser.Keyword { if k == keyword { isKeywordExist = true break } } if isKeywordExist { pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词\n或发送 /cancel 命令来取消操作", keyword, utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) } else { logger.Debug(). Str("keyword", keyword). Msg("User add a keyword to chat") chatForUser.Keyword = append(chatForUser.Keyword, keyword) user.ChatsForUser[chatForUserIndex] = chatForUser KeywordDataList.Users[user.UserID] = user err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Str("keyword", keyword). Msg("Failed to add keyword and save keyword list") return handlerErr.Addf("failed to add keyword and save keyword list: %w", err).Flat() } pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ],您可以继续向此群组添加更多关键词\n或发送 /cancel 命令来取消操作", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, keyword) } } if isKeywordExist { button = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "⬅️ 停止添加关键词", CallbackData: "detectkw_u_finish", }}}} } else { button = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{ { Text: "↩️ 撤销操作", CallbackData: fmt.Sprintf("detectkw_u_undo_%d_%s", user.AddingChatID, opts.Message.Text), }, { Text: "⬅️ 停止添加关键词", CallbackData: "detectkw_u_finish", // CallbackData: fmt.Sprintf("detectkw_u_chat_%d", user.AddingChatID), }, }}} } _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: pendingMessage, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ParseMode: models.ParseModeHTML, ReplyMarkup: button, }) if err != nil { logger.Error(). Err(err). Str("content", "keyword added notice"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "keyword added notice", err) } } } else { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "请输入有效的关键词", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "⬅️ 停止添加关键词", CallbackData: "detectkw_u_finish", }}}}, }) if err != nil { logger.Error(). Err(err). Str("content", "keyword invalid notice"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "keyword invalid notice", err) } // plugin_utils.EditStateHandler(opts.Message.Chat.ID, 1, nil) } return handlerErr.Flat() } // 重新构建 by chat ID handler 列表 func buildListenList() { // 遍历群组列表,清理群组的用户 ID 列表,再删除 by chat ID handler for index, chat := range KeywordDataList.Chats { chat.UsersID = []int64{} KeywordDataList.Chats[index] = chat plugin_utils.RemoveHandlerByMessageChatIDHandler(chat.ChatID, "detect_keyword") } // 遍历用户列表,若用户启用了该群组,则将用户 ID 添加到群组的用户 ID 列表 for _, user := range KeywordDataList.Users { if !user.IsDisable { for _, keywordForChat := range user.ChatsForUser { if !keywordForChat.IsDisable { chat := KeywordDataList.Chats[keywordForChat.ChatID] chat.UsersID = append(chat.UsersID, user.UserID) KeywordDataList.Chats[keywordForChat.ChatID] = chat } } } } // 遍历群组列表,判断用户 ID 列表数量不为 0 时,添加该群组的 by chat ID handler for _, chat := range KeywordDataList.Chats { if !chat.IsDisable && len(chat.UsersID) > 0 { plugin_utils.AddHandlerByMessageChatIDHandlers(plugin_utils.ByMessageChatIDHandler{ ForChatID: chat.ChatID, PluginName: "detect_keyword", MessageHandler: keywordDetector, }) } } } func keywordDetector(opts *handler_params.Message) error { var handlerErr flaterr.MultErr var text string if opts.Message.Caption != "" { text = opts.Message.Caption } else if opts.Message.Text != "" { text = opts.Message.Text } else { // 没有文字和 caption 直接返回 return nil } // 遍历该群组中启用此功能的用户 ID for _, userID := range KeywordDataList.Chats[opts.Message.Chat.ID].UsersID { // 获取用户信息,开始匹配关键词 user := KeywordDataList.Users[userID] if !user.IsDisable && !user.IsNotInit { // 如果用户设定排除了自己发送的消息,则跳过 if !user.IsIncludeSelf && opts.Message.From != nil && opts.Message.From.ID == userID { continue } // 用户为单独群组设定的关键词 for _, userChatKeywordList := range user.ChatsForUser { // 判断是否是此群组 if userChatKeywordList.ChatID == opts.Message.Chat.ID { for _, keyword := range userChatKeywordList.Keyword { if contain.SubStringCaseInsensitive(keyword, text) { return handlerErr.Add(notifyUser(opts.Ctx, opts.Thebot, opts.Message, &user, keyword, text, false)).Flat() } } } } // 用户全局设定的关键词 for _, userGlobalKeyword := range user.GlobalKeyword { if contain.SubStringCaseInsensitive(userGlobalKeyword, text) { return handlerErr.Add(notifyUser(opts.Ctx, opts.Thebot, opts.Message, &user, userGlobalKeyword, text, true)).Flat() } } } } return handlerErr.Flat() } func notifyUser(ctx context.Context, thebot *bot.Bot, message *models.Message, user *KeywordUserList, keyword, text string, isGlobalKeyword bool) error { var handlerErr flaterr.MultErr // 防止原信息过长导致最后组合的信息超过单条消息字符上限(4096) if len(text) > 2048 { text = text[:2048] + "..." } _, err := thebot.SendMessage(ctx, &bot.SendMessageParams{ ChatID: user.UserID, Text: fmt.Sprintf("在 %s 中\n来自 %s 的消息\n触发了%s关键词 [ %s ]\n
%s
", utils.RemoveIDPrefix(message.Chat.ID), message.Chat.Title, utils.GetMessageFromHyperLink(message, models.ParseModeHTML), utils.TextForTrueOrFalse(isGlobalKeyword, "全局", "群组"), keyword, utils.IgnoreHTMLTags(text), ), ParseMode: models.ParseModeHTML, DisableNotification: user.IsSilentNotice, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "前往查看", URL: utils.MsgLinkPrivate(message.Chat.ID, message.ID), }}}}, }) if err != nil { zerolog.Ctx(ctx).Error(). Err(err). Str("pluginName", "DetectKeyword"). Str(utils.GetCurrentFuncName()). Dict(utils.GetChatDict(&message.Chat)). Int64("userID", user.UserID). Str("keyword", keyword). Str("content", "keyword detected notice to user"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "keyword detected notice to user", err) } user.MentionCount++ KeywordDataList.Users[user.UserID] = *user return handlerErr.Flat() } func groupManageCallbackHandler(opts *handler_params.CallbackQuery) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). Str(utils.GetCurrentFuncName()). Dict(utils.GetChatDict(&opts.CallbackQuery.Message.Message.Chat)). Dict(utils.GetUserDict(&opts.CallbackQuery.From)). Logger() var handlerErr flaterr.MultErr if !utils.UserIsAdmin(opts.Ctx, opts.Thebot, opts.CallbackQuery.Message.Message.Chat.ID, opts.CallbackQuery.From.ID) { _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.CallbackQuery.ID, Text: "您没有权限修改此配置", ShowAlert: true, }) if err != nil { logger.Error(). Err(err). Str("content", "no permission to change group functions"). Msg(flaterr.AnswerCallbackQuery.Str()) handlerErr.Addt(flaterr.AnswerCallbackQuery, "no permission to change group functions", err) } return handlerErr.Flat() } chat := KeywordDataList.Chats[opts.CallbackQuery.Message.Message.Chat.ID] if opts.CallbackQuery.Data == "detectkw_g_switch" { // 群组里的全局开关,是否允许群组内用户使用这个功能,优先级最高 chat.IsDisable = !chat.IsDisable KeywordDataList.Chats[opts.CallbackQuery.Message.Message.Chat.ID] = chat buildListenList() err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Msg("Failed to change group switch and save keyword list") handlerErr.Addf("failed to change group switch and save keyword list: %w", err) _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.CallbackQuery.ID, Text: "更改此选项时发生错误", ShowAlert: true, }) if err != nil { logger.Error(). Err(err). Str("content", "failed to change group switch notice"). Msg(flaterr.AnswerCallbackQuery.Str()) handlerErr.Addt(flaterr.AnswerCallbackQuery, "failed to change group switch notice", err) } return handlerErr.Flat() } } var buttons [][]models.InlineKeyboardButton if chat.IsDisable { buttons = [][]models.InlineKeyboardButton{ {{ Text: "🔄 当前状态: 已禁用 ❌", CallbackData: "detectkw_g_switch", }}, } } else { buttons = [][]models.InlineKeyboardButton{ {{ Text: "设定关键词", URL: fmt.Sprintf("https://t.me/%s?start=detectkw-addgroup_%d", configs.BotMe.Username, opts.CallbackQuery.Message.Message.Chat.ID), }}, {{ Text: "🔄 当前状态: 已启用 ✅", CallbackData: "detectkw_g_switch", }}, } } _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.CallbackQuery.Message.Message.ID, Text: fmt.Sprintf("消息关键词检测\n%s\n\n当前群组中有 %d 个用户启用了此功能",utils.TextForTrueOrFalse(chat.IsDisable, "已为当前群组关闭关键词检测功能,已设定了关键词的用户将无法再收到此群组的提醒", "此功能允许用户设定一些关键词,当机器人检测到群组内的消息包含用户设定的关键词时,向用户发送提醒"), len(chat.UsersID)), ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: buttons}, }) if err != nil { logger.Error(). Err(err). Str("content", "group function manager keyboard"). Msg(flaterr.EditMessageText.Str()) handlerErr.Addt(flaterr.EditMessageText, "group function manager keyboard", err) } return handlerErr.Flat() } func userManageCallbackHandler(opts *handler_params.CallbackQuery) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). Str(utils.GetCurrentFuncName()). Dict(utils.GetUserDict(&opts.CallbackQuery.From)). Str("callbackQueryData", opts.CallbackQuery.Data). Logger() var handlerErr flaterr.MultErr var needEdit bool = true var needSave bool = true user := KeywordDataList.Users[opts.CallbackQuery.From.ID] switch opts.CallbackQuery.Data { case "detectkw_u": // 用户管理关键词主菜单 needSave = false case "detectkw_u_globalswitch": // 功能全局开关 user.IsDisable = !user.IsDisable case "detectkw_u_noticeswitch": // 是否静默通知 user.IsSilentNotice = !user.IsSilentNotice case "detectkw_u_selfswitch": // 是否检测自己发送的消息 user.IsIncludeSelf = !user.IsIncludeSelf case "detectkw_u_finish": // 停止添加群组关键词 user.AddingChatID = 0 plugin_utils.RemoveMessageStateHandler(user.UserID) case "detectkw_u_chatdisablebyadmin": // 目标群组的管理员为群组关闭了此功能 needEdit = false needSave = false _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.CallbackQuery.ID, Text: "此群组中的管理员禁用了此功能,因此,您无法再收到来自该群组的关键词提醒,您可以询问该群组的管理员是否可以重新开启这个功能", ShowAlert: true, }) if err != nil { logger.Error(). Err(err). Str("content", "this group is disable by admins"). Msg(flaterr.AnswerCallbackQuery.Str()) handlerErr.Addt(flaterr.AnswerCallbackQuery, "this group is disable by admins", err) } default: if strings.HasPrefix(opts.CallbackQuery.Data, "detectkw_u_undo_") || strings.HasPrefix(opts.CallbackQuery.Data, "detectkw_u_delkw_") { needEdit = false // 撤销添加或删除关键词 var chatIDAndKeyword string if strings.HasPrefix(opts.CallbackQuery.Data, "detectkw_u_undo_") { chatIDAndKeyword = strings.TrimPrefix(opts.CallbackQuery.Data, "detectkw_u_undo_") } else { chatIDAndKeyword = strings.TrimPrefix(opts.CallbackQuery.Data, "detectkw_u_delkw_") } chatIDAndKeywordList := strings.Split(chatIDAndKeyword, "_") chatID, err := strconv.ParseInt(chatIDAndKeywordList[0], 10, 64) if err != nil { logger.Error(). Err(err). Msg("Failed to parse chat ID when user undo add or delete a keyword") return handlerErr.Addf("failed to parse chat ID when user undo add or delete a keyword: %w", err).Flat() } else { // 删除关键词过程 if chatID == user.UserID { // 全局关键词 var tempKeyword []string for _, keyword := range user.GlobalKeyword { if keyword != chatIDAndKeywordList[1] { tempKeyword = append(tempKeyword, keyword) } } user.GlobalKeyword = tempKeyword } else { // 群组关键词 for index, chatForUser := range user.ChatsForUser { if chatForUser.ChatID == chatID { var tempKeyword []string for _, keyword := range chatForUser.Keyword { if keyword != chatIDAndKeywordList[1] { tempKeyword = append(tempKeyword, keyword) } } chatForUser.Keyword = tempKeyword user.ChatsForUser[index] = chatForUser } } } if strings.HasPrefix(opts.CallbackQuery.Data, "detectkw_u_undo_") { _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.CallbackQuery.Message.Message.ID, Text: fmt.Sprintf("已取消添加 [ %s ] 关键词,您可以继续添加其他关键词", chatIDAndKeywordList[1]), ParseMode: models.ParseModeHTML, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{ { Text: "✅ 完成", CallbackData: "detectkw_u_finish", }}}}, }) if err != nil { logger.Error(). Err(err). Str("content", "add keyword has been canceled notice"). Msg(flaterr.EditMessageText.Str()) handlerErr.Addt(flaterr.EditMessageText, "add keyword has been canceled notice", err) } } else { var buttons [][]models.InlineKeyboardButton var pendingMessage string if chatID == user.UserID { buttons = user.globalKeywordButtons() pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前设定了 %d 个全局关键词", chatIDAndKeywordList[1], len(buttons)) } else { for _, chat := range user.ChatsForUser { if chat.ChatID == chatID { buttons = chat.keywordButtons() pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前为 %s 群组设定了 %d 个关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName, len(buttons)) break } } } buttons = append(buttons, []models.InlineKeyboardButton{ { Text: "⬅️ 返回主菜单", CallbackData: "detectkw_u", }, { Text: "➕ 添加关键词", CallbackData: fmt.Sprintf("detectkw_u_adding_%d", chatID), }, }) _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.CallbackQuery.Message.Message.ID, Text: pendingMessage, ParseMode: models.ParseModeHTML, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: buttons }, }) if err != nil { logger.Error(). Err(err). Str("content", "keyword list keyboard with deleted keyword notice"). Msg(flaterr.EditMessageText.Str()) } } } } else if strings.HasPrefix(opts.CallbackQuery.Data, "detectkw_u_adding_") { needEdit = false // 设定要往哪个群组里添加关键词 chatID := strings.TrimPrefix(opts.CallbackQuery.Data, "detectkw_u_adding_") chatID_int64, err := strconv.ParseInt(chatID, 10, 64) if err != nil { logger.Error(). Err(err). Msg("Failed to parse chat ID when user selecting chat to add keyword") return handlerErr.Addf("failed to parse chat ID when user selecting chat to add keyword: %w", err).Flat() } else { user.AddingChatID = chatID_int64 var pendingMessage string if chatID_int64 == user.UserID { pendingMessage = "已将全局关键词设为添加关键词的目标,请继续发送您要添加单个的全局关键词\n或发送 /cancel 命令来取消操作" } else { pendingMessage = fmt.Sprintf("已将 %s 群组设为添加关键词的目标群组,请继续发送您要添加单个的群组关键词\n或发送 /cancel 命令来取消操作", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName) } _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.CallbackQuery.Message.Message.ID, Text: pendingMessage, ParseMode: models.ParseModeHTML, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "⬅️ 停止添加关键词", CallbackData: "detectkw_u_finish", }}}}, }) if err != nil { logger.Error(). Err(err). Str("content", "ready to add keyword notice"). Msg(flaterr.EditMessageText.Str()) handlerErr.Addt(flaterr.EditMessageText, "ready to add keyword notice", err) } else { plugin_utils.AddMessageStateHandler(plugin_utils.MessageStateHandler{ ForChatID: opts.CallbackQuery.Message.Message.Chat.ID, PluginName: "addKeywordState", Remaining: -1, MessageHandler: addKeywordStateHandler, }) } } } else if strings.HasPrefix(opts.CallbackQuery.Data, "detectkw_u_switch_chat_") { // 用户启用或禁用某个群组的关键词检测开关 chatID, err := strconv.ParseInt(strings.TrimPrefix(opts.CallbackQuery.Data, "detectkw_u_switch_chat_"), 10, 64) if err != nil { logger.Error(). Err(err). Msg("Failed to parse chat ID when user change the group switch") return handlerErr.Addf("failed to parse chat ID when user change the group switch: %w", err).Flat() } else { for index, chat := range user.ChatsForUser { if chat.ChatID == chatID { chat.IsDisable = !chat.IsDisable } user.ChatsForUser[index] = chat } } } else if strings.HasPrefix(opts.CallbackQuery.Data, "detectkw_u_chat_") { needEdit = false needSave = false // 显示某个群组的关键词列表 chatID, err := strconv.ParseInt(strings.TrimPrefix(opts.CallbackQuery.Data, "detectkw_u_chat_"), 10, 64) if err != nil { logger.Error(). Err(err). Msg("Failed to parse chat ID when user wanna manage keyword for group") return handlerErr.Addf("failed to parse chat ID when user wanna manage keyword for group: %w", err).Flat() } else { var buttons [][]models.InlineKeyboardButton var pendingMessage string if chatID == user.UserID { // 全局关键词 buttons = user.globalKeywordButtons() if len(buttons) == 0 { pendingMessage = "您没有设定任何全局关键词\n点击下方按钮来添加全局关键词" } else { pendingMessage = fmt.Sprintf("您当前设定了 %d 个全局关键词\n
全局关键词将对您添加的全部群组生效,但在部分情况下,全局关键词不会生效:\n- 您手动将群组设定为禁用状态\n- 对应群组的管理员为该群组关闭了此功能
", len(buttons)) } } else { // 为群组设定的关键词 for _, chat := range KeywordDataList.Users[opts.CallbackQuery.From.ID].ChatsForUser { if chat.ChatID == chatID { buttons = chat.keywordButtons() if len(buttons) == 0 { pendingMessage = fmt.Sprintf("当前群组 %s 没有关键词\n点击下方按钮来为此群组添加关键词", utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName) } else { pendingMessage = fmt.Sprintf("您当前为 %s 群组设定了 %d 个关键词", utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName, len(buttons)) } break } } } buttons = append(buttons, []models.InlineKeyboardButton{ { Text: "⬅️ 返回主菜单", CallbackData: "detectkw_u", }, { Text: "➕ 添加关键词", CallbackData: fmt.Sprintf("detectkw_u_adding_%d", chatID), }, }) _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.CallbackQuery.Message.Message.ID, Text: pendingMessage, ParseMode: models.ParseModeHTML, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: buttons }, }) if err != nil { logger.Error(). Err(err). Str("content", "group keyword list keyboard"). Msg(flaterr.EditMessageText.Str()) handlerErr.Addt(flaterr.EditMessageText, "group keyword list keyboard", err) } } } else if strings.HasPrefix(opts.CallbackQuery.Data, "detectkw_u_kw_") { needEdit = false needSave = false // 管理一个关键词 chatIDAndKeyword := strings.TrimPrefix(opts.CallbackQuery.Data, "detectkw_u_kw_") chatIDAndKeywordList := strings.Split(chatIDAndKeyword, "_") chatID, err := strconv.ParseInt(chatIDAndKeywordList[0], 10, 64) if err != nil { logger.Error(). Err(err). Msg("Failed to parse chat ID when user wanna manage a keyword") return handlerErr.Addf("failed to parse chat ID when user wanna manage a keyword: %w", err).Flat() } else { var pendingMessage string if chatID == user.UserID { pendingMessage = fmt.Sprintf("[ %s ] 是您设定的全局关键词", chatIDAndKeywordList[1]) } else { pendingMessage = fmt.Sprintf("[ %s ] 是为 %s 群组设定的关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName) } _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.CallbackQuery.Message.Message.ID, Text: pendingMessage, ParseMode: models.ParseModeHTML, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{ { Text: "⬅️ 返回", CallbackData: "detectkw_u_chat_" + chatIDAndKeywordList[0], }, { Text: "❌ 删除此关键词", CallbackData: "detectkw_u_delkw_" + chatIDAndKeyword, }, }}}, }) if err != nil { logger.Error(). Err(err). Str("content", "keyword manager keyboard"). Msg(flaterr.EditMessageText.Str()) handlerErr.Addt(flaterr.EditMessageText, "keyword manager keyboard", err) } } } else if strings.HasPrefix(opts.CallbackQuery.Data, "detectkw_u_add_") { needEdit = false // 在未设定添加群组时直接发送 `/setkeyword 关键词` 显示的键盘中的按钮,见 KeywordUserList.selectChat 方法 chatIDAndKeyword := strings.TrimPrefix(opts.CallbackQuery.Data, "detectkw_u_add_") chatIDAndKeywordList := strings.Split(chatIDAndKeyword, "_") chatID, err := strconv.ParseInt(chatIDAndKeywordList[0], 10, 64) if err != nil { logger.Error(). Err(err). Msg("Failed to parse chat ID when user wanna add a keyword") return handlerErr.Addf("failed to parse chat ID when user wanna add a keyword: %w", err).Flat() } else { var pendingMessage string var button models.ReplyMarkup var isKeywordExist bool if chatID == user.UserID { // 全局关键词 for _, k := range user.GlobalKeyword { if k == chatIDAndKeywordList[1] { isKeywordExist = true break } } if !isKeywordExist { logger.Debug(). Str("globalKeyword", chatIDAndKeywordList[1]). Msg("User add a global keyword") user.GlobalKeyword = append(user.GlobalKeyword, chatIDAndKeywordList[1]) pendingMessage = fmt.Sprintf("已添加全局关键词 [ %s ]", chatIDAndKeywordList[1]) } else { pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在", chatIDAndKeywordList[1]) } } else { // 群组关键词 var chatForUser ChatForUser var chatForUserIndex int for i, c := range user.ChatsForUser { if c.ChatID == chatID { chatForUser = c chatForUserIndex = i } } targetChat := KeywordDataList.Chats[chatID] for _, k := range chatForUser.Keyword { if k == chatIDAndKeywordList[1] { isKeywordExist = true break } } if !isKeywordExist { logger.Debug(). Str("keyword", chatIDAndKeywordList[1]). Msg("User add a keyword to chat") chatForUser.Keyword = append(chatForUser.Keyword, chatIDAndKeywordList[1]) user.ChatsForUser[chatForUserIndex] = chatForUser pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ]", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(chatIDAndKeywordList[1])) } else { pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) } } if isKeywordExist { button = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "✅ 完成", CallbackData: "detectkw_u", }}}} } else { button = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{ { Text: "↩️ 撤销操作", CallbackData: "detectkw_u_undo_" + chatIDAndKeyword, }, { Text: "✅ 完成", CallbackData: "detectkw_u", }, }}} } _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.CallbackQuery.Message.Message.ID, Text: pendingMessage, ReplyMarkup: button, ParseMode: models.ParseModeHTML, }) if err != nil { logger.Error(). Err(err). Str("content", "keyword added notice"). Msg(flaterr.EditMessageText.Str()) handlerErr.Addt(flaterr.EditMessageText, "keyword added notice", err) } } } } if needEdit { _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.CallbackQuery.Message.Message.ID, Text: user.userStatus(), ReplyMarkup: user.buildUserChatList(), }) if err != nil { logger.Error(). Err(err). Str("content", "main manager keyboard"). Msg(flaterr.EditMessageText.Str()) handlerErr.Addt(flaterr.EditMessageText, "main manager keyboard", err) } } if needSave { KeywordDataList.Users[opts.CallbackQuery.From.ID] = user buildListenList() err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Dict(utils.GetUserDict(&opts.CallbackQuery.From)). Msg("Failed to save keyword list") handlerErr.Addf("failed to save keyword list: %w", err) } } return handlerErr.Flat() } func startPrefixAddGroup(opts *handler_params.Message) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). Str(utils.GetCurrentFuncName()). Dict(utils.GetUserDict(opts.Message.From)). Str("messageText", opts.Message.Text). Logger() var handlerErr flaterr.MultErr var needSave bool user := KeywordDataList.Users[opts.Message.From.ID] if user.AddTime == "" { // 初始化用户 user = KeywordUserList{ UserID: opts.Message.From.ID, AddTime: time.Now().Format(time.RFC3339), Limit: 50, } } else if user.IsNotInit { // 用户之前仅在群组内发送命令添加了关键词,但并没有点击机器人来初始化 user.IsNotInit = false needSave = true } if strings.HasPrefix(opts.Fields[1], "detectkw-addgroup_") { var chat KeywordChatList var isExist bool groupID_int64, err := strconv.ParseInt(strings.TrimPrefix(opts.Fields[1], "detectkw-addgroup_"), 10, 64) if err != nil { logger.Error(). Err(err). Msg("Failed to parse chat ID when user add a group by /start command") handlerErr.Addf("failed to parse chat ID when user add a group by /start command: %w", err) } else { chat, isExist = KeywordDataList.Chats[groupID_int64] } if isExist { var IsAdded bool = false var pendingMessage string for _, keyword := range user.ChatsForUser { if keyword.ChatID == groupID_int64 { IsAdded = true break } } if !IsAdded { logger.Debug(). Msg("User add a chat to listen list by /start command") user.ChatsForUser = append(user.ChatsForUser, ChatForUser{ ChatID: groupID_int64, }) needSave = true pendingMessage = fmt.Sprintf("已添加 %s 群组\n%s", utils.RemoveIDPrefix(chat.ChatID), chat.ChatName, user.userStatus()) } else { pendingMessage = user.userStatus() } _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: pendingMessage, ParseMode: models.ParseModeHTML, ReplyMarkup: user.buildUserChatList(), }) if err != nil { logger.Error(). Err(err). Str("content", "added group in user list"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "added group in user list", err) } } else { _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "无法添加群组,请尝试在对应群组中发送 /setkeyword 命令后再尝试点击 [ 设定关键词 ] 按钮", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "将此机器人添加到群组或频道", URL: fmt.Sprintf("https://t.me/%s?startgroup=detectkw", configs.BotMe.Username), }}}}, }) if err != nil { logger.Error(). Err(err). Str("content", "invalid chat ID in /start message"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "invalid chat ID in /start message", err) } } } if needSave { KeywordDataList.Users[opts.Message.From.ID] = user buildListenList() err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Message.From)). Msg("Failed to save keyword list") handlerErr.Addf("failed to save keyword list: %w", err) } } return handlerErr.Flat() } func startBotAddedToGroup(opts *handler_params.Message) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). Str(utils.GetCurrentFuncName()). Dict(utils.GetChatDict(&opts.Message.Chat)). Dict(utils.GetUserDict(opts.Message.From)). Logger() var handlerErr flaterr.MultErr chat := KeywordDataList.Chats[opts.Message.Chat.ID] if chat.AddTime == "" { // 初始化群组 chat = KeywordChatList{ ChatID: opts.Message.Chat.ID, ChatName: opts.Message.Chat.Title, ChatUsername: opts.Message.Chat.Username, ChatType: opts.Message.Chat.Type, AddTime: time.Now().Format(time.RFC3339), InitByID: opts.Message.From.ID, } KeywordDataList.Chats[opts.Message.Chat.ID] = chat err := saveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). Dict(utils.GetChatDict(&opts.Message.Chat)). Msg("Failed to init chat and save keyword list") return handlerErr.Addf("failed to init chat and save keyword list: %w", err).Flat() } } // 只有一个 /setkeyword 命令 _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Message.Chat.ID, Text: "已记录群组,点击下方左侧按钮来设定监听关键词\n若您是群组的管理员,您可以点击右侧的按钮来管理此功能", ReplyParameters: &models.ReplyParameters{ MessageID: opts.Message.ID }, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{ { Text: "设定关键词", URL: fmt.Sprintf("https://t.me/%s?start=detectkw-addgroup_%d", configs.BotMe.Username, opts.Message.Chat.ID), }, { Text: "管理此功能", CallbackData: "detectkw_g", }, }}}, }) if err != nil { logger.Error(). Err(err). Str("content", "group record link button"). Msg(flaterr.SendMessage.Str()) handlerErr.Addt(flaterr.SendMessage, "group record link button", err) } return handlerErr.Flat() }