// Dynamic bot settings panel โ€” toggle all settings from within the bot. // No more .env edits needed! All changes are saved to MongoDB and cached in memory. package plugins import ( "fmt" "regexp" "strconv" "strings" "github.com/Jisin0/Go-Filter-Bot/database" "github.com/Jisin0/Go-Filter-Bot/utils" "github.com/PaulSonOfLars/gotgbot/v2" "github.com/PaulSonOfLars/gotgbot/v2/ext" ) var cbSettingsRegex = regexp.MustCompile(`settings\((.+)\)`) func boolEmoji(v bool) string { if v { return "โœ…" } return "โŒ" } // buildSettingsMarkup generates the inline keyboard for the settings panel. func buildSettingsMarkup(s database.BotSettings) gotgbot.InlineKeyboardMarkup { return gotgbot.InlineKeyboardMarkup{ InlineKeyboard: [][]gotgbot.InlineKeyboardButton{ { {Text: fmt.Sprintf("%s Multi Filter", boolEmoji(s.MultiFilter)), CallbackData: "settings(toggle_multi_filter)"}, }, { {Text: fmt.Sprintf("%s Search as Button", boolEmoji(s.SearchAsButton)), CallbackData: "settings(toggle_search_button)"}, }, { {Text: fmt.Sprintf("โฑ Auto Delete: %dm", s.AutoDelete), CallbackData: "settings(auto_delete)"}, }, { {Text: "๐Ÿ”— Shortener URL", CallbackData: "settings(shortener_url)"}, {Text: "๐Ÿ”‘ Shortener API", CallbackData: "settings(shortener_api)"}, }, { {Text: fmt.Sprintf("๐Ÿ“ข Force-Sub (%d ch)", len(s.ForceSubChats)), CallbackData: "settings(fsub_info)"}, {Text: fmt.Sprintf("๐Ÿ“ Index (%d ch)", len(s.IndexChannels)), CallbackData: "settings(index_info)"}, }, { {Text: fmt.Sprintf("๐Ÿ‘‘ Admins (%d dynamic)", len(s.DynamicAdmins)), CallbackData: "settings(admin_info)"}, }, { {Text: "๐Ÿ”„ Refresh", CallbackData: "settings(refresh)"}, {Text: "โœ– Close", CallbackData: "close"}, }, }, } } // buildSettingsText generates the main settings panel text. func buildSettingsText(s database.BotSettings) string { shortURL := s.ShortenerURL if shortURL == "" { shortURL = "Not Set" } shortAPI := "โ€ขโ€ขโ€ขโ€ขโ€ขโ€ข" if s.ShortenerAPI == "" { shortAPI = "Not Set" } fsubList := "None" if len(s.ForceSubChats) > 0 { parts := make([]string, len(s.ForceSubChats)) for i, c := range s.ForceSubChats { parts[i] = fmt.Sprintf("%d", c) } fsubList = strings.Join(parts, ", ") } indexList := "None" if len(s.IndexChannels) > 0 { parts := make([]string, len(s.IndexChannels)) for i, c := range s.IndexChannels { parts[i] = fmt.Sprintf("%d", c) } indexList = strings.Join(parts, ", ") } return fmt.Sprintf(`โš™๏ธ Bแดแด› Sแด‡แด›แด›ษชษดษขs Pแด€ษดแด‡สŸ Multi Filter : %s Search Mode : %s Auto Delete : %d minutes Shortener URL : %s Shortener API : %s Force-Sub : %s Auto-Index : %s Dynamic Admins : %d Tap the buttons below to change settings. Changes take effect immediately โ€” no restart needed!`, boolEmoji(s.MultiFilter), func() string { if s.SearchAsButton { return "Button" } return "Hyperlink" }(), s.AutoDelete, shortURL, shortAPI, fsubList, indexList, len(s.DynamicAdmins), ) } // Settings handles the /settings command. // //nolint:errcheck func Settings(bot *gotgbot.Bot, ctx *ext.Context) error { if !utils.IsAdmin(ctx.EffectiveUser.Id) { ctx.EffectiveMessage.Reply(bot, "โ›” Only bot admins can use this command!", &gotgbot.SendMessageOpts{}) return nil } s := database.GetBotSettings() _, err := ctx.Message.Reply(bot, buildSettingsText(s), &gotgbot.SendMessageOpts{ ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: buildSettingsMarkup(s), }) if err != nil { fmt.Printf("settings: %v\n", err) } return nil } // CbSettings handles all settings callbacks. // //nolint:errcheck func CbSettings(bot *gotgbot.Bot, ctx *ext.Context) error { update := ctx.CallbackQuery if !utils.IsAdmin(update.From.Id) { update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "โ›” Only bot admins can change settings!", ShowAlert: true}) return nil } match := cbSettingsRegex.FindStringSubmatch(update.Data) if len(match) < 2 { return nil } action := match[1] s := database.GetBotSettings() switch action { case "toggle_multi_filter": DB.SetBotSetting("multi_filter", !s.MultiFilter) update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{ Text: fmt.Sprintf("Multi Filter: %s", boolEmoji(!s.MultiFilter)), }) case "toggle_search_button": DB.SetBotSetting("search_as_button", !s.SearchAsButton) update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{ Text: fmt.Sprintf("Search as Button: %s", boolEmoji(!s.SearchAsButton)), }) case "auto_delete": // Cycle through: 0 โ†’ 5 โ†’ 10 โ†’ 30 โ†’ 60 โ†’ 0 var next int64 switch { case s.AutoDelete == 0: next = 5 case s.AutoDelete < 10: next = 10 case s.AutoDelete < 30: next = 30 case s.AutoDelete < 60: next = 60 default: next = 0 } DB.SetBotSetting("auto_delete", next) if next == 0 { update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Auto Delete: OFF"}) } else { update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: fmt.Sprintf("Auto Delete: %d minutes", next)}) } case "shortener_url": update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{}) // Ask in PM โ€” works from both group and PM contexts pmChat := &gotgbot.Chat{Id: update.From.Id, Type: gotgbot.ChatTypePrivate} m := utils.Ask(bot, "๐Ÿ“ Send the new Shortener URL\n\nExample: fpshortner.in\n\nSend off to disable.", pmChat, &update.From) if m == nil { return nil } val := strings.TrimSpace(m.Text) if strings.ToLower(val) == "off" { val = "" } DB.SetBotSetting("shortener_url", val) if val == "" { bot.SendMessage(update.From.Id, "โœ… Shortener URL disabled.", &gotgbot.SendMessageOpts{}) } else { bot.SendMessage(update.From.Id, fmt.Sprintf("โœ… Shortener URL set to: %s", val), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML}) } case "shortener_api": update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{}) pmChat := &gotgbot.Chat{Id: update.From.Id, Type: gotgbot.ChatTypePrivate} m := utils.Ask(bot, "๐Ÿ“ Send the new Shortener API Key\n\nSend off to disable.", pmChat, &update.From) if m == nil { return nil } val := strings.TrimSpace(m.Text) if strings.ToLower(val) == "off" { val = "" } DB.SetBotSetting("shortener_api", val) if val == "" { bot.SendMessage(update.From.Id, "โœ… Shortener API disabled.", &gotgbot.SendMessageOpts{}) } else { bot.SendMessage(update.From.Id, "โœ… Shortener API Key updated!", &gotgbot.SendMessageOpts{}) } case "fsub_info": text := "Use /addfsub <channel_id> to add\nUse /delfsub <channel_id> to remove\nUse /fsub to list" update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Manage with /addfsub, /delfsub, /fsub", ShowAlert: true}) msg, ok := update.Message.(*gotgbot.Message) if !ok { return nil } bot.SendMessage(msg.Chat.Id, text, &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML}) return nil case "index_info": text := "Use /addindex <channel_id> to add\nUse /delindex <channel_id> to remove\nUse /bulkindex <channel_id> [from] [to] for bulk indexing" update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Manage with /addindex, /delindex, /bulkindex", ShowAlert: true}) msg2, ok2 := update.Message.(*gotgbot.Message) if !ok2 { return nil } bot.SendMessage(msg2.Chat.Id, text, &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML}) return nil case "admin_info": text := "Use /addadmin <user_id> to add a dynamic admin\nUse /deladmin <user_id> to remove\nUse /admins to list all admins" update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Manage with /addadmin, /deladmin, /admins", ShowAlert: true}) msg3, ok3 := update.Message.(*gotgbot.Message) if !ok3 { return nil } bot.SendMessage(msg3.Chat.Id, text, &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML}) return nil case "refresh": DB.RefreshBotSettings() update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "โ™ป๏ธ Settings refreshed!"}) default: // Handle set_autodelete_ from potential custom input if strings.HasPrefix(action, "set_autodelete_") { valStr := strings.TrimPrefix(action, "set_autodelete_") val, err := strconv.ParseInt(valStr, 10, 64) if err == nil { DB.SetBotSetting("auto_delete", val) update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: fmt.Sprintf("Auto Delete: %d min", val)}) } } } // Refresh the settings panel with updated values newS := database.GetBotSettings() update.Message.EditText(bot, buildSettingsText(newS), &gotgbot.EditMessageTextOpts{ ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: buildSettingsMarkup(newS), }) return nil } // CbClose handles the close button on settings and other panels. // //nolint:errcheck func CbClose(bot *gotgbot.Bot, ctx *ext.Context) error { ctx.CallbackQuery.Message.Delete(bot, nil) ctx.CallbackQuery.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{}) return nil }