initial commit
This commit is contained in:
commit
6f70daebca
4 changed files with 300 additions and 0 deletions
49
config.go
Normal file
49
config.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
func (c *Config) LoadConfig() error {
|
||||
f, err := os.Open("config.yaml")
|
||||
if err != nil {
|
||||
c.CreateConfig()
|
||||
return fmt.Errorf("config.yaml not found, a new one has been created. Please fill it out and restart the bot")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
decoder := yaml.NewDecoder(f)
|
||||
err = decoder.Decode(c)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Config) CreateConfig() error {
|
||||
f, err := os.Create("config.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
defaultConfig := Config{
|
||||
BotToken: "YOUR_BOT_TOKEN_HERE",
|
||||
AdminChatId: 0,
|
||||
TargetChatId: 0,
|
||||
EntryMessage: "You have requested to join the group, please write a brief message explaining why you want to join.",
|
||||
}
|
||||
|
||||
encoder := yaml.NewEncoder(f)
|
||||
err = encoder.Encode(defaultConfig)
|
||||
return err
|
||||
}
|
||||
8
go.mod
Normal file
8
go.mod
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module git.zio.sh/astra/telegram-approval-join
|
||||
|
||||
go 1.25.3
|
||||
|
||||
require (
|
||||
github.com/OvyFlash/telegram-bot-api v0.0.0-20251112155921-e82db5fd534b
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
)
|
||||
6
go.sum
Normal file
6
go.sum
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
github.com/OvyFlash/telegram-bot-api v0.0.0-20251112155921-e82db5fd534b h1:vC+cZNbleRsR1busnocKwnZ3Hm9Bp37QeWH81Dz91g8=
|
||||
github.com/OvyFlash/telegram-bot-api v0.0.0-20251112155921-e82db5fd534b/go.mod h1:2nRUdsKyWhvezqW/rBGWEQdcTQeTtnbSNd2dgx76WYA=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
237
main.go
Normal file
237
main.go
Normal 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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue