package main import ( "fmt" "log" "strings" "time" api "github.com/OvyFlash/telegram-bot-api" ) type ExtendedChatJoinRequest struct { *api.ChatJoinRequest JoinReason string JoinRequestMessageID int } type Bot struct { API *api.BotAPI WaitingForApproval map[int64]*ExtendedChatJoinRequest Config Config } func main() { b := &Bot{} b.Config = Config{} err := b.Config.LoadConfig() if err != nil { log.Fatal(err.Error()) } bot, err := api.NewBotAPI(b.Config.BotToken) if err != nil { panic(err) } b.API = bot b.WaitingForApproval = make(map[int64]*ExtendedChatJoinRequest) log.Printf("Authorized on account %s", bot.Self.UserName) updateConfig := api.NewUpdate(0) updateConfig.Timeout = 60 updatesChannel := b.API.GetUpdatesChan(updateConfig) for update := range updatesChannel { if update.ChatJoinRequest != nil { b.handleJoinRequest(update.ChatJoinRequest) continue } if update.CallbackQuery != nil { b.handleCallbackQuery(update.CallbackQuery) continue } if update.Message == nil { continue } if user, ok := b.WaitingForApproval[update.Message.From.ID]; ok { if update.Message.Chat.ID == update.Message.From.ID { b.handleJoinRequestResponse(user, update.Message) } } } } func (bot *Bot) handleJoinRequestResponse(user *ExtendedChatJoinRequest, update *api.Message) { if user.JoinReason == "" { user.JoinReason = escapeMarkdown(update.Text) edit := api.NewEditMessageText(bot.Config.AdminChatId, user.JoinRequestMessageID, fmt.Sprintf("New join request from _%s_\n\nJoin reason: %s", user.From.String(), user.JoinReason)) approveButton := api.NewInlineKeyboardButtonData("✅ Approve", fmt.Sprintf("approve_%d", user.From.ID)) rejectButton := api.NewInlineKeyboardButtonData("❌ Reject", fmt.Sprintf("reject_%d", user.From.ID)) keyboard := api.NewInlineKeyboardMarkup( []api.InlineKeyboardButton{approveButton, rejectButton}, ) edit.ReplyMarkup = &keyboard edit.ParseMode = api.ModeMarkdown bot.API.Send(edit) ack := api.NewMessage(update.From.ID, "Thank you! Your request has been sent to the admins for review.") bot.API.Send(ack) } else { // if user.Date+(60*60*5) < int(time.Now().Unix()) { // } else { m := api.NewMessage(update.From.ID, "Your request is already pending approval.") bot.API.Send(m) // } } } func (bot *Bot) handleJoinRequest(request *api.ChatJoinRequest) { if bot.Config.TargetChatId == request.Chat.ID { bot.WaitingForApproval[request.From.ID] = &ExtendedChatJoinRequest{ ChatJoinRequest: request, JoinReason: "", } m := api.NewMessage(request.From.ID, bot.Config.EntryMessage) m.ParseMode = api.ModeMarkdown bot.API.Send(m) m = api.NewMessage(bot.Config.AdminChatId, fmt.Sprintf("New join request from _%s_\n\nJoin reason: (no reason provided yet)", request.From.String())) approveButton := api.NewInlineKeyboardButtonData("✅ Approve", fmt.Sprintf("approve_%d", request.From.ID)) rejectButton := api.NewInlineKeyboardButtonData("❌ Reject", fmt.Sprintf("reject_%d", request.From.ID)) keyboard := api.NewInlineKeyboardMarkup( []api.InlineKeyboardButton{approveButton, rejectButton}, ) m.ReplyMarkup = keyboard m.ParseMode = api.ModeMarkdown if bot.Config.AdminChatTopicId != 0 { m.MessageThreadID = bot.Config.AdminChatTopicId } r, _ := bot.API.Send(m) bot.WaitingForApproval[request.From.ID].JoinRequestMessageID = r.MessageID } } func (bot *Bot) handleCallbackQuery(query *api.CallbackQuery) { data := strings.Join(strings.Split(query.Data, "_"), " ") var userId int64 var action string _, err := fmt.Sscanf(data, "%s %d", &action, &userId) if err != nil { log.Printf("Failed to parse callback data: %v", err) return } // handle callbacks from admin chat if query.Message.Chat.ID == bot.Config.AdminChatId { user, exists := bot.WaitingForApproval[userId] if !exists { log.Printf("No pending request for user ID %d", userId) return } switch action { case "approve": r := api.ApproveChatJoinRequestConfig{ ChatConfig: api.ChatConfig{ ChatID: user.ChatJoinRequest.Chat.ID, }, UserID: user.ChatJoinRequest.From.ID, } bot.API.Send(r) edit := api.NewEditMessageText(bot.Config.AdminChatId, user.JoinRequestMessageID, fmt.Sprintf("✅ Join #request approved for _%s_\n\nJoin reason: %s\nApproved by: %s\nApproved at: %s", user.From.String(), user.JoinReason, query.From.String(), time.Now().Format("2006-01-02 15:04:05"))) edit.ParseMode = api.ModeMarkdown bot.API.Send(edit) m := api.NewMessage(user.From.ID, bot.Config.ApprovalMessage) m.ParseMode = api.ModeMarkdown bot.API.Send(m) case "reject": r := api.DeclineChatJoinRequest{ ChatConfig: api.ChatConfig{ ChatID: user.ChatJoinRequest.Chat.ID, }, UserID: user.ChatJoinRequest.From.ID, } bot.API.Send(r) edit := api.NewEditMessageText(bot.Config.AdminChatId, user.JoinRequestMessageID, fmt.Sprintf("❌ Join #request rejected for _%s_\n\nJoin reason: %s\nRejected by: %s\nRejected at: %s", user.From.String(), user.JoinReason, query.From.String(), time.Now().Format("2006-01-02 15:04:05"))) edit.ParseMode = api.ModeMarkdown bot.API.Send(edit) } delete(bot.WaitingForApproval, userId) } } func escapeMarkdown(s string) string { toEscape := []string{"*", "_", "`", "[", "]", "(", ")", "\\", "#", "-"} replacements := make([]string, 0, len(toEscape)*2) for _, char := range toEscape { replacements = append(replacements, char, "\\"+char) } replacer := strings.NewReplacer(replacements...) return replacer.Replace(s) }