|
@@ -65,14 +65,18 @@ var (
|
|
|
|
|
|
var userStates = make(map[int64]string)
|
|
|
|
|
|
+// LoginStatus represents the result of a login attempt.
|
|
|
type LoginStatus byte
|
|
|
|
|
|
+// Login status constants
|
|
|
const (
|
|
|
- LoginSuccess LoginStatus = 1
|
|
|
- LoginFail LoginStatus = 0
|
|
|
- EmptyTelegramUserID = int64(0)
|
|
|
+ LoginSuccess LoginStatus = 1 // Login was successful
|
|
|
+ LoginFail LoginStatus = 0 // Login failed
|
|
|
+ EmptyTelegramUserID = int64(0) // Default value for empty Telegram user ID
|
|
|
)
|
|
|
|
|
|
+// Tgbot provides business logic for Telegram bot integration.
|
|
|
+// It handles bot commands, user interactions, and status reporting via Telegram.
|
|
|
type Tgbot struct {
|
|
|
inboundService InboundService
|
|
|
settingService SettingService
|
|
@@ -81,18 +85,22 @@ type Tgbot struct {
|
|
|
lastStatus *Status
|
|
|
}
|
|
|
|
|
|
+// NewTgbot creates a new Tgbot instance.
|
|
|
func (t *Tgbot) NewTgbot() *Tgbot {
|
|
|
return new(Tgbot)
|
|
|
}
|
|
|
|
|
|
+// I18nBot retrieves a localized message for the bot interface.
|
|
|
func (t *Tgbot) I18nBot(name string, params ...string) string {
|
|
|
return locale.I18n(locale.Bot, name, params...)
|
|
|
}
|
|
|
|
|
|
+// GetHashStorage returns the hash storage instance for callback queries.
|
|
|
func (t *Tgbot) GetHashStorage() *global.HashStorage {
|
|
|
return hashStorage
|
|
|
}
|
|
|
|
|
|
+// Start initializes and starts the Telegram bot with the provided translation files.
|
|
|
func (t *Tgbot) Start(i18nFS embed.FS) error {
|
|
|
// Initialize localizer
|
|
|
err := locale.InitLocalizer(i18nFS, &t.settingService)
|
|
@@ -173,6 +181,7 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// NewBot creates a new Telegram bot instance with optional proxy and API server settings.
|
|
|
func (t *Tgbot) NewBot(token string, proxyUrl string, apiServerUrl string) (*telego.Bot, error) {
|
|
|
if proxyUrl == "" && apiServerUrl == "" {
|
|
|
return telego.NewBot(token)
|
|
@@ -209,10 +218,12 @@ func (t *Tgbot) NewBot(token string, proxyUrl string, apiServerUrl string) (*tel
|
|
|
return telego.NewBot(token, telego.WithAPIServer(apiServerUrl))
|
|
|
}
|
|
|
|
|
|
+// IsRunning checks if the Telegram bot is currently running.
|
|
|
func (t *Tgbot) IsRunning() bool {
|
|
|
return isRunning
|
|
|
}
|
|
|
|
|
|
+// SetHostname sets the hostname for the bot.
|
|
|
func (t *Tgbot) SetHostname() {
|
|
|
host, err := os.Hostname()
|
|
|
if err != nil {
|
|
@@ -223,6 +234,7 @@ func (t *Tgbot) SetHostname() {
|
|
|
hostname = host
|
|
|
}
|
|
|
|
|
|
+// Stop stops the Telegram bot and cleans up resources.
|
|
|
func (t *Tgbot) Stop() {
|
|
|
if botHandler != nil {
|
|
|
botHandler.Stop()
|
|
@@ -232,6 +244,7 @@ func (t *Tgbot) Stop() {
|
|
|
adminIds = nil
|
|
|
}
|
|
|
|
|
|
+// encodeQuery encodes the query string if it's longer than 64 characters.
|
|
|
func (t *Tgbot) encodeQuery(query string) string {
|
|
|
// NOTE: we only need to hash for more than 64 chars
|
|
|
if len(query) <= 64 {
|
|
@@ -241,6 +254,7 @@ func (t *Tgbot) encodeQuery(query string) string {
|
|
|
return hashStorage.SaveHash(query)
|
|
|
}
|
|
|
|
|
|
+// decodeQuery decodes a hashed query string back to its original form.
|
|
|
func (t *Tgbot) decodeQuery(query string) (string, error) {
|
|
|
if !hashStorage.IsMD5(query) {
|
|
|
return query, nil
|
|
@@ -254,6 +268,7 @@ func (t *Tgbot) decodeQuery(query string) (string, error) {
|
|
|
return decoded, nil
|
|
|
}
|
|
|
|
|
|
+// OnReceive starts the message receiving loop for the Telegram bot.
|
|
|
func (t *Tgbot) OnReceive() {
|
|
|
params := telego.GetUpdatesParams{
|
|
|
Timeout: 10,
|
|
@@ -430,6 +445,7 @@ func (t *Tgbot) OnReceive() {
|
|
|
botHandler.Start()
|
|
|
}
|
|
|
|
|
|
+// answerCommand processes incoming command messages from Telegram users.
|
|
|
func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) {
|
|
|
msg, onlyMessage := "", false
|
|
|
|
|
@@ -505,7 +521,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Helper function to send the message based on onlyMessage flag.
|
|
|
+// sendResponse sends the response message based on the onlyMessage flag.
|
|
|
func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool) {
|
|
|
if onlyMessage {
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
|
@@ -514,6 +530,7 @@ func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// randomLowerAndNum generates a random string of lowercase letters and numbers.
|
|
|
func (t *Tgbot) randomLowerAndNum(length int) string {
|
|
|
charset := "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
|
bytes := make([]byte, length)
|
|
@@ -524,6 +541,7 @@ func (t *Tgbot) randomLowerAndNum(length int) string {
|
|
|
return string(bytes)
|
|
|
}
|
|
|
|
|
|
+// randomShadowSocksPassword generates a random password for Shadowsocks.
|
|
|
func (t *Tgbot) randomShadowSocksPassword() string {
|
|
|
array := make([]byte, 32)
|
|
|
_, err := rand.Read(array)
|
|
@@ -533,6 +551,7 @@ func (t *Tgbot) randomShadowSocksPassword() string {
|
|
|
return base64.StdEncoding.EncodeToString(array)
|
|
|
}
|
|
|
|
|
|
+// answerCallback processes callback queries from inline keyboards.
|
|
|
func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
|
|
|
chatId := callbackQuery.Message.GetChat().ID
|
|
|
|
|
@@ -1815,6 +1834,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// BuildInboundClientDataMessage builds a message with client data for the given inbound and protocol.
|
|
|
func (t *Tgbot) BuildInboundClientDataMessage(inbound_remark string, protocol model.Protocol) (string, error) {
|
|
|
var message string
|
|
|
|
|
@@ -1864,6 +1884,7 @@ func (t *Tgbot) BuildInboundClientDataMessage(inbound_remark string, protocol mo
|
|
|
return message, nil
|
|
|
}
|
|
|
|
|
|
+// BuildJSONForProtocol builds a JSON string for the given protocol with client data.
|
|
|
func (t *Tgbot) BuildJSONForProtocol(protocol model.Protocol) (string, error) {
|
|
|
var jsonString string
|
|
|
|
|
@@ -1942,6 +1963,7 @@ func (t *Tgbot) BuildJSONForProtocol(protocol model.Protocol) (string, error) {
|
|
|
return jsonString, nil
|
|
|
}
|
|
|
|
|
|
+// SubmitAddClient submits the client addition request to the inbound service.
|
|
|
func (t *Tgbot) SubmitAddClient() (bool, error) {
|
|
|
|
|
|
inbound, err := t.inboundService.GetInbound(receiver_inbound_ID)
|
|
@@ -1964,6 +1986,7 @@ func (t *Tgbot) SubmitAddClient() (bool, error) {
|
|
|
return t.inboundService.AddInboundClient(newInbound)
|
|
|
}
|
|
|
|
|
|
+// checkAdmin checks if the given Telegram ID is an admin.
|
|
|
func checkAdmin(tgId int64) bool {
|
|
|
for _, adminId := range adminIds {
|
|
|
if adminId == tgId {
|
|
@@ -1973,6 +1996,7 @@ func checkAdmin(tgId int64) bool {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
+// SendAnswer sends a response message with an inline keyboard to the specified chat.
|
|
|
func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
|
|
numericKeyboard := tu.InlineKeyboard(
|
|
|
tu.InlineKeyboardRow(
|
|
@@ -2028,6 +2052,7 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
|
|
t.SendMsgToTgbot(chatId, msg, ReplyMarkup)
|
|
|
}
|
|
|
|
|
|
+// SendMsgToTgbot sends a message to the Telegram bot with optional reply markup.
|
|
|
func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.ReplyMarkup) {
|
|
|
if !isRunning {
|
|
|
return
|
|
@@ -2143,6 +2168,7 @@ func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
|
|
|
return subURL, subJsonURL, nil
|
|
|
}
|
|
|
|
|
|
+// sendClientSubLinks sends the subscription links for the client to the chat.
|
|
|
func (t *Tgbot) sendClientSubLinks(chatId int64, email string) {
|
|
|
subURL, subJsonURL, err := t.buildSubscriptionURLs(email)
|
|
|
if err != nil {
|
|
@@ -2338,6 +2364,7 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// SendMsgToTgbotAdmins sends a message to all admin Telegram chats.
|
|
|
func (t *Tgbot) SendMsgToTgbotAdmins(msg string, replyMarkup ...telego.ReplyMarkup) {
|
|
|
if len(replyMarkup) > 0 {
|
|
|
for _, adminId := range adminIds {
|
|
@@ -2350,6 +2377,7 @@ func (t *Tgbot) SendMsgToTgbotAdmins(msg string, replyMarkup ...telego.ReplyMark
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// SendReport sends a periodic report to admin chats.
|
|
|
func (t *Tgbot) SendReport() {
|
|
|
runTime, err := t.settingService.GetTgbotRuntime()
|
|
|
if err == nil && len(runTime) > 0 {
|
|
@@ -2371,6 +2399,7 @@ func (t *Tgbot) SendReport() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// SendBackupToAdmins sends a database backup to admin chats.
|
|
|
func (t *Tgbot) SendBackupToAdmins() {
|
|
|
if !t.IsRunning() {
|
|
|
return
|
|
@@ -2380,6 +2409,7 @@ func (t *Tgbot) SendBackupToAdmins() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// sendExhaustedToAdmins sends notifications about exhausted clients to admins.
|
|
|
func (t *Tgbot) sendExhaustedToAdmins() {
|
|
|
if !t.IsRunning() {
|
|
|
return
|
|
@@ -2389,6 +2419,7 @@ func (t *Tgbot) sendExhaustedToAdmins() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// getServerUsage retrieves and formats server usage information.
|
|
|
func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) string {
|
|
|
info := t.prepareServerUsageInfo()
|
|
|
|
|
@@ -2410,6 +2441,7 @@ func (t *Tgbot) sendServerUsage() string {
|
|
|
return info
|
|
|
}
|
|
|
|
|
|
+// prepareServerUsageInfo prepares the server usage information string.
|
|
|
func (t *Tgbot) prepareServerUsageInfo() string {
|
|
|
info, ipv4, ipv6 := "", "", ""
|
|
|
|
|
@@ -2459,6 +2491,7 @@ func (t *Tgbot) prepareServerUsageInfo() string {
|
|
|
return info
|
|
|
}
|
|
|
|
|
|
+// UserLoginNotify sends a notification about user login attempts to admins.
|
|
|
func (t *Tgbot) UserLoginNotify(username string, password string, ip string, time string, status LoginStatus) {
|
|
|
if !t.IsRunning() {
|
|
|
return
|
|
@@ -2490,6 +2523,7 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
|
|
|
t.SendMsgToTgbotAdmins(msg)
|
|
|
}
|
|
|
|
|
|
+// getInboundUsages retrieves and formats inbound usage information.
|
|
|
func (t *Tgbot) getInboundUsages() string {
|
|
|
info := ""
|
|
|
// get traffic
|
|
@@ -2515,6 +2549,8 @@ func (t *Tgbot) getInboundUsages() string {
|
|
|
}
|
|
|
return info
|
|
|
}
|
|
|
+
|
|
|
+// getInbounds creates an inline keyboard with all inbounds.
|
|
|
func (t *Tgbot) getInbounds() (*telego.InlineKeyboardMarkup, error) {
|
|
|
inbounds, err := t.inboundService.GetAllInbounds()
|
|
|
if err != nil {
|
|
@@ -2546,8 +2582,7 @@ func (t *Tgbot) getInbounds() (*telego.InlineKeyboardMarkup, error) {
|
|
|
return keyboard, nil
|
|
|
}
|
|
|
|
|
|
-// getInboundsFor builds an inline keyboard of inbounds where each button leads to a custom next action
|
|
|
-// nextAction should be one of: get_clients_for_sub|get_clients_for_individual|get_clients_for_qr
|
|
|
+// getInboundsFor builds an inline keyboard of inbounds for a custom next action.
|
|
|
func (t *Tgbot) getInboundsFor(nextAction string) (*telego.InlineKeyboardMarkup, error) {
|
|
|
inbounds, err := t.inboundService.GetAllInbounds()
|
|
|
if err != nil {
|
|
@@ -2614,6 +2649,7 @@ func (t *Tgbot) getInboundClientsFor(inboundID int, action string) (*telego.Inli
|
|
|
return keyboard, nil
|
|
|
}
|
|
|
|
|
|
+// getInboundsAddClient creates an inline keyboard for adding clients to inbounds.
|
|
|
func (t *Tgbot) getInboundsAddClient() (*telego.InlineKeyboardMarkup, error) {
|
|
|
inbounds, err := t.inboundService.GetAllInbounds()
|
|
|
if err != nil {
|
|
@@ -2656,6 +2692,7 @@ func (t *Tgbot) getInboundsAddClient() (*telego.InlineKeyboardMarkup, error) {
|
|
|
return keyboard, nil
|
|
|
}
|
|
|
|
|
|
+// getInboundClients creates an inline keyboard with clients of a specific inbound.
|
|
|
func (t *Tgbot) getInboundClients(id int) (*telego.InlineKeyboardMarkup, error) {
|
|
|
inbound, err := t.inboundService.GetInbound(id)
|
|
|
if err != nil {
|
|
@@ -2690,6 +2727,7 @@ func (t *Tgbot) getInboundClients(id int) (*telego.InlineKeyboardMarkup, error)
|
|
|
return keyboard, nil
|
|
|
}
|
|
|
|
|
|
+// clientInfoMsg formats client information message based on traffic and flags.
|
|
|
func (t *Tgbot) clientInfoMsg(
|
|
|
traffic *xray.ClientTraffic,
|
|
|
printEnabled bool,
|
|
@@ -2796,6 +2834,7 @@ func (t *Tgbot) clientInfoMsg(
|
|
|
return output
|
|
|
}
|
|
|
|
|
|
+// getClientUsage retrieves and sends client usage information to the chat.
|
|
|
func (t *Tgbot) getClientUsage(chatId int64, tgUserID int64, email ...string) {
|
|
|
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
|
|
|
if err != nil {
|
|
@@ -2838,6 +2877,7 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserID int64, email ...string) {
|
|
|
t.SendAnswer(chatId, output, false)
|
|
|
}
|
|
|
|
|
|
+// searchClientIps searches and sends client IP addresses for the given email.
|
|
|
func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) {
|
|
|
ips, err := t.inboundService.GetInboundClientIps(email)
|
|
|
if err != nil || len(ips) == 0 {
|
|
@@ -2865,6 +2905,7 @@ func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// clientTelegramUserInfo retrieves and sends Telegram user info for the client.
|
|
|
func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...int) {
|
|
|
traffic, client, err := t.inboundService.GetClientByEmail(email)
|
|
|
if err != nil {
|
|
@@ -2917,6 +2958,7 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// searchClient searches for a client by email and sends the information.
|
|
|
func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
|
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
|
|
if err != nil {
|
|
@@ -2962,6 +3004,7 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// addClient handles the process of adding a new client to an inbound.
|
|
|
func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
|
|
|
inbound, err := t.inboundService.GetInbound(receiver_inbound_ID)
|
|
|
if err != nil {
|
|
@@ -3058,6 +3101,7 @@ func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
|
|
|
|
|
|
}
|
|
|
|
|
|
+// searchInbound searches for inbounds by remark and sends the results.
|
|
|
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
|
|
inbounds, err := t.inboundService.SearchInbounds(remark)
|
|
|
if err != nil {
|
|
@@ -3095,6 +3139,7 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// getExhausted retrieves and sends information about exhausted clients.
|
|
|
func (t *Tgbot) getExhausted(chatId int64) {
|
|
|
trDiff := int64(0)
|
|
|
exDiff := int64(0)
|
|
@@ -3191,6 +3236,7 @@ func (t *Tgbot) getExhausted(chatId int64) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// notifyExhausted sends notifications for exhausted clients.
|
|
|
func (t *Tgbot) notifyExhausted() {
|
|
|
trDiff := int64(0)
|
|
|
exDiff := int64(0)
|
|
@@ -3262,6 +3308,7 @@ func (t *Tgbot) notifyExhausted() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// int64Contains checks if an int64 slice contains a specific item.
|
|
|
func int64Contains(slice []int64, item int64) bool {
|
|
|
for _, s := range slice {
|
|
|
if s == item {
|
|
@@ -3271,6 +3318,7 @@ func int64Contains(slice []int64, item int64) bool {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
+// onlineClients retrieves and sends information about online clients.
|
|
|
func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
|
|
if !p.IsRunning() {
|
|
|
return
|
|
@@ -3305,6 +3353,7 @@ func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// sendBackup sends a backup of the database and configuration files.
|
|
|
func (t *Tgbot) sendBackup(chatId int64) {
|
|
|
output := t.I18nBot("tgbot.messages.backupTime", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
|
t.SendMsgToTgbot(chatId, output)
|
|
@@ -3344,6 +3393,7 @@ func (t *Tgbot) sendBackup(chatId int64) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// sendBanLogs sends the ban logs to the specified chat.
|
|
|
func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
|
|
if dt {
|
|
|
output := t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
@@ -3393,6 +3443,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// sendCallbackAnswerTgBot answers a callback query with a message.
|
|
|
func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) {
|
|
|
params := telego.AnswerCallbackQueryParams{
|
|
|
CallbackQueryID: id,
|
|
@@ -3403,6 +3454,7 @@ func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// editMessageCallbackTgBot edits the reply markup of a message.
|
|
|
func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyboard *telego.InlineKeyboardMarkup) {
|
|
|
params := telego.EditMessageReplyMarkupParams{
|
|
|
ChatID: tu.ID(chatId),
|
|
@@ -3414,6 +3466,7 @@ func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyb
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// editMessageTgBot edits the text and reply markup of a message.
|
|
|
func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlineKeyboard ...*telego.InlineKeyboardMarkup) {
|
|
|
params := telego.EditMessageTextParams{
|
|
|
ChatID: tu.ID(chatId),
|
|
@@ -3429,6 +3482,7 @@ func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlin
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// SendMsgToTgbotDeleteAfter sends a message and deletes it after a specified delay.
|
|
|
func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSeconds int, replyMarkup ...telego.ReplyMarkup) {
|
|
|
// Determine if replyMarkup was passed; otherwise, set it to nil
|
|
|
var replyMarkupParam telego.ReplyMarkup
|
|
@@ -3455,6 +3509,7 @@ func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSecon
|
|
|
}()
|
|
|
}
|
|
|
|
|
|
+// deleteMessageTgBot deletes a message from the chat.
|
|
|
func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) {
|
|
|
params := telego.DeleteMessageParams{
|
|
|
ChatID: tu.ID(chatId),
|
|
@@ -3467,6 +3522,7 @@ func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// isSingleWord checks if the text contains only a single word.
|
|
|
func (t *Tgbot) isSingleWord(text string) bool {
|
|
|
text = strings.TrimSpace(text)
|
|
|
re := regexp.MustCompile(`\s+`)
|