code refactor

This commit is contained in:
Astra 2026-02-17 20:05:06 +00:00
parent 7275b93666
commit d157f9b2c9
6 changed files with 187 additions and 193 deletions

View file

@ -19,12 +19,10 @@ func (bot *Bot) HandleAdminCommands(update *api.Update) {
}
_, e := utils.SendMessage(bot.API, bot.API.Self.ID, 0, update.Message.CommandArguments())
if e != nil {
if strings.HasPrefix(e.Error(), "Bad Request:") {
utils.SendMessage(bot.API, update.Message.Chat.ID, update.Message.MessageThreadID,
fmt.Sprintf("Unable to set entry message: <code>%s</code>", e))
return
}
if e != nil && strings.HasPrefix(e.Error(), "Bad Request:") {
utils.SendMessage(bot.API, update.Message.Chat.ID, update.Message.MessageThreadID,
fmt.Sprintf("Unable to set entry message: <code>%s</code>", e))
return
}
bot.Config.EntryMessage = update.Message.CommandArguments()
@ -57,23 +55,19 @@ func (bot *Bot) HandleAdminCommands(update *api.Update) {
update.Message.Chat.ID, topicID))
case "togglesendapproval":
if bot.Config.ApprovalMessage != "" {
switch bot.Config.SendApprovalMessage {
case true:
bot.Config.SendApprovalMessage = false
case false:
bot.Config.SendApprovalMessage = true
}
if err := bot.Config.SaveConfig(); err != nil {
log.Printf("Failed to save config: %v", err)
}
utils.SendMessage(bot.API, update.Message.Chat.ID, update.Message.MessageThreadID,
fmt.Sprintf("Send approval message: %v", bot.Config.SendApprovalMessage))
} else {
if bot.Config.ApprovalMessage == "" {
utils.SendMessage(bot.API, update.Message.Chat.ID, update.Message.MessageThreadID,
"Please set an approval message with <code>/setapprovalmessage</code>")
return
}
bot.Config.SendApprovalMessage = !bot.Config.SendApprovalMessage
if err := bot.Config.SaveConfig(); err != nil {
log.Printf("Failed to save config: %v", err)
}
utils.SendMessage(bot.API, update.Message.Chat.ID, update.Message.MessageThreadID,
fmt.Sprintf("Send approval message: %v", bot.Config.SendApprovalMessage))
case "setapprovalmessage":
if update.Message.CommandArguments() == "" {
utils.SendMessage(bot.API, update.Message.Chat.ID, update.Message.MessageThreadID,
@ -93,12 +87,14 @@ func (bot *Bot) HandleAdminCommands(update *api.Update) {
if *bot.Config.TargetChatId != 0 {
targetChatID = fmt.Sprintf("%d", *bot.Config.TargetChatId)
}
infoMsg := fmt.Sprintf("%s\n%s\n%s\n%s\n%s",
fmt.Sprintf("Admin Chat ID: <b>%d</b>", update.Message.Chat.ID),
fmt.Sprintf("Admin Topic ID: <b>%d</b>", *bot.Config.AdminChatTopicId),
fmt.Sprintf("Target Chat ID: <b>%s</b>", targetChatID),
fmt.Sprintf("Entry Message: %s", bot.Config.EntryMessage),
fmt.Sprintf("Approval Message: %s", bot.Config.ApprovalMessage))
utils.SendMessage(bot.API, update.Message.Chat.ID, update.Message.MessageThreadID, infoMsg)
utils.SendMessage(bot.API, update.Message.Chat.ID, update.Message.MessageThreadID,
fmt.Sprintf(
"Admin Chat ID: <b>%d</b>\nAdmin Topic ID: <b>%d</b>\nTarget Chat ID: <b>%s</b>\nEntry Message: %s\nApproval Message: %s",
update.Message.Chat.ID,
*bot.Config.AdminChatTopicId,
targetChatID,
bot.Config.EntryMessage,
bot.Config.ApprovalMessage,
))
}
}

View file

@ -12,37 +12,21 @@ import (
// HandleCallbackQuery processes inline button callbacks (approve/decline/leave).
func (bot *Bot) HandleCallbackQuery(query *api.CallbackQuery) {
data := strings.Join(strings.Split(query.Data, "_"), " ")
var action string
var args int64
_, err := fmt.Sscanf(data, "%s %d", &action, &args)
action, args, err := parseCallbackData(query.Data)
if err != nil {
log.Printf("Failed to parse callback data: %v", err)
return
}
if action == "leave" {
utils.LeaveChatRequest(bot.API, []int64{args})
utils.EditMessage(bot.API, query.Message.Chat.ID, query.Message.MessageID,
fmt.Sprintf("We have left chat <i>%d</i>", args))
callback := api.NewCallback(query.ID, "")
bot.API.Request(callback)
bot.handleLeaveAction(query, args)
return
}
user := bot.GetPendingUser(args)
if user == nil {
log.Printf("No pending request for user ID %d", args)
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)
callback := api.NewCallback(query.ID, "")
bot.API.Request(callback)
bot.handleMissingUser(query)
return
}
@ -56,98 +40,83 @@ func (bot *Bot) HandleCallbackQuery(query *api.CallbackQuery) {
bot.handleDeclineRequest(query, user, userString, adminUserString)
}
if bot.Config.DeleteRequestAfterDecision {
deleteTimer := time.NewTimer(10 * time.Second)
go func() {
defer deleteTimer.Stop()
<-deleteTimer.C
del := api.NewDeleteMessage(query.Message.Chat.ID, query.Message.MessageID)
bot.API.Send(del)
}()
}
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 approval callback.
// 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,
ChatConfig: api.ChatConfig{ChatID: user.ChatJoinRequest.Chat.ID},
UserID: user.ChatJoinRequest.From.ID,
}
_, e := bot.API.Request(r)
if e != nil {
log.Println(e.Error())
edit := api.NewEditMessageText(query.Message.Chat.ID, query.Message.MessageID, query.Message.Text)
edit.Entities = query.Message.Entities
bot.API.Send(edit)
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")))
time.Now().Format("2006-01-02 15:04:05"),
),
)
if bot.Config.SendApprovalMessage {
utils.SendMessage(bot.API, user.From.ID, 0, bot.Config.ApprovalMessage)
}
callback := api.NewCallback(query.ID, "Join request approved.")
bot.API.Request(callback)
bot.API.Request(api.NewCallback(query.ID, "Join request approved."))
}
// handleDeclineRequest declines a join request and sends decline callback.
// 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,
ChatConfig: api.ChatConfig{ChatID: user.ChatJoinRequest.Chat.ID},
UserID: user.ChatJoinRequest.From.ID,
}
_, e := bot.API.Request(r)
if e != nil {
log.Println(e.Error())
edit := api.NewEditMessageText(query.Message.Chat.ID, query.Message.MessageID, query.Message.Text)
edit.Entities = query.Message.Entities
bot.API.Send(edit)
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,
fmt.Sprintf(AdminDeclinedMsg,
userString, user.From.ID, user.JoinReason, adminUserString,
time.Now().Format("2006-01-02 15:04:05"),
"(no reason provided, reply to this to set one, prepend with + to also send to user)"),
defaultReason,
),
)
callback := api.NewCallback(query.ID, "Join request declined.")
bot.API.Request(callback)
bot.API.Request(api.NewCallback(query.ID, "Join request declined."))
}
// 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,
"(no reason provided, reply to this to set one, prepend with + to also send to user)") {
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
}
reason := utils.EscapeHTML(update.Message.Text)
userID, username, joinReason, declinedBy, declinedAt := utils.GetInfoFromMsg(repliedMsg.Text)
entities, _ := utils.FilterEntitiesByTypeWithContext(repliedMsg, "italic")
if len(entities) >= 1 {
if entities, _ := utils.FilterEntitiesByTypeWithContext(repliedMsg, "italic"); len(entities) >= 1 {
username = fmt.Sprintf("<i>%s</i>", entities[0].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,
@ -157,3 +126,49 @@ func (bot *Bot) HandleDeclineReason(update *api.Update) {
utils.EditMessage(bot.API, update.Message.Chat.ID, repliedMsg.MessageID,
fmt.Sprintf(AdminDeclinedMsg, username, userID, joinReason, declinedBy, declinedAt, reason))
}
// 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 <i>%d</i>", 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))
}

View file

@ -12,6 +12,7 @@ const (
AdminApprovedMsg = "✅ Join #request approved for %s [<code>%d</code>]\n\n<b>Join reason</b>: %s\n<b>Approved by</b>: %s\n<b>Approved at</b>: %s"
AdminDeclinedMsg = "❌ Join #request declined for %s [<code>%d</code>]\n\n<b>Join reason</b>: %s\n<b>Declined by</b>: %s\n<b>Declined at</b>: %s\n<b>Declined reason</b>: %s"
AdminFailedMsg = "⚠️ Join #request failed for %s [<code>%d</code>]\n\n<b>Join reason</b>: %s\n<b>Failure reason</b>: %s"
defaultReason = "(no reason provided, reply to this to set one, prepend with + to also send to user)"
)
// Types shared by handler files
@ -32,20 +33,13 @@ type Bot struct {
func (bot *Bot) GetPendingUser(userID int64) *ExtendedChatJoinRequest {
bot.mu.RLock()
defer bot.mu.RUnlock()
user := bot.WaitingForApproval[userID]
if user == nil {
return nil
}
return user
return bot.WaitingForApproval[userID]
}
// SetPendingUser stores a pending user request (write-safe).
func (bot *Bot) SetPendingUser(userID int64, user *ExtendedChatJoinRequest) {
bot.mu.Lock()
defer bot.mu.Unlock()
if _, ok := bot.WaitingForApproval[userID]; !ok {
bot.WaitingForApproval = make(map[int64]*ExtendedChatJoinRequest)
}
bot.WaitingForApproval[userID] = user
}

View file

@ -10,35 +10,23 @@ import (
// HandleJoinRequestResponse records the user's join reason and notifies admins.
func (bot *Bot) HandleJoinRequestResponse(user *ExtendedChatJoinRequest, update *api.Message) {
if user.JoinReason == "" {
user.JoinReason = utils.EscapeHTML(update.Text)
userString := utils.BuildUserString(&user.From)
keyboard := utils.NewApprovalKeyboard(user.From.ID)
utils.EditMessageWithKeyboard(bot.API, *bot.Config.AdminChatId, user.JoinRequestMessageID,
fmt.Sprintf(AdminJoinRequestMsg,
userString, user.From.ID, user.JoinReason), &keyboard)
utils.SendMessage(bot.API, update.From.ID, 0, "Thank you! Your request has been sent to the admins for review.")
} else {
if user.JoinReason != "" {
utils.SendMessage(bot.API, update.From.ID, 0, "Your request is already pending approval.")
}
}
// HandleJoinRequest initiates join approval flow by sending entry message and admin notification.
func (bot *Bot) HandleJoinRequest(request *api.ChatJoinRequest) {
// if chat is not in config, ignore
if *bot.Config.TargetChatId != request.Chat.ID {
m := api.NewMessage(*bot.Config.AdminChatId,
fmt.Sprintf("Received join request for chat %s (<code>%d</code>), but it's not in config, ignoring",
request.Chat.Title, request.Chat.ID))
leaveBtn := api.NewInlineKeyboardButtonData("Leave Chat", fmt.Sprintf("leave_%d", request.Chat.ID))
m.ReplyMarkup = api.NewInlineKeyboardMarkup([]api.InlineKeyboardButton{leaveBtn})
m.ParseMode = api.ModeHTML
bot.API.Send(m)
return
}
user.JoinReason = utils.EscapeHTML(update.Text)
userString := utils.BuildUserString(&user.From)
keyboard := utils.NewApprovalKeyboard(user.From.ID)
utils.EditMessageWithKeyboard(bot.API, *bot.Config.AdminChatId, user.JoinRequestMessageID,
fmt.Sprintf(AdminJoinRequestMsg, userString, user.From.ID, user.JoinReason), &keyboard)
utils.SendMessage(bot.API, update.From.ID, 0, "Thank you! Your request has been sent to the admins for review.")
}
// HandleJoinRequest initiates the join approval flow by sending the entry message and admin notification.
func (bot *Bot) HandleJoinRequest(request *api.ChatJoinRequest) {
utils.SendMessage(bot.API, request.From.ID, 0, bot.Config.EntryMessage)
userString := utils.BuildUserString(&request.From)
@ -49,6 +37,7 @@ func (bot *Bot) HandleJoinRequest(request *api.ChatJoinRequest) {
if topic := *bot.Config.AdminChatTopicId; topic != 0 {
m.MessageThreadID = topic
}
r, err := bot.API.Send(m)
if err != nil {
log.Printf("Failed to send join request to admin chat: %v", err)
@ -66,7 +55,5 @@ func (bot *Bot) HandleJoinRequest(request *api.ChatJoinRequest) {
func (bot *Bot) SendFailureMessage(user *ExtendedChatJoinRequest, query *api.CallbackQuery, userString string) {
utils.EditMessage(bot.API, *bot.Config.AdminChatId, user.JoinRequestMessageID,
fmt.Sprintf(AdminFailedMsg, userString, user.From.ID, utils.EscapeHTML(user.JoinReason), "User not found in requests."))
callback := api.NewCallback(query.ID, "Join request failed.")
bot.API.Request(callback)
bot.API.Request(api.NewCallback(query.ID, "Join request failed."))
}