Code refactor
This commit is contained in:
parent
5e37545782
commit
d2f7297d92
7 changed files with 534 additions and 330 deletions
52
config.go
52
config.go
|
|
@ -1,52 +0,0 @@
|
||||||
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"`
|
|
||||||
DeleteRequestAfterDecision bool `yaml:"delete_request_after_decision"`
|
|
||||||
}
|
|
||||||
|
|
||||||
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.",
|
|
||||||
ApprovalMessage: "",
|
|
||||||
DeleteRequestAfterDecision: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
encoder := yaml.NewEncoder(f)
|
|
||||||
err = encoder.Encode(defaultConfig)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
62
config/config.go
Normal file
62
config/config.go
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
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"`
|
||||||
|
DeleteRequestAfterDecision bool `yaml:"delete_request_after_decision"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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: StringPtr("YOUR_BOT_TOKEN_HERE"),
|
||||||
|
AdminChatId: Int64Ptr(0),
|
||||||
|
AdminChatTopicId: IntPtr(0),
|
||||||
|
TargetChatId: Int64Ptr(0),
|
||||||
|
EntryMessage: "You have requested to join the group, please write a brief message explaining why you want to join.",
|
||||||
|
ApprovalMessage: "",
|
||||||
|
DeleteRequestAfterDecision: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := yaml.NewEncoder(f)
|
||||||
|
err = encoder.Encode(defaultConfig)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringPtr returns a pointer to the given string.
|
||||||
|
func StringPtr(s string) *string { return &s }
|
||||||
|
|
||||||
|
// Int64Ptr returns a pointer to the given int64.
|
||||||
|
func Int64Ptr(i int64) *int64 { return &i }
|
||||||
|
|
||||||
|
// IntPtr returns a pointer to the given int.
|
||||||
|
func IntPtr(i int) *int { return &i }
|
||||||
159
handlers/callbacks.go
Normal file
159
handlers/callbacks.go
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
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/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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleApproveRequest approves a join request and sends 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,
|
||||||
|
}
|
||||||
|
_, 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)
|
||||||
|
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.ApprovalMessage != "" {
|
||||||
|
utils.SendMessage(bot.API, user.From.ID, 0, bot.Config.ApprovalMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
callback := api.NewCallback(query.ID, "Join request approved.")
|
||||||
|
bot.API.Request(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleDeclineRequest declines a join request and sends 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,
|
||||||
|
}
|
||||||
|
_, 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)
|
||||||
|
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"),
|
||||||
|
"(no reason provided, reply to this to set one, prepend with + to also send to user)"),
|
||||||
|
)
|
||||||
|
|
||||||
|
callback := api.NewCallback(query.ID, "Join request declined.")
|
||||||
|
bot.API.Request(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)") {
|
||||||
|
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 {
|
||||||
|
username = fmt.Sprintf("<i>%s</i>", entities[0].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))
|
||||||
|
}
|
||||||
60
handlers/handlers.go
Normal file
60
handlers/handlers.go
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
config "git.zio.sh/astra/telegram-approval-join/config"
|
||||||
|
api "github.com/OvyFlash/telegram-bot-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AdminJoinRequestMsg = "New join #request from %s [<code>%d</code>]\n\n<b>Join reason</b>: %s"
|
||||||
|
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"
|
||||||
|
BotApprovalEnabled = "Join approval bot enabled, to get started, add the bot to the target group and make it an admin with invite permissions, then use <code>/targetchat <chat_id></code> to add the group to the config.\n\nTo set the entry message that users will receive when they request to join, use <code>/setentrymessage <entry_message></code> in the admin chat. You can use HTML formatting in the entry message."
|
||||||
|
BotAddedToGroup = "Hello! I help out with join approvals.\n\nTo get started, make sure this bot is added to the admin group where it will send join approvals to be accepted or declined, then type /request when you have done that."
|
||||||
|
BotRequestMsg = "You have requested this chat to be the admin chat, please wait for it to be activated."
|
||||||
|
)
|
||||||
|
|
||||||
|
// Types shared by handler files
|
||||||
|
type ExtendedChatJoinRequest struct {
|
||||||
|
*api.ChatJoinRequest
|
||||||
|
JoinReason string
|
||||||
|
JoinRequestMessageID int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bot struct {
|
||||||
|
API *api.BotAPI
|
||||||
|
Config config.Config
|
||||||
|
mu sync.RWMutex
|
||||||
|
WaitingForApproval map[int64]*ExtendedChatJoinRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPendingUser retrieves a pending user request (read-safe).
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePendingUser removes a pending user request (write-safe).
|
||||||
|
func (bot *Bot) DeletePendingUser(userID int64) {
|
||||||
|
bot.mu.Lock()
|
||||||
|
defer bot.mu.Unlock()
|
||||||
|
delete(bot.WaitingForApproval, userID)
|
||||||
|
}
|
||||||
72
handlers/join.go
Normal file
72
handlers/join.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
utils "git.zio.sh/astra/telegram-approval-join/pkg/utils"
|
||||||
|
api "github.com/OvyFlash/telegram-bot-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendMessage(bot.API, request.From.ID, 0, bot.Config.EntryMessage)
|
||||||
|
userString := utils.BuildUserString(&request.From)
|
||||||
|
|
||||||
|
m := api.NewMessage(*bot.Config.AdminChatId,
|
||||||
|
fmt.Sprintf(AdminJoinRequestMsg, userString, request.From.ID, "(awaiting user response)"))
|
||||||
|
m.ReplyMarkup = utils.NewApprovalKeyboard(request.From.ID)
|
||||||
|
m.ParseMode = api.ModeHTML
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.SetPendingUser(request.From.ID, &ExtendedChatJoinRequest{
|
||||||
|
ChatJoinRequest: request,
|
||||||
|
JoinReason: "",
|
||||||
|
JoinRequestMessageID: r.MessageID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendFailureMessage edits the admin request message to show a failure status.
|
||||||
|
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)
|
||||||
|
}
|
||||||
293
main.go
293
main.go
|
|
@ -1,49 +1,31 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"git.zio.sh/astra/telegram-approval-join/config"
|
||||||
|
"git.zio.sh/astra/telegram-approval-join/handlers"
|
||||||
api "github.com/OvyFlash/telegram-bot-api"
|
api "github.com/OvyFlash/telegram-bot-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
AdminJoinRequestMsg = "New join #request from %s [<code>%d</code>]\n\n<b>Join reason</b>: %s"
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExtendedChatJoinRequest struct {
|
|
||||||
*api.ChatJoinRequest
|
|
||||||
JoinReason string
|
|
||||||
JoinRequestMessageID int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bot struct {
|
|
||||||
API *api.BotAPI
|
|
||||||
WaitingForApproval map[int64]*ExtendedChatJoinRequest
|
|
||||||
Config Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
b := &Bot{}
|
b := &handlers.Bot{}
|
||||||
b.Config = Config{}
|
b.Config = config.Config{}
|
||||||
err := b.Config.LoadConfig()
|
err := b.Config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
bot, err := api.NewBotAPI(b.Config.BotToken)
|
if b.Config.BotToken == nil {
|
||||||
|
log.Fatal("Edit config.yaml and fill out bot token")
|
||||||
|
}
|
||||||
|
bot, err := api.NewBotAPI(*b.Config.BotToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.API = bot
|
b.API = bot
|
||||||
b.WaitingForApproval = make(map[int64]*ExtendedChatJoinRequest)
|
b.WaitingForApproval = make(map[int64]*handlers.ExtendedChatJoinRequest)
|
||||||
|
|
||||||
log.Printf("Authorized on account %s", bot.Self.UserName)
|
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||||
|
|
||||||
|
|
@ -53,14 +35,14 @@ func main() {
|
||||||
|
|
||||||
for update := range updatesChannel {
|
for update := range updatesChannel {
|
||||||
if update.ChatJoinRequest != nil {
|
if update.ChatJoinRequest != nil {
|
||||||
if update.ChatJoinRequest.Chat.ID == b.Config.TargetChatId {
|
if update.ChatJoinRequest.Chat.ID == *b.Config.TargetChatId {
|
||||||
b.handleJoinRequest(update.ChatJoinRequest)
|
b.HandleJoinRequest(update.ChatJoinRequest)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if update.CallbackQuery != nil {
|
if update.CallbackQuery != nil {
|
||||||
b.handleCallbackQuery(update.CallbackQuery)
|
b.HandleCallbackQuery(update.CallbackQuery)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,257 +52,12 @@ func main() {
|
||||||
|
|
||||||
if user, ok := b.WaitingForApproval[update.Message.From.ID]; ok {
|
if user, ok := b.WaitingForApproval[update.Message.From.ID]; ok {
|
||||||
if update.Message.Chat.ID == update.Message.From.ID {
|
if update.Message.Chat.ID == update.Message.From.ID {
|
||||||
b.handleJoinRequestResponse(user, update.Message)
|
b.HandleJoinRequestResponse(user, update.Message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if update.Message.Chat.ID == b.Config.AdminChatId && update.Message.ReplyToMessage != nil {
|
if update.Message.Chat.ID == *b.Config.AdminChatId && update.Message.ReplyToMessage != nil {
|
||||||
// handle admin declineion reason
|
b.HandleDeclineReason(&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)") {
|
|
||||||
// now we need to make sure the one that declined it is the one replying
|
|
||||||
lines := strings.Split(repliedMsg.Text, "\n")
|
|
||||||
userString := ""
|
|
||||||
if update.Message.From.UserName != "" {
|
|
||||||
userString = "@" + update.Message.From.UserName
|
|
||||||
} else {
|
|
||||||
userString = fmt.Sprintf("%s %s", update.Message.From.FirstName, update.Message.From.LastName)
|
|
||||||
}
|
|
||||||
if strings.TrimPrefix(lines[3], "Declined by: ") == userString {
|
|
||||||
reason := EscapeHTML(update.Message.Text)
|
|
||||||
userID, username, joinReason, declinedBy, declinedAt := GetInfoFromMsg(repliedMsg.Text)
|
|
||||||
if strings.HasPrefix(update.Message.Text, "+") {
|
|
||||||
reason = EscapeHTML(update.Message.Text[1:])
|
|
||||||
m := api.NewMessage(userID, fmt.Sprintf("Your join request was declined for the following reason:\n\n%s",
|
|
||||||
reason))
|
|
||||||
b.API.Send(m)
|
|
||||||
}
|
|
||||||
edit := api.NewEditMessageText(b.Config.AdminChatId, repliedMsg.MessageID,
|
|
||||||
fmt.Sprintf(AdminDeclinedMsg,
|
|
||||||
username, userID, joinReason, declinedBy, declinedAt, reason))
|
|
||||||
edit.ParseMode = api.ModeHTML
|
|
||||||
b.API.Send(edit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *Bot) handleJoinRequestResponse(user *ExtendedChatJoinRequest, update *api.Message) {
|
|
||||||
if user.JoinReason == "" {
|
|
||||||
user.JoinReason = EscapeHTML(update.Text)
|
|
||||||
|
|
||||||
userString := ""
|
|
||||||
if user.From.UserName != "" {
|
|
||||||
userString = "@" + user.From.UserName
|
|
||||||
} else {
|
|
||||||
userString = fmt.Sprintf("%s %s", user.From.FirstName, user.From.LastName)
|
|
||||||
}
|
|
||||||
edit := api.NewEditMessageText(bot.Config.AdminChatId, user.JoinRequestMessageID,
|
|
||||||
fmt.Sprintf(AdminJoinRequestMsg,
|
|
||||||
userString, user.From.ID, user.JoinReason))
|
|
||||||
keyboard := NewApprovalKeyboard(user.From.ID)
|
|
||||||
edit.ReplyMarkup = &keyboard
|
|
||||||
edit.ParseMode = api.ModeHTML
|
|
||||||
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 {
|
|
||||||
m := api.NewMessage(update.From.ID, "Your request is already pending approval.")
|
|
||||||
bot.API.Send(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) handleJoinRequest(request *api.ChatJoinRequest) {
|
|
||||||
bot.WaitingForApproval[request.From.ID] = &ExtendedChatJoinRequest{
|
|
||||||
ChatJoinRequest: request,
|
|
||||||
JoinReason: "",
|
|
||||||
}
|
|
||||||
m := api.NewMessage(request.From.ID, bot.Config.EntryMessage)
|
|
||||||
m.ParseMode = api.ModeHTML
|
|
||||||
bot.API.Send(m)
|
|
||||||
|
|
||||||
userString := ""
|
|
||||||
if request.From.UserName != "" {
|
|
||||||
userString = "@" + request.From.UserName
|
|
||||||
} else {
|
|
||||||
userString = fmt.Sprintf("%s %s", request.From.FirstName, request.From.LastName)
|
|
||||||
}
|
|
||||||
m = api.NewMessage(bot.Config.AdminChatId,
|
|
||||||
fmt.Sprintf(AdminJoinRequestMsg, userString, request.From.ID, "(awaiting user response)"))
|
|
||||||
m.ReplyMarkup = NewApprovalKeyboard(request.From.ID)
|
|
||||||
m.ParseMode = api.ModeHTML
|
|
||||||
if bot.Config.AdminChatTopicId != 0 {
|
|
||||||
m.MessageThreadID = bot.Config.AdminChatTopicId
|
|
||||||
}
|
|
||||||
r, err := bot.API.Send(m)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to send join request to admin chat: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
userString := ""
|
|
||||||
if user.From.UserName != "" {
|
|
||||||
userString = "@" + user.From.UserName
|
|
||||||
} else {
|
|
||||||
userString = fmt.Sprintf("%s %s (no username)", user.From.FirstName, user.From.LastName)
|
|
||||||
}
|
|
||||||
|
|
||||||
adminUserString := ""
|
|
||||||
if query.From.UserName != "" {
|
|
||||||
adminUserString = "@" + query.From.UserName
|
|
||||||
} else {
|
|
||||||
adminUserString = fmt.Sprintf("%s %s", query.From.FirstName, query.From.LastName)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case "approve":
|
|
||||||
r := api.ApproveChatJoinRequestConfig{
|
|
||||||
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())
|
|
||||||
bot.sendFailureMessage(user, query, userString)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
edit := api.NewEditMessageText(bot.Config.AdminChatId, user.JoinRequestMessageID,
|
|
||||||
fmt.Sprintf(AdminApprovedMsg,
|
|
||||||
userString, user.From.ID, user.JoinReason, adminUserString, time.Now().Format("2006-01-02 15:04:05")))
|
|
||||||
edit.ParseMode = api.ModeHTML
|
|
||||||
bot.API.Send(edit)
|
|
||||||
|
|
||||||
if bot.Config.ApprovalMessage != "" {
|
|
||||||
m := api.NewMessage(user.From.ID, bot.Config.ApprovalMessage)
|
|
||||||
m.ParseMode = api.ModeHTML
|
|
||||||
bot.API.Send(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
callback := api.NewCallback(query.ID, "Join request approved.")
|
|
||||||
bot.API.Request(callback)
|
|
||||||
|
|
||||||
case "decline":
|
|
||||||
r := api.DeclineChatJoinRequest{
|
|
||||||
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())
|
|
||||||
bot.sendFailureMessage(user, query, userString)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
edit := api.NewEditMessageText(bot.Config.AdminChatId, user.JoinRequestMessageID,
|
|
||||||
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)"))
|
|
||||||
edit.ParseMode = api.ModeHTML
|
|
||||||
bot.API.Send(edit)
|
|
||||||
|
|
||||||
callback := api.NewCallback(query.ID, "Join request declined.")
|
|
||||||
bot.API.Request(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bot.Config.DeleteRequestAfterDecision {
|
|
||||||
deleteTimer := time.NewTimer(10 * time.Second)
|
|
||||||
go func() {
|
|
||||||
<-deleteTimer.C
|
|
||||||
|
|
||||||
del := api.NewDeleteMessage(bot.Config.AdminChatId, user.JoinRequestMessageID)
|
|
||||||
bot.API.Send(del)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(bot.WaitingForApproval, userId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) sendFailureMessage(user *ExtendedChatJoinRequest, query *api.CallbackQuery, userString string) {
|
|
||||||
edit := api.NewEditMessageText(bot.Config.AdminChatId, user.JoinRequestMessageID,
|
|
||||||
fmt.Sprintf(AdminFailedMsg, userString, user.From.ID, user.JoinReason, "User not found in requests."))
|
|
||||||
edit.ParseMode = api.ModeHTML
|
|
||||||
bot.API.Send(edit)
|
|
||||||
|
|
||||||
callback := api.NewCallback(query.ID, "Join request failed.")
|
|
||||||
bot.API.Request(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func EscapeHTML(s string) string {
|
|
||||||
toEscape := []string{"&", "<", ">", "\"", "'"}
|
|
||||||
|
|
||||||
replacements := make([]string, 0, len(toEscape)*2)
|
|
||||||
replacements = append(replacements, "&", "&")
|
|
||||||
replacements = append(replacements, "<", "<")
|
|
||||||
replacements = append(replacements, ">", ">")
|
|
||||||
replacements = append(replacements, "\"", """)
|
|
||||||
|
|
||||||
replacer := strings.NewReplacer(replacements...)
|
|
||||||
return replacer.Replace(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewApprovalKeyboard(userID int64) api.InlineKeyboardMarkup {
|
|
||||||
approveBtn := api.NewInlineKeyboardButtonData("Approve", fmt.Sprintf("approve_%d", userID))
|
|
||||||
declineBtn := api.NewInlineKeyboardButtonData("Decline", fmt.Sprintf("decline_%d", userID))
|
|
||||||
return api.NewInlineKeyboardMarkup([]api.InlineKeyboardButton{approveBtn, declineBtn})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetInfoFromMsg(msg string) (userId int64, username, joinReason, declinedBy, declinedAt string) {
|
|
||||||
lines := strings.Split(msg, "\n")
|
|
||||||
|
|
||||||
joinReason = string([]rune(lines[2])[len([]rune("Join reason: ")):])
|
|
||||||
declinedBy = string([]rune(lines[3])[len([]rune("Declined by: ")):])
|
|
||||||
declinedAt = string([]rune(lines[4])[len([]rune("Declined at: ")):])
|
|
||||||
index := LastIndexRuneInRunes([]rune(lines[0]), '[')
|
|
||||||
userID, _ := strconv.Atoi(string([]rune(lines[0])[index+1 : len([]rune(lines[0]))-1]))
|
|
||||||
|
|
||||||
return int64(userID), username, joinReason, declinedBy, declinedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastIndexRuneInRunes(runes []rune, r rune) int {
|
|
||||||
for i := len(runes) - 1; i >= 0; i-- {
|
|
||||||
if runes[i] == r {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
|
||||||
166
pkg/utils/utils.go
Normal file
166
pkg/utils/utils.go
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf16"
|
||||||
|
|
||||||
|
api "github.com/OvyFlash/telegram-bot-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EscapeHTML(s string) string {
|
||||||
|
toEscape := []string{"&", "<", ">", "\"", "'"}
|
||||||
|
|
||||||
|
replacements := make([]string, 0, len(toEscape)*2)
|
||||||
|
replacements = append(replacements, "&", "&")
|
||||||
|
replacements = append(replacements, "<", "<")
|
||||||
|
replacements = append(replacements, ">", ">")
|
||||||
|
replacements = append(replacements, "\"", """)
|
||||||
|
|
||||||
|
replacer := strings.NewReplacer(replacements...)
|
||||||
|
return replacer.Replace(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApprovalKeyboard(userID int64) api.InlineKeyboardMarkup {
|
||||||
|
approveBtn := api.NewInlineKeyboardButtonData("Approve", fmt.Sprintf("approve_%d", userID))
|
||||||
|
declineBtn := api.NewInlineKeyboardButtonData("Decline", fmt.Sprintf("decline_%d", userID))
|
||||||
|
return api.NewInlineKeyboardMarkup([]api.InlineKeyboardButton{approveBtn, declineBtn})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetInfoFromMsg(msg string) (userId int64, username, joinReason, declinedBy, declinedAt string) {
|
||||||
|
lines := strings.Split(msg, "\n")
|
||||||
|
|
||||||
|
joinReason = string([]rune(lines[2])[len([]rune("Join reason: ")):])
|
||||||
|
declinedBy = string([]rune(lines[3])[len([]rune("Declined by: ")):])
|
||||||
|
declinedAt = string([]rune(lines[4])[len([]rune("Declined at: ")):])
|
||||||
|
index := LastIndexRuneInRunes([]rune(lines[0]), '[')
|
||||||
|
userID, _ := strconv.Atoi(string([]rune(lines[0])[index+1 : len([]rune(lines[0]))-1]))
|
||||||
|
|
||||||
|
return int64(userID), EscapeHTML(username), EscapeHTML(joinReason), declinedBy, declinedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastIndexRuneInRunes(runes []rune, r rune) int {
|
||||||
|
for i := len(runes) - 1; i >= 0; i-- {
|
||||||
|
if runes[i] == r {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityWithText struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
Length int `json:"length"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterEntitiesByTypeWithContext(payload *api.Message, entityType string) ([]EntityWithText, error) {
|
||||||
|
textRunes := utf16.Encode([]rune(payload.Text))
|
||||||
|
var filtered []EntityWithText
|
||||||
|
for _, entity := range payload.Entities {
|
||||||
|
if entity.Type == entityType {
|
||||||
|
endOffset := entity.Offset + entity.Length
|
||||||
|
|
||||||
|
entityText := ""
|
||||||
|
if entity.Offset < len(textRunes) {
|
||||||
|
entityText = string(utf16.Decode(textRunes[entity.Offset:endOffset]))
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = append(filtered, EntityWithText{
|
||||||
|
Type: entity.Type,
|
||||||
|
Offset: entity.Offset,
|
||||||
|
Length: entity.Length,
|
||||||
|
Text: entityText,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildUserString(user *api.User) string {
|
||||||
|
if user.UserName != "" {
|
||||||
|
return "@" + user.UserName
|
||||||
|
}
|
||||||
|
|
||||||
|
var name strings.Builder
|
||||||
|
name.WriteString("<i>")
|
||||||
|
if user.FirstName != "" {
|
||||||
|
fmt.Fprint(&name, EscapeHTML(user.FirstName))
|
||||||
|
}
|
||||||
|
if user.LastName != "" {
|
||||||
|
fmt.Fprintf(&name, " %s", EscapeHTML(user.LastName))
|
||||||
|
}
|
||||||
|
name.WriteString("</i>")
|
||||||
|
return name.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendMessage(botAPI *api.BotAPI, chatID int64, topicID int, text string) (resp api.Message, err error) {
|
||||||
|
msg := api.NewMessage(chatID, text)
|
||||||
|
msg.ParseMode = api.ModeHTML
|
||||||
|
if topicID != 0 {
|
||||||
|
msg.MessageThreadID = topicID
|
||||||
|
}
|
||||||
|
return botAPI.Send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditMessage(botAPI *api.BotAPI, chatID int64, messageID int, text string) (resp api.Message, err error) {
|
||||||
|
edit := api.NewEditMessageText(chatID, messageID, text)
|
||||||
|
edit.ParseMode = api.ModeHTML
|
||||||
|
return botAPI.Send(edit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LeaveChatRequest(botAPI *api.BotAPI, chatIDs []int64) error {
|
||||||
|
var err error
|
||||||
|
for _, chatID := range chatIDs {
|
||||||
|
leaveChatConfig := api.LeaveChatConfig{ChatConfig: api.ChatConfig{ChatID: chatID}}
|
||||||
|
_, err = botAPI.Request(leaveChatConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditMessageWithKeyboard(botAPI *api.BotAPI, chatID int64, messageID int, text string, keyboard *api.InlineKeyboardMarkup) {
|
||||||
|
edit := api.NewEditMessageText(chatID, messageID, text)
|
||||||
|
edit.ParseMode = api.ModeHTML
|
||||||
|
if keyboard != nil {
|
||||||
|
edit.ReplyMarkup = keyboard
|
||||||
|
}
|
||||||
|
botAPI.Send(edit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseIntArg parses a string argument as int, returns (value, error message).
|
||||||
|
// Error message is empty on success.
|
||||||
|
func ParseIntArg(arg string) (int, string) {
|
||||||
|
val, err := strconv.Atoi(arg)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "Invalid value"
|
||||||
|
}
|
||||||
|
return val, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseInt64Arg parses a string argument as int64, returns (value, error message).
|
||||||
|
// Error message is empty on success.
|
||||||
|
func ParseInt64Arg(arg string) (int64, string) {
|
||||||
|
val, err := strconv.ParseInt(arg, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "Invalid value"
|
||||||
|
}
|
||||||
|
return val, ""
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue