From 8aa8521304dcf7cc682992cd9c957a4d30d41844 Mon Sep 17 00:00:00 2001 From: Astra Date: Mon, 9 Mar 2026 20:53:04 +0000 Subject: [PATCH] Update telegram-approval-join submodule and apply branding patches --- config/config.go | 18 +++-- handlers/callbacks.go | 77 ++++++++++++++++--- handlers/handlers.go | 2 +- ...{0002-nuzzles.patch => 0001-nuzzles.patch} | 0 scripts/sync.sh | 4 +- 5 files changed, 80 insertions(+), 21 deletions(-) rename patches/{0002-nuzzles.patch => 0001-nuzzles.patch} (100%) diff --git a/config/config.go b/config/config.go index 256eb98..45215ea 100644 --- a/config/config.go +++ b/config/config.go @@ -8,14 +8,15 @@ import ( ) type Config struct { - BotToken *string `yaml:"bot_token"` - AdminChatId *int64 `yaml:"admin_chat_id"` - AdminChatTopicId *int `yaml:"admin_chat_topic_id"` - TargetChatId *int64 `yaml:"target_chat_id"` - EntryMessage string `yaml:"entry_message"` - ApprovalMessage string `yaml:"approval_message"` - SendApprovalMessage bool `yaml:"send_approval_message"` - DeleteRequestAfterDecision bool `yaml:"delete_request_after_decision"` + BotToken *string `yaml:"bot_token"` + AdminChatId *int64 `yaml:"admin_chat_id"` + AdminChatTopicId *int `yaml:"admin_chat_topic_id"` + TargetChatId *int64 `yaml:"target_chat_id"` + EntryMessage string `yaml:"entry_message"` + ApprovalMessage string `yaml:"approval_message"` + SendApprovalMessage bool `yaml:"send_approval_message"` + DeleteRequestAfterDecision bool `yaml:"delete_request_after_decision"` + CannedDeclineResponses []string `yaml:"canned_decline_responses"` } func (c *Config) LoadConfig() error { @@ -47,6 +48,7 @@ func (c *Config) CreateConfig() error { ApprovalMessage: "", SendApprovalMessage: false, DeleteRequestAfterDecision: false, + CannedDeclineResponses: []string{}, } encoder := yaml.NewEncoder(f) diff --git a/handlers/callbacks.go b/handlers/callbacks.go index df5b3d6..8ef2573 100644 --- a/handlers/callbacks.go +++ b/handlers/callbacks.go @@ -36,6 +36,7 @@ func (bot *Bot) HandleCallbackQuery(query *api.CallbackQuery) { switch action { case "approve": bot.handleApproveRequest(query, user, userString, adminUserString) + bot.DeletePendingUser(args) case "decline": bot.handleDeclineRequest(query, user, userString, adminUserString) case "ban": @@ -44,10 +45,19 @@ func (bot *Bot) HandleCallbackQuery(query *api.CallbackQuery) { return case "banc": bot.handleBanRequest(query, user, userString, adminUserString) + bot.DeletePendingUser(args) + case "cannedrespsel": + parts := strings.Split(query.Data, "_") + if len(parts) >= 3 { + var respIdx int + fmt.Sscanf(parts[2], "%d", &respIdx) + bot.sendCannedResponse(query, user, respIdx) + } + bot.DeletePendingUser(args) + bot.API.Request(api.NewCallback(query.ID, "")) + return } - bot.DeletePendingUser(args) - if bot.Config.DeleteRequestAfterDecision { go bot.scheduleMessageDeletion(query.Message.Chat.ID, query.Message.MessageID, 10*time.Second) } @@ -93,14 +103,39 @@ func (bot *Bot) handleDeclineRequest(query *api.CallbackQuery, user *ExtendedCha return } - utils.EditMessage(bot.API, query.Message.Chat.ID, query.Message.MessageID, - fmt.Sprintf(AdminDeclinedMsg, - userString, user.From.ID, user.JoinReason, adminUserString, - time.Now().Format("2006-01-02 15:04:05"), - defaultReason, - ), + messageText := fmt.Sprintf(AdminDeclinedMsg, + userString, user.From.ID, user.JoinReason, adminUserString, + time.Now().Format("2006-01-02 15:04:05"), + defaultReason, ) + edit := api.NewEditMessageText(query.Message.Chat.ID, query.Message.MessageID, messageText) + edit.ParseMode = api.ModeHTML + edit.Entities = query.Message.Entities + + if len(bot.Config.CannedDeclineResponses) > 0 { + var rows [][]api.InlineKeyboardButton + for i, response := range bot.Config.CannedDeclineResponses { + // Clean up the response text for button display + snippet := strings.TrimSpace(response) + snippet = strings.ReplaceAll(snippet, "\n", " ") + // Remove multiple consecutive spaces + for strings.Contains(snippet, " ") { + snippet = strings.ReplaceAll(snippet, " ", " ") + } + // Truncate to 30 chars for button text + if len(snippet) > 30 { + snippet = snippet[:30] + "..." + } + btn := api.NewInlineKeyboardButtonData(snippet, fmt.Sprintf("cannedrespsel_%d_%d", user.From.ID, i)) + rows = append(rows, []api.InlineKeyboardButton{btn}) + } + keyboard := api.NewInlineKeyboardMarkup(rows...) + edit.ReplyMarkup = &keyboard + } + + bot.API.Send(edit) + bot.API.Request(api.NewCallback(query.ID, "Join request declined.")) } @@ -175,12 +210,14 @@ func (bot *Bot) HandleDeclineReason(update *api.Update) { userID, username, joinReason, declinedBy, declinedAt := utils.GetInfoFromMsg(repliedMsg.Text) reason := utils.EscapeHTML(update.Message.Text) - if strings.HasPrefix(update.Message.Text, "+") { - reason = utils.EscapeHTML(update.Message.Text[1:]) + if !strings.HasPrefix(update.Message.Text, "/") { + reason = utils.EscapeHTML(update.Message.Text) utils.SendMessage(bot.API, userID, 0, fmt.Sprintf("Your join request was declined for the following reason:\n\n%s", reason)) } + reason = strings.TrimPrefix(reason, "/") + utils.EditMessage(bot.API, update.Message.Chat.ID, repliedMsg.MessageID, fmt.Sprintf(AdminDeclinedMsg, username, userID, joinReason, declinedBy, declinedAt, reason)) @@ -188,6 +225,26 @@ func (bot *Bot) HandleDeclineReason(update *api.Update) { bot.API.Send(api.NewDeleteMessage(update.Message.Chat.ID, update.Message.MessageID)) } +// sendCannedResponse sends a canned decline response to the declined user. +func (bot *Bot) sendCannedResponse(query *api.CallbackQuery, user *ExtendedChatJoinRequest, respIdx int) { + if respIdx < 0 || respIdx >= len(bot.Config.CannedDeclineResponses) { + return + } + + reason := utils.EscapeHTML(bot.Config.CannedDeclineResponses[respIdx]) + utils.SendMessage(bot.API, user.From.ID, 0, + fmt.Sprintf("Your join request was declined for the following reason:\n\n%s", reason)) + + // Extract user info from original message and reformat with the canned response + userID, username, joinReason, declinedBy, declinedAt := utils.GetInfoFromMsg(query.Message.Text) + + edit := api.NewEditMessageText(query.Message.Chat.ID, query.Message.MessageID, + fmt.Sprintf(AdminDeclinedMsg, username, userID, joinReason, declinedBy, declinedAt, reason)) + edit.ParseMode = api.ModeHTML + edit.Entities = query.Message.Entities + bot.API.Send(edit) +} + // parseCallbackData parses the action and user ID from a callback query's data string. func parseCallbackData(data string) (action string, userID int64, err error) { normalized := strings.Join(strings.Split(data, "_"), " ") diff --git a/handlers/handlers.go b/handlers/handlers.go index 5493ae6..bce80c6 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -13,7 +13,7 @@ const ( AdminDeclinedMsg = "❌ Join #request declined for %s [%d]\n\nJoin reason: %s\nDeclined by: %s\nDeclined at: %s\nDeclined reason: %s" AdminBannedMsg = "🚫 Join #request banned for %s [%d]\n\nJoin reason: %s\nBanned by: %s\nBanned at: %s\nBanned until: %s" AdminFailedMsg = "⚠️ Join #request failed for %s [%d]\n\nJoin reason: %s\nFailure reason: %s" - defaultReason = "(no reason provided, reply to this to set one, prepend with + to also send to user)" + defaultReason = "(no reason provided, reply to this message to send one to the user, prepend with / to just set it)" ) // Types shared by handler files diff --git a/patches/0002-nuzzles.patch b/patches/0001-nuzzles.patch similarity index 100% rename from patches/0002-nuzzles.patch rename to patches/0001-nuzzles.patch diff --git a/scripts/sync.sh b/scripts/sync.sh index 880b481..3f1bbae 100755 --- a/scripts/sync.sh +++ b/scripts/sync.sh @@ -63,11 +63,11 @@ if [[ -f "$PATCH_FILE" ]]; then echo " Run the following to see conflicts:" echo " patch --dry-run -p1 -d $ROOT_DIR < $PATCH_FILE" echo "" - echo " Update patches/branding.patch to match the new source, then re-run sync." + echo " Update patches/0001-nuzzles.patch to match the new source, then re-run sync." exit 1 fi else - echo " No patch file found at patches/branding.patch — skipping." + echo " No patch file found at patches/0001-nuzzles.patch — skipping." fi # ── 5. Verify it builds ───────────────────────────────────────────────────────