package handlers import ( "fmt" "log" "strings" "time" utils "git.zio.sh/astra/telegram-approval-join/pkg/utils" api "github.com/OvyFlash/telegram-bot-api" ) // HandleCallbackQuery processes inline button callbacks (approve/decline/ban/leave). func (bot *Bot) HandleCallbackQuery(query *api.CallbackQuery) { action, args, err := parseCallbackData(query.Data) if err != nil { log.Printf("Failed to parse callback data: %v", err) return } if action == "leave" { bot.handleLeaveAction(query, args) return } user := bot.GetPendingUser(args) if user == nil { log.Printf("No pending request for user ID %d", args) bot.handleMissingUser(query) return } userString := utils.BuildUserString(&user.From) adminUserString := utils.BuildUserString(query.From) switch action { case "approve": bot.handleApproveRequest(query, user, userString, adminUserString) case "decline": bot.handleDeclineRequest(query, user, userString, adminUserString) case "ban": bot.showBanConfirmation(query, user) bot.API.Request(api.NewCallback(query.ID, "")) return case "banc": bot.handleBanRequest(query, user, userString, adminUserString) } bot.DeletePendingUser(args) if bot.Config.DeleteRequestAfterDecision { go bot.scheduleMessageDeletion(query.Message.Chat.ID, query.Message.MessageID, 10*time.Second) } } // handleApproveRequest approves a join request and sends an approval callback. func (bot *Bot) handleApproveRequest(query *api.CallbackQuery, user *ExtendedChatJoinRequest, userString, adminUserString string) { r := api.ApproveChatJoinRequestConfig{ ChatConfig: api.ChatConfig{ChatID: user.ChatJoinRequest.Chat.ID}, UserID: user.ChatJoinRequest.From.ID, } if _, err := bot.API.Request(r); err != nil { log.Println(err) bot.restoreMessage(query) return } utils.EditMessage(bot.API, query.Message.Chat.ID, query.Message.MessageID, fmt.Sprintf(AdminApprovedMsg, userString, user.From.ID, user.JoinReason, adminUserString, time.Now().Format("2006-01-02 15:04:05"), ), ) if bot.Config.SendApprovalMessage { utils.SendMessage(bot.API, user.From.ID, 0, bot.Config.ApprovalMessage) } bot.API.Request(api.NewCallback(query.ID, "Join request approved.")) } // handleDeclineRequest declines a join request and sends a decline callback. func (bot *Bot) handleDeclineRequest(query *api.CallbackQuery, user *ExtendedChatJoinRequest, userString, adminUserString string) { r := api.DeclineChatJoinRequest{ ChatConfig: api.ChatConfig{ChatID: user.ChatJoinRequest.Chat.ID}, UserID: user.ChatJoinRequest.From.ID, } if _, err := bot.API.Request(r); err != nil { log.Println(err) bot.restoreMessage(query) 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, ), ) bot.API.Request(api.NewCallback(query.ID, "Join request declined.")) } // handleBanRequest bans a user from the chat for 24 hours. func (bot *Bot) handleBanRequest(query *api.CallbackQuery, user *ExtendedChatJoinRequest, userString, adminUserString string) { r := api.BanChatMemberConfig{ ChatMemberConfig: api.ChatMemberConfig{ ChatConfig: api.ChatConfig{ChatID: user.ChatJoinRequest.Chat.ID}, UserID: user.ChatJoinRequest.From.ID, }, UntilDate: time.Now().Add(24 * time.Hour).Unix(), } if _, err := bot.API.Request(r); err != nil { log.Println(err) bot.restoreMessage(query) return } bannedUntil := time.Now().Add(24 * time.Hour).Format("2006-01-02 15:04:05") utils.EditMessage(bot.API, query.Message.Chat.ID, query.Message.MessageID, fmt.Sprintf(AdminBannedMsg, userString, user.From.ID, user.JoinReason, adminUserString, time.Now().Format("2006-01-02 15:04:05"), bannedUntil, ), ) bot.API.Request(api.NewCallback(query.ID, "User banned.")) } // showBanConfirmation displays a confirmation prompt for the ban action. func (bot *Bot) showBanConfirmation(query *api.CallbackQuery, user *ExtendedChatJoinRequest) { approveBtn := api.NewInlineKeyboardButtonData("Approve", fmt.Sprintf("approve_%d", user.From.ID)) declineBtn := api.NewInlineKeyboardButtonData("Decline", fmt.Sprintf("decline_%d", user.From.ID)) confirmBtn := api.NewInlineKeyboardButtonData("Ban (confirm)", fmt.Sprintf("banc_%d", user.From.ID)) keyboard := api.NewInlineKeyboardMarkup( []api.InlineKeyboardButton{approveBtn, declineBtn}, []api.InlineKeyboardButton{confirmBtn}, ) edit := api.NewEditMessageText(query.Message.Chat.ID, query.Message.MessageID, query.Message.Text) edit.ReplyMarkup = &keyboard edit.ParseMode = api.ModeHTML edit.Entities = query.Message.Entities bot.API.Send(edit) } // HandleDeclineReason allows admins to provide a decline reason by replying to decline messages. func (bot *Bot) HandleDeclineReason(update *api.Update) { repliedMsg := update.Message.ReplyToMessage if !strings.Contains(repliedMsg.Text, defaultReason) { return } lines := strings.Split(repliedMsg.Text, "\n") userString := utils.BuildUserString(update.Message.From) if strings.TrimPrefix(lines[3], "Declined by: ") != userString { return } 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:]) utils.SendMessage(bot.API, userID, 0, fmt.Sprintf("Your join request was declined for the following reason:\n\n%s", reason)) } utils.EditMessage(bot.API, update.Message.Chat.ID, repliedMsg.MessageID, fmt.Sprintf(AdminDeclinedMsg, username, userID, joinReason, declinedBy, declinedAt, reason)) // Delete the admin's message after processing bot.API.Send(api.NewDeleteMessage(update.Message.Chat.ID, update.Message.MessageID)) } // 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, "_"), " ") _, err = fmt.Sscanf(normalized, "%s %d", &action, &userID) return } // handleLeaveAction handles the "leave" callback by leaving the specified chat. func (bot *Bot) handleLeaveAction(query *api.CallbackQuery, chatID int64) { utils.LeaveChatRequest(bot.API, []int64{chatID}) utils.EditMessage(bot.API, query.Message.Chat.ID, query.Message.MessageID, fmt.Sprintf("We have left chat %d", chatID)) bot.API.Request(api.NewCallback(query.ID, "")) } // handleMissingUser notifies the admin that the user's pending request could not be found. func (bot *Bot) handleMissingUser(query *api.CallbackQuery) { msg := api.NewMessage(query.Message.Chat.ID, "Unable to find user, bot may have restarted") msg.ReplyParameters = api.ReplyParameters{ MessageID: query.Message.MessageID, ChatID: query.Message.Chat.ID, } r, _ := bot.API.Send(msg) edit := api.NewEditMessageText(query.Message.Chat.ID, r.ReplyToMessage.MessageID, r.ReplyToMessage.Text) edit.Entities = r.ReplyToMessage.Entities bot.API.Send(edit) bot.API.Request(api.NewCallback(query.ID, "")) } // restoreMessage restores a message to its original state after a failed API request. func (bot *Bot) restoreMessage(query *api.CallbackQuery) { edit := api.NewEditMessageText(query.Message.Chat.ID, query.Message.MessageID, query.Message.Text) edit.Entities = query.Message.Entities bot.API.Send(edit) } // scheduleMessageDeletion deletes a message after a given delay. func (bot *Bot) scheduleMessageDeletion(chatID int64, messageID int, delay time.Duration) { timer := time.NewTimer(delay) defer timer.Stop() <-timer.C bot.API.Send(api.NewDeleteMessage(chatID, messageID)) }