initial commit

This commit is contained in:
Astra 2026-01-28 15:56:14 +00:00
commit 6f70daebca
4 changed files with 300 additions and 0 deletions

237
main.go Normal file
View file

@ -0,0 +1,237 @@
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)
var updatesChannelMock chan api.Update = make(chan api.Update, 1)
mockUpdates := []api.Update{
{
ChatJoinRequest: &api.ChatJoinRequest{
Chat: api.Chat{
ID: -1001895847484,
Type: "supergroup",
Title: "Example Group",
},
From: api.User{
ID: 55258520,
FirstName: "Astra",
LastName: "Doe",
UserName: "asstra",
},
Date: int(time.Now().Unix()),
UserChatID: 55258520,
},
},
{
Message: &api.Message{
MessageID: 1,
From: &api.User{
ID: 55258520,
FirstName: "Astra",
LastName: "Doe",
UserName: "asstra",
},
Chat: api.Chat{
ID: 55258520,
Type: "private",
},
Date: int(time.Now().Unix()),
Text: "I would like to join because *I* <b>test</b> love this group!",
},
},
}
go func() {
for _, update := range mockUpdates {
updatesChannelMock <- update
time.Sleep(2 * time.Second) // delay between updates
}
close(updatesChannelMock)
}()
for update := range updatesChannel { //Mock {
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)
}