2023-03-17 16:07:49 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
2025-08-08 18:41:06 +00:00
|
|
|
"context"
|
2025-03-26 18:16:35 +00:00
|
|
|
"crypto/rand"
|
2023-05-20 15:38:01 +00:00
|
|
|
"embed"
|
2025-03-26 18:16:35 +00:00
|
|
|
"encoding/base64"
|
2026-02-11 21:21:09 +00:00
|
|
|
"encoding/json"
|
2023-03-17 16:07:49 +00:00
|
|
|
"fmt"
|
2026-03-04 10:35:24 +00:00
|
|
|
"html"
|
2025-03-26 18:16:35 +00:00
|
|
|
"math/big"
|
2023-03-17 16:07:49 +00:00
|
|
|
"net"
|
2025-09-14 17:51:57 +00:00
|
|
|
"net/http"
|
2024-01-02 09:42:07 +00:00
|
|
|
"net/url"
|
2023-03-17 16:07:49 +00:00
|
|
|
"os"
|
2025-03-26 18:16:35 +00:00
|
|
|
"regexp"
|
2026-03-04 12:05:29 +00:00
|
|
|
"slices"
|
2026-05-12 21:25:35 +00:00
|
|
|
"sort"
|
2023-03-17 16:07:49 +00:00
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2025-09-21 17:27:05 +00:00
|
|
|
"sync"
|
2023-03-17 16:07:49 +00:00
|
|
|
"time"
|
2024-03-10 21:31:24 +00:00
|
|
|
|
2026-05-10 00:13:42 +00:00
|
|
|
"github.com/mhsanaei/3x-ui/v3/config"
|
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/database/model"
|
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/logger"
|
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/util/common"
|
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/web/global"
|
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/web/locale"
|
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/xray"
|
2023-03-17 16:07:49 +00:00
|
|
|
|
2025-03-26 18:16:35 +00:00
|
|
|
"github.com/google/uuid"
|
2023-05-14 15:20:01 +00:00
|
|
|
"github.com/mymmrac/telego"
|
|
|
|
|
th "github.com/mymmrac/telego/telegohandler"
|
|
|
|
|
tu "github.com/mymmrac/telego/telegoutil"
|
2025-09-14 17:51:57 +00:00
|
|
|
"github.com/skip2/go-qrcode"
|
2024-01-02 09:42:07 +00:00
|
|
|
"github.com/valyala/fasthttp"
|
|
|
|
|
"github.com/valyala/fasthttp/fasthttpproxy"
|
2023-03-17 16:07:49 +00:00
|
|
|
)
|
|
|
|
|
|
2024-03-10 21:31:24 +00:00
|
|
|
var (
|
2025-11-01 11:56:55 +00:00
|
|
|
bot *telego.Bot
|
|
|
|
|
|
|
|
|
|
// botCancel stores the function to cancel the context, stopping Long Polling gracefully.
|
|
|
|
|
botCancel context.CancelFunc
|
|
|
|
|
// tgBotMutex protects concurrent access to botCancel variable
|
|
|
|
|
tgBotMutex sync.Mutex
|
|
|
|
|
// botWG waits for the OnReceive Long Polling goroutine to finish.
|
|
|
|
|
botWG sync.WaitGroup
|
|
|
|
|
|
2025-04-06 22:45:52 +00:00
|
|
|
botHandler *th.BotHandler
|
|
|
|
|
adminIds []int64
|
|
|
|
|
isRunning bool
|
|
|
|
|
hostname string
|
|
|
|
|
hashStorage *global.HashStorage
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
// Performance improvements
|
2025-09-21 22:20:05 +00:00
|
|
|
messageWorkerPool chan struct{} // Semaphore for limiting concurrent message processing
|
|
|
|
|
optimizedHTTPClient *http.Client // HTTP client with connection pooling and timeouts
|
|
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
// Simple cache for frequently accessed data
|
|
|
|
|
statusCache struct {
|
|
|
|
|
data *Status
|
|
|
|
|
timestamp time.Time
|
|
|
|
|
mutex sync.RWMutex
|
|
|
|
|
}
|
2025-09-21 22:20:05 +00:00
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
serverStatsCache struct {
|
|
|
|
|
data string
|
|
|
|
|
timestamp time.Time
|
|
|
|
|
mutex sync.RWMutex
|
|
|
|
|
}
|
2025-09-21 22:20:05 +00:00
|
|
|
|
2025-03-26 18:16:35 +00:00
|
|
|
// clients data to adding new client
|
2026-05-12 21:25:35 +00:00
|
|
|
clientStates map[int64]*ClientState
|
|
|
|
|
clientStatesMutex sync.RWMutex
|
2024-03-10 21:31:24 +00:00
|
|
|
)
|
2023-03-17 16:07:49 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
|
|
|
|
|
type ClientState struct {
|
|
|
|
|
ReceiverInboundID int
|
|
|
|
|
Id string
|
|
|
|
|
Flow string
|
|
|
|
|
Email string
|
|
|
|
|
LimitIP int
|
|
|
|
|
TotalGB int64
|
|
|
|
|
ExpiryTime int64
|
|
|
|
|
Enable bool
|
|
|
|
|
TgID string
|
|
|
|
|
SubID string
|
|
|
|
|
Comment string
|
|
|
|
|
Reset int
|
|
|
|
|
Security string
|
|
|
|
|
ShPassword string
|
|
|
|
|
TrPassword string
|
|
|
|
|
Method string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) getClientState(chatId int64) *ClientState {
|
|
|
|
|
clientStatesMutex.RLock()
|
|
|
|
|
state, exists := clientStates[chatId]
|
|
|
|
|
clientStatesMutex.RUnlock()
|
|
|
|
|
if !exists {
|
|
|
|
|
clientStatesMutex.Lock()
|
|
|
|
|
if clientStates == nil {
|
|
|
|
|
clientStates = make(map[int64]*ClientState)
|
|
|
|
|
}
|
|
|
|
|
state = &ClientState{}
|
|
|
|
|
clientStates[chatId] = state
|
|
|
|
|
clientStatesMutex.Unlock()
|
|
|
|
|
}
|
|
|
|
|
return state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) clearClientState(chatId int64) {
|
|
|
|
|
clientStatesMutex.Lock()
|
|
|
|
|
delete(clientStates, chatId)
|
|
|
|
|
clientStatesMutex.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-26 18:16:35 +00:00
|
|
|
var userStates = make(map[int64]string)
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// LoginStatus represents the result of a login attempt.
|
2023-03-17 16:07:49 +00:00
|
|
|
type LoginStatus byte
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// Login status constants
|
2023-03-17 16:07:49 +00:00
|
|
|
const (
|
2025-09-20 07:35:50 +00:00
|
|
|
LoginSuccess LoginStatus = 1 // Login was successful
|
|
|
|
|
LoginFail LoginStatus = 0 // Login failed
|
|
|
|
|
EmptyTelegramUserID = int64(0) // Default value for empty Telegram user ID
|
2023-03-17 16:07:49 +00:00
|
|
|
)
|
|
|
|
|
|
2026-05-07 21:36:11 +00:00
|
|
|
// LoginAttempt contains safe metadata for panel login notifications.
|
|
|
|
|
// It intentionally does not include attempted passwords.
|
|
|
|
|
type LoginAttempt struct {
|
|
|
|
|
Username string
|
|
|
|
|
IP string
|
|
|
|
|
Time string
|
|
|
|
|
Status LoginStatus
|
|
|
|
|
Reason string
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// Tgbot provides business logic for Telegram bot integration.
|
|
|
|
|
// It handles bot commands, user interactions, and status reporting via Telegram.
|
2023-03-17 16:07:49 +00:00
|
|
|
type Tgbot struct {
|
|
|
|
|
inboundService InboundService
|
|
|
|
|
settingService SettingService
|
|
|
|
|
serverService ServerService
|
2023-05-04 23:17:26 +00:00
|
|
|
xrayService XrayService
|
2023-03-17 16:07:49 +00:00
|
|
|
lastStatus *Status
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// NewTgbot creates a new Tgbot instance.
|
2023-03-17 16:07:49 +00:00
|
|
|
func (t *Tgbot) NewTgbot() *Tgbot {
|
|
|
|
|
return new(Tgbot)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// I18nBot retrieves a localized message for the bot interface.
|
2023-05-20 23:00:26 +00:00
|
|
|
func (t *Tgbot) I18nBot(name string, params ...string) string {
|
2023-05-20 15:38:01 +00:00
|
|
|
return locale.I18n(locale.Bot, name, params...)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// GetHashStorage returns the hash storage instance for callback queries.
|
2023-05-20 15:59:28 +00:00
|
|
|
func (t *Tgbot) GetHashStorage() *global.HashStorage {
|
2023-05-21 01:03:01 +00:00
|
|
|
return hashStorage
|
2023-05-20 15:59:28 +00:00
|
|
|
}
|
|
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
// getCachedStatus returns cached server status if it's fresh enough (less than 5 seconds old)
|
|
|
|
|
func (t *Tgbot) getCachedStatus() (*Status, bool) {
|
|
|
|
|
statusCache.mutex.RLock()
|
|
|
|
|
defer statusCache.mutex.RUnlock()
|
2025-09-21 22:20:05 +00:00
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
if statusCache.data != nil && time.Since(statusCache.timestamp) < 5*time.Second {
|
|
|
|
|
return statusCache.data, true
|
|
|
|
|
}
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setCachedStatus updates the status cache
|
|
|
|
|
func (t *Tgbot) setCachedStatus(status *Status) {
|
|
|
|
|
statusCache.mutex.Lock()
|
|
|
|
|
defer statusCache.mutex.Unlock()
|
2025-09-21 22:20:05 +00:00
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
statusCache.data = status
|
|
|
|
|
statusCache.timestamp = time.Now()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getCachedServerStats returns cached server stats if it's fresh enough (less than 10 seconds old)
|
|
|
|
|
func (t *Tgbot) getCachedServerStats() (string, bool) {
|
|
|
|
|
serverStatsCache.mutex.RLock()
|
|
|
|
|
defer serverStatsCache.mutex.RUnlock()
|
2025-09-21 22:20:05 +00:00
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
if serverStatsCache.data != "" && time.Since(serverStatsCache.timestamp) < 10*time.Second {
|
|
|
|
|
return serverStatsCache.data, true
|
|
|
|
|
}
|
|
|
|
|
return "", false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setCachedServerStats updates the server stats cache
|
|
|
|
|
func (t *Tgbot) setCachedServerStats(stats string) {
|
|
|
|
|
serverStatsCache.mutex.Lock()
|
|
|
|
|
defer serverStatsCache.mutex.Unlock()
|
2025-09-21 22:20:05 +00:00
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
serverStatsCache.data = stats
|
|
|
|
|
serverStatsCache.timestamp = time.Now()
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// Start initializes and starts the Telegram bot with the provided translation files.
|
2023-05-20 15:38:01 +00:00
|
|
|
func (t *Tgbot) Start(i18nFS embed.FS) error {
|
2024-07-08 21:08:00 +00:00
|
|
|
// Initialize localizer
|
2023-05-20 15:38:01 +00:00
|
|
|
err := locale.InitLocalizer(i18nFS, &t.settingService)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-02 15:13:32 +00:00
|
|
|
// If Start is called again (e.g. during reload), ensure any previous long-polling
|
|
|
|
|
// loop is stopped before creating a new bot / receiver.
|
|
|
|
|
StopBot()
|
|
|
|
|
|
2024-07-08 21:08:00 +00:00
|
|
|
// Initialize hash storage to store callback queries
|
2023-05-21 04:03:08 +00:00
|
|
|
hashStorage = global.NewHashStorage(20 * time.Minute)
|
2023-05-20 15:59:28 +00:00
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
// Initialize worker pool for concurrent message processing (max 10 concurrent handlers)
|
|
|
|
|
messageWorkerPool = make(chan struct{}, 10)
|
2025-09-21 22:20:05 +00:00
|
|
|
|
2025-09-21 17:27:05 +00:00
|
|
|
// Initialize optimized HTTP client with connection pooling
|
|
|
|
|
optimizedHTTPClient = &http.Client{
|
|
|
|
|
Timeout: 15 * time.Second,
|
|
|
|
|
Transport: &http.Transport{
|
|
|
|
|
MaxIdleConns: 100,
|
|
|
|
|
MaxIdleConnsPerHost: 10,
|
|
|
|
|
IdleConnTimeout: 30 * time.Second,
|
|
|
|
|
DisableKeepAlives: false,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-20 23:00:26 +00:00
|
|
|
t.SetHostname()
|
2024-07-08 21:08:00 +00:00
|
|
|
|
|
|
|
|
// Get Telegram bot token
|
|
|
|
|
tgBotToken, err := t.settingService.GetTgBotToken()
|
|
|
|
|
if err != nil || tgBotToken == "" {
|
|
|
|
|
logger.Warning("Failed to get Telegram bot token:", err)
|
2023-03-17 16:07:49 +00:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-08 21:08:00 +00:00
|
|
|
// Get Telegram bot chat ID(s)
|
|
|
|
|
tgBotID, err := t.settingService.GetTgBotChatId()
|
2023-03-17 16:07:49 +00:00
|
|
|
if err != nil {
|
2024-07-08 21:08:00 +00:00
|
|
|
logger.Warning("Failed to get Telegram bot chat ID:", err)
|
2023-03-17 16:07:49 +00:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-02 15:13:32 +00:00
|
|
|
parsedAdminIds := make([]int64, 0)
|
2024-07-08 21:08:00 +00:00
|
|
|
// Parse admin IDs from comma-separated string
|
|
|
|
|
if tgBotID != "" {
|
|
|
|
|
for _, adminID := range strings.Split(tgBotID, ",") {
|
2025-12-03 13:58:54 +00:00
|
|
|
id, err := strconv.ParseInt(adminID, 10, 64)
|
2023-05-31 01:31:20 +00:00
|
|
|
if err != nil {
|
2024-07-08 21:08:00 +00:00
|
|
|
logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err)
|
2023-05-31 01:31:20 +00:00
|
|
|
return err
|
|
|
|
|
}
|
2026-01-02 15:13:32 +00:00
|
|
|
parsedAdminIds = append(parsedAdminIds, int64(id))
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-02 15:13:32 +00:00
|
|
|
tgBotMutex.Lock()
|
|
|
|
|
adminIds = parsedAdminIds
|
|
|
|
|
tgBotMutex.Unlock()
|
2023-03-17 16:07:49 +00:00
|
|
|
|
2024-07-08 21:08:00 +00:00
|
|
|
// Get Telegram bot proxy URL
|
2024-01-02 09:42:07 +00:00
|
|
|
tgBotProxy, err := t.settingService.GetTgBotProxy()
|
|
|
|
|
if err != nil {
|
2024-07-08 21:08:00 +00:00
|
|
|
logger.Warning("Failed to get Telegram bot proxy URL:", err)
|
2024-01-02 09:42:07 +00:00
|
|
|
}
|
|
|
|
|
|
2024-10-17 08:59:42 +00:00
|
|
|
// Get Telegram bot API server URL
|
|
|
|
|
tgBotAPIServer, err := t.settingService.GetTgBotAPIServer()
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Warning("Failed to get Telegram bot API server URL:", err)
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-08 21:08:00 +00:00
|
|
|
// Create new Telegram bot instance
|
2024-10-17 08:59:42 +00:00
|
|
|
bot, err = t.NewBot(tgBotToken, tgBotProxy, tgBotAPIServer)
|
2023-03-17 16:07:49 +00:00
|
|
|
if err != nil {
|
2024-07-08 21:08:00 +00:00
|
|
|
logger.Error("Failed to initialize Telegram bot API:", err)
|
2023-03-17 16:07:49 +00:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 08:47:30 +00:00
|
|
|
t.trySetBotCommands(bot)
|
2025-05-28 08:26:29 +00:00
|
|
|
|
2024-07-08 21:08:00 +00:00
|
|
|
// Start receiving Telegram bot messages
|
2026-01-02 15:13:32 +00:00
|
|
|
tgBotMutex.Lock()
|
|
|
|
|
alreadyRunning := isRunning || botCancel != nil
|
|
|
|
|
tgBotMutex.Unlock()
|
|
|
|
|
if !alreadyRunning {
|
2024-07-08 21:08:00 +00:00
|
|
|
logger.Info("Telegram bot receiver started")
|
2023-03-17 16:07:49 +00:00
|
|
|
go t.OnReceive()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 08:47:30 +00:00
|
|
|
func (t *Tgbot) trySetBotCommands(bot *telego.Bot) {
|
|
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
|
logger.Warning("Failed to register bot commands (Telegram may be rate-limiting); bot will continue without them:", r)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
err := bot.SetMyCommands(context.Background(), &telego.SetMyCommandsParams{
|
|
|
|
|
Commands: []telego.BotCommand{
|
|
|
|
|
{Command: "start", Description: t.I18nBot("tgbot.commands.startDesc")},
|
|
|
|
|
{Command: "help", Description: t.I18nBot("tgbot.commands.helpDesc")},
|
|
|
|
|
{Command: "status", Description: t.I18nBot("tgbot.commands.statusDesc")},
|
|
|
|
|
{Command: "id", Description: t.I18nBot("tgbot.commands.idDesc")},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Warning("Failed to set bot commands:", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 21:49:19 +00:00
|
|
|
// createRobustFastHTTPClient creates a fasthttp.Client with proper connection handling
|
|
|
|
|
func (t *Tgbot) createRobustFastHTTPClient(proxyUrl string) *fasthttp.Client {
|
|
|
|
|
client := &fasthttp.Client{
|
|
|
|
|
// Connection timeouts
|
|
|
|
|
ReadTimeout: 30 * time.Second,
|
|
|
|
|
WriteTimeout: 30 * time.Second,
|
|
|
|
|
MaxIdleConnDuration: 60 * time.Second,
|
|
|
|
|
MaxConnDuration: 0, // unlimited, but controlled by MaxIdleConnDuration
|
|
|
|
|
MaxIdemponentCallAttempts: 3,
|
|
|
|
|
ReadBufferSize: 4096,
|
|
|
|
|
WriteBufferSize: 4096,
|
|
|
|
|
MaxConnsPerHost: 100,
|
|
|
|
|
MaxConnWaitTimeout: 10 * time.Second,
|
|
|
|
|
DisableHeaderNamesNormalizing: false,
|
|
|
|
|
DisablePathNormalizing: false,
|
|
|
|
|
// Retry on connection errors
|
|
|
|
|
RetryIf: func(request *fasthttp.Request) bool {
|
|
|
|
|
// Retry on connection errors for GET requests
|
|
|
|
|
return string(request.Header.Method()) == "GET" || string(request.Header.Method()) == "POST"
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set proxy if provided
|
|
|
|
|
if proxyUrl != "" {
|
|
|
|
|
client.Dial = fasthttpproxy.FasthttpSocksDialer(proxyUrl)
|
2024-02-03 14:45:47 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 21:49:19 +00:00
|
|
|
return client
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
|
// Validate proxy URL if provided
|
2024-10-17 08:59:42 +00:00
|
|
|
if proxyUrl != "" {
|
|
|
|
|
if !strings.HasPrefix(proxyUrl, "socks5://") {
|
2026-02-14 21:49:19 +00:00
|
|
|
logger.Warning("Invalid socks5 URL, ignoring proxy")
|
|
|
|
|
proxyUrl = "" // Clear invalid proxy
|
|
|
|
|
} else {
|
|
|
|
|
_, err := url.Parse(proxyUrl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Warningf("Can't parse proxy URL, ignoring proxy: %v", err)
|
|
|
|
|
proxyUrl = ""
|
|
|
|
|
}
|
2024-10-17 08:59:42 +00:00
|
|
|
}
|
2026-02-14 21:49:19 +00:00
|
|
|
}
|
2024-10-17 08:59:42 +00:00
|
|
|
|
2026-02-14 21:49:19 +00:00
|
|
|
// Validate API server URL if provided
|
|
|
|
|
if apiServerUrl != "" {
|
|
|
|
|
if !strings.HasPrefix(apiServerUrl, "http") {
|
|
|
|
|
logger.Warning("Invalid http(s) URL for API server, using default")
|
|
|
|
|
apiServerUrl = ""
|
|
|
|
|
} else {
|
|
|
|
|
_, err := url.Parse(apiServerUrl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Warningf("Can't parse API server URL, using default: %v", err)
|
|
|
|
|
apiServerUrl = ""
|
|
|
|
|
}
|
2024-10-17 08:59:42 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 21:49:19 +00:00
|
|
|
// Create robust fasthttp client
|
|
|
|
|
client := t.createRobustFastHTTPClient(proxyUrl)
|
2024-01-02 09:42:07 +00:00
|
|
|
|
2026-02-14 21:49:19 +00:00
|
|
|
// Build bot options
|
|
|
|
|
var options []telego.BotOption
|
|
|
|
|
options = append(options, telego.WithFastHTTPClient(client))
|
|
|
|
|
|
|
|
|
|
if apiServerUrl != "" {
|
|
|
|
|
options = append(options, telego.WithAPIServer(apiServerUrl))
|
2024-01-02 09:42:07 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 21:49:19 +00:00
|
|
|
return telego.NewBot(token, options...)
|
2024-01-02 09:42:07 +00:00
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// IsRunning checks if the Telegram bot is currently running.
|
2023-05-20 15:09:01 +00:00
|
|
|
func (t *Tgbot) IsRunning() bool {
|
2026-01-02 15:13:32 +00:00
|
|
|
tgBotMutex.Lock()
|
|
|
|
|
defer tgBotMutex.Unlock()
|
2023-03-17 16:07:49 +00:00
|
|
|
return isRunning
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// SetHostname sets the hostname for the bot.
|
2023-05-20 23:00:26 +00:00
|
|
|
func (t *Tgbot) SetHostname() {
|
|
|
|
|
host, err := os.Hostname()
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Error("get hostname error:", err)
|
|
|
|
|
hostname = ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
hostname = host
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 11:56:55 +00:00
|
|
|
// Stop safely stops the Telegram bot's Long Polling operation.
|
|
|
|
|
// This method now calls the global StopBot function and cleans up other resources.
|
2023-03-17 16:07:49 +00:00
|
|
|
func (t *Tgbot) Stop() {
|
2025-11-01 11:56:55 +00:00
|
|
|
StopBot()
|
2023-03-17 16:07:49 +00:00
|
|
|
logger.Info("Stop Telegram receiver ...")
|
2026-01-02 15:13:32 +00:00
|
|
|
tgBotMutex.Lock()
|
2023-03-17 16:07:49 +00:00
|
|
|
adminIds = nil
|
2026-01-02 15:13:32 +00:00
|
|
|
tgBotMutex.Unlock()
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 11:56:55 +00:00
|
|
|
// StopBot safely stops the Telegram bot's Long Polling operation by cancelling its context.
|
|
|
|
|
// This is the global function called from main.go's signal handler and t.Stop().
|
|
|
|
|
func StopBot() {
|
2026-01-02 15:13:32 +00:00
|
|
|
// Don't hold the mutex while cancelling/waiting.
|
2025-11-01 11:56:55 +00:00
|
|
|
tgBotMutex.Lock()
|
2026-01-02 15:13:32 +00:00
|
|
|
cancel := botCancel
|
|
|
|
|
botCancel = nil
|
|
|
|
|
handler := botHandler
|
|
|
|
|
botHandler = nil
|
|
|
|
|
isRunning = false
|
|
|
|
|
tgBotMutex.Unlock()
|
2025-11-01 11:56:55 +00:00
|
|
|
|
2026-01-02 15:13:32 +00:00
|
|
|
if handler != nil {
|
|
|
|
|
handler.Stop()
|
|
|
|
|
}
|
2025-11-01 11:56:55 +00:00
|
|
|
|
2026-01-02 15:13:32 +00:00
|
|
|
if cancel != nil {
|
|
|
|
|
logger.Info("Sending cancellation signal to Telegram bot...")
|
|
|
|
|
// Cancels the context passed to UpdatesViaLongPolling; this closes updates channel
|
|
|
|
|
// and lets botHandler.Start() exit cleanly.
|
|
|
|
|
cancel()
|
2025-11-01 11:56:55 +00:00
|
|
|
botWG.Wait()
|
|
|
|
|
logger.Info("Telegram bot successfully stopped.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// encodeQuery encodes the query string if it's longer than 64 characters.
|
2023-05-21 04:03:08 +00:00
|
|
|
func (t *Tgbot) encodeQuery(query string) string {
|
|
|
|
|
// NOTE: we only need to hash for more than 64 chars
|
|
|
|
|
if len(query) <= 64 {
|
|
|
|
|
return query
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hashStorage.SaveHash(query)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// decodeQuery decodes a hashed query string back to its original form.
|
2023-05-21 04:03:08 +00:00
|
|
|
func (t *Tgbot) decodeQuery(query string) (string, error) {
|
|
|
|
|
if !hashStorage.IsMD5(query) {
|
|
|
|
|
return query, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
decoded, exists := hashStorage.GetValue(query)
|
|
|
|
|
if !exists {
|
|
|
|
|
return "", common.NewError("hash not found in storage!")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return decoded, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// OnReceive starts the message receiving loop for the Telegram bot.
|
2023-03-17 16:07:49 +00:00
|
|
|
func (t *Tgbot) OnReceive() {
|
2023-05-14 15:20:01 +00:00
|
|
|
params := telego.GetUpdatesParams{
|
2026-02-14 21:49:19 +00:00
|
|
|
Timeout: 20, // Reduced timeout to detect connection issues faster
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2026-01-02 15:13:32 +00:00
|
|
|
// Strict singleton: never start a second long-polling loop.
|
2025-11-01 11:56:55 +00:00
|
|
|
tgBotMutex.Lock()
|
2026-01-02 15:13:32 +00:00
|
|
|
if botCancel != nil || isRunning {
|
|
|
|
|
tgBotMutex.Unlock()
|
|
|
|
|
logger.Warning("TgBot OnReceive called while already running; ignoring.")
|
|
|
|
|
return
|
2025-11-01 11:56:55 +00:00
|
|
|
}
|
2023-05-14 15:20:01 +00:00
|
|
|
|
2026-01-02 15:13:32 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
botCancel = cancel
|
|
|
|
|
isRunning = true
|
|
|
|
|
// Add to WaitGroup before releasing the lock so StopBot() can't return
|
|
|
|
|
// before this receiver goroutine is accounted for.
|
|
|
|
|
botWG.Add(1)
|
2025-11-01 11:56:55 +00:00
|
|
|
tgBotMutex.Unlock()
|
2023-05-14 18:37:49 +00:00
|
|
|
|
2026-02-14 21:49:19 +00:00
|
|
|
// Get updates channel using the context with shorter timeout for better error recovery
|
2025-11-01 11:56:55 +00:00
|
|
|
updates, _ := bot.UpdatesViaLongPolling(ctx, ¶ms)
|
2026-01-02 15:13:32 +00:00
|
|
|
go func() {
|
|
|
|
|
defer botWG.Done()
|
|
|
|
|
h, _ := th.NewBotHandler(bot, updates)
|
|
|
|
|
tgBotMutex.Lock()
|
|
|
|
|
botHandler = h
|
|
|
|
|
tgBotMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
h.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
2025-09-21 17:27:05 +00:00
|
|
|
delete(userStates, message.Chat.ID)
|
2025-11-01 11:56:55 +00:00
|
|
|
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove())
|
|
|
|
|
return nil
|
|
|
|
|
}, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard")))
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-01-02 15:13:32 +00:00
|
|
|
h.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
2026-05-12 21:25:35 +00:00
|
|
|
logger.Debug("Telegram command received:", message.Text)
|
2025-11-01 11:56:55 +00:00
|
|
|
// Use goroutine with worker pool for concurrent command processing
|
|
|
|
|
go func() {
|
|
|
|
|
messageWorkerPool <- struct{}{} // Acquire worker
|
|
|
|
|
defer func() { <-messageWorkerPool }() // Release worker
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2025-11-01 11:56:55 +00:00
|
|
|
delete(userStates, message.Chat.ID)
|
|
|
|
|
t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID))
|
|
|
|
|
}()
|
|
|
|
|
return nil
|
|
|
|
|
}, th.AnyCommand())
|
|
|
|
|
|
2026-01-02 15:13:32 +00:00
|
|
|
h.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error {
|
2026-05-12 21:25:35 +00:00
|
|
|
logger.Debug("Telegram callback received:", query.Data)
|
2025-11-01 11:56:55 +00:00
|
|
|
// Use goroutine with worker pool for concurrent callback processing
|
|
|
|
|
go func() {
|
|
|
|
|
messageWorkerPool <- struct{}{} // Acquire worker
|
|
|
|
|
defer func() { <-messageWorkerPool }() // Release worker
|
|
|
|
|
|
|
|
|
|
delete(userStates, query.Message.GetChat().ID)
|
|
|
|
|
t.answerCallback(&query, checkAdmin(query.From.ID))
|
|
|
|
|
}()
|
|
|
|
|
return nil
|
|
|
|
|
}, th.AnyCallbackQueryWithMessage())
|
|
|
|
|
|
2026-01-02 15:13:32 +00:00
|
|
|
h.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
2026-05-12 21:25:35 +00:00
|
|
|
logger.Debug("Telegram message received:", message.Text)
|
|
|
|
|
chatId := message.Chat.ID
|
|
|
|
|
state := t.getClientState(chatId)
|
|
|
|
|
if userState, exists := userStates[chatId]; exists {
|
2025-11-01 11:56:55 +00:00
|
|
|
switch userState {
|
|
|
|
|
case "awaiting_id":
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.Id == strings.TrimSpace(message.Text) {
|
2025-11-01 11:56:55 +00:00
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-11-01 11:56:55 +00:00
|
|
|
t.addClient(message.Chat.ID, message_text)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Id = strings.TrimSpace(message.Text)
|
|
|
|
|
if t.isSingleWord(state.Id) {
|
2025-11-01 11:56:55 +00:00
|
|
|
userStates[message.Chat.ID] = "awaiting_id"
|
|
|
|
|
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup)
|
|
|
|
|
} else {
|
|
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_id"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-11-01 11:56:55 +00:00
|
|
|
t.addClient(message.Chat.ID, message_text)
|
|
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
case "awaiting_subid":
|
2026-03-04 20:07:58 +00:00
|
|
|
newSubID := strings.TrimSpace(message.Text)
|
2026-05-12 00:52:02 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.SubID == newSubID {
|
2026-03-03 23:41:13 +00:00
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2026-03-04 20:07:58 +00:00
|
|
|
t.addClient(message.Chat.ID, message_text)
|
2026-03-03 23:41:13 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 20:07:58 +00:00
|
|
|
isValidURI, _ := regexp.MatchString(`^[\p{L}\p{N}\-_]+$`, newSubID)
|
|
|
|
|
|
|
|
|
|
if !isValidURI {
|
|
|
|
|
userStates[message.Chat.ID] = "awaiting_subid"
|
|
|
|
|
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.invalid_subid"), cancel_btn_markup)
|
|
|
|
|
} else {
|
2026-05-12 21:25:35 +00:00
|
|
|
state.SubID = newSubID
|
2026-03-04 20:07:58 +00:00
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_subid"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2026-03-04 20:07:58 +00:00
|
|
|
t.addClient(message.Chat.ID, message_text)
|
|
|
|
|
}
|
2025-11-01 11:56:55 +00:00
|
|
|
case "awaiting_password_tr":
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.TrPassword == strings.TrimSpace(message.Text) {
|
2025-11-01 11:56:55 +00:00
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
state.TrPassword = strings.TrimSpace(message.Text)
|
|
|
|
|
if t.isSingleWord(state.TrPassword) {
|
2025-11-01 11:56:55 +00:00
|
|
|
userStates[message.Chat.ID] = "awaiting_password_tr"
|
|
|
|
|
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup)
|
|
|
|
|
} else {
|
|
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_password"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-11-01 11:56:55 +00:00
|
|
|
t.addClient(message.Chat.ID, message_text)
|
|
|
|
|
}
|
|
|
|
|
case "awaiting_password_sh":
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.ShPassword == strings.TrimSpace(message.Text) {
|
2025-11-01 11:56:55 +00:00
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
state.ShPassword = strings.TrimSpace(message.Text)
|
|
|
|
|
if t.isSingleWord(state.ShPassword) {
|
2025-11-01 11:56:55 +00:00
|
|
|
userStates[message.Chat.ID] = "awaiting_password_sh"
|
|
|
|
|
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup)
|
|
|
|
|
} else {
|
|
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_password"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-11-01 11:56:55 +00:00
|
|
|
t.addClient(message.Chat.ID, message_text)
|
|
|
|
|
}
|
|
|
|
|
case "awaiting_email":
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.Email == strings.TrimSpace(message.Text) {
|
2025-11-01 11:56:55 +00:00
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Email = strings.TrimSpace(message.Text)
|
|
|
|
|
if t.isSingleWord(state.Email) {
|
2025-11-01 11:56:55 +00:00
|
|
|
userStates[message.Chat.ID] = "awaiting_email"
|
|
|
|
|
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup)
|
|
|
|
|
} else {
|
|
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_email"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-11-01 11:56:55 +00:00
|
|
|
t.addClient(message.Chat.ID, message_text)
|
|
|
|
|
}
|
|
|
|
|
case "awaiting_comment":
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.Comment == strings.TrimSpace(message.Text) {
|
2025-11-01 11:56:55 +00:00
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, message.Chat.ID)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Comment = strings.TrimSpace(message.Text)
|
2025-11-01 11:56:55 +00:00
|
|
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_comment"), 3, tu.ReplyKeyboardRemove())
|
2025-03-26 18:16:35 +00:00
|
|
|
delete(userStates, message.Chat.ID)
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-03-26 18:16:35 +00:00
|
|
|
t.addClient(message.Chat.ID, message_text)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 11:56:55 +00:00
|
|
|
} else {
|
|
|
|
|
if message.UsersShared != nil {
|
|
|
|
|
if checkAdmin(message.From.ID) {
|
|
|
|
|
for _, sharedUser := range message.UsersShared.Users {
|
|
|
|
|
userID := sharedUser.UserID
|
|
|
|
|
needRestart, err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userID)
|
|
|
|
|
if needRestart {
|
|
|
|
|
t.xrayService.SetToNeedRestart()
|
|
|
|
|
}
|
|
|
|
|
output := ""
|
|
|
|
|
if err != nil {
|
|
|
|
|
output += t.I18nBot("tgbot.messages.selectUserFailed")
|
|
|
|
|
} else {
|
|
|
|
|
output += t.I18nBot("tgbot.messages.userSaved")
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove())
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
2025-11-01 11:56:55 +00:00
|
|
|
} else {
|
|
|
|
|
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove())
|
2024-04-02 11:34:44 +00:00
|
|
|
}
|
2023-05-14 18:37:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-01 11:56:55 +00:00
|
|
|
return nil
|
|
|
|
|
}, th.AnyMessage())
|
2023-05-14 18:37:49 +00:00
|
|
|
|
2026-01-02 15:13:32 +00:00
|
|
|
h.Start()
|
|
|
|
|
}()
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
func checkAdmin(tgId int64) bool {
|
|
|
|
|
for _, adminId := range adminIds {
|
|
|
|
|
if adminId == tgId {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.SortedTrafficUsageReport")).WithCallbackData(t.encodeQuery("get_sorted_traffic_usage_report")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.serverUsage")).WithCallbackData(t.encodeQuery("get_usage")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ResetAllTraffics")).WithCallbackData(t.encodeQuery("reset_all_traffics")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.dbBackup")).WithCallbackData(t.encodeQuery("get_backup")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getInbounds")).WithCallbackData(t.encodeQuery("inbounds")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.depleteSoon")).WithCallbackData(t.encodeQuery("deplete_soon")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getBanLogs")).WithCallbackData(t.encodeQuery("get_banlogs")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.allClients")).WithCallbackData(t.encodeQuery("get_inbounds")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.addClient")).WithCallbackData(t.encodeQuery("add_client")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("commands")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithCallbackData(t.encodeQuery("admin_client_tg_user_links")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetTraffic")).WithCallbackData(t.encodeQuery("admin_client_individual_links")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("qrCode")).WithCallbackData(t.encodeQuery("admin_client_qr_links")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
clientKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clientUsage")).WithCallbackData(t.encodeQuery("client_traffic")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("client_commands")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithCallbackData(t.encodeQuery("client_sub_links")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetTraffic")).WithCallbackData(t.encodeQuery("client_individual_links")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("qrCode")).WithCallbackData(t.encodeQuery("client_qr_links")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if isAdmin {
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg, numericKeyboard)
|
|
|
|
|
} else {
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg, clientKeyboard)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// answerCommand processes incoming command messages from Telegram users.
|
2023-05-14 15:20:01 +00:00
|
|
|
func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) {
|
2023-05-30 22:01:00 +00:00
|
|
|
msg, onlyMessage := "", false
|
2023-05-14 15:20:01 +00:00
|
|
|
|
2023-10-25 19:33:17 +00:00
|
|
|
command, _, commandArgs := tu.ParseCommand(message.Text)
|
2023-05-14 15:20:01 +00:00
|
|
|
|
2024-10-16 12:39:25 +00:00
|
|
|
// Helper function to handle unknown commands.
|
|
|
|
|
handleUnknownCommand := func() {
|
|
|
|
|
msg += t.I18nBot("tgbot.commands.unknown")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle the command.
|
2023-05-14 15:20:01 +00:00
|
|
|
switch command {
|
2023-03-17 16:07:49 +00:00
|
|
|
case "help":
|
2023-05-20 23:00:26 +00:00
|
|
|
msg += t.I18nBot("tgbot.commands.help")
|
|
|
|
|
msg += t.I18nBot("tgbot.commands.pleaseChoose")
|
2023-03-17 16:07:49 +00:00
|
|
|
case "start":
|
2026-03-04 10:35:24 +00:00
|
|
|
msg += t.I18nBot("tgbot.commands.start", "Firstname=="+html.EscapeString(message.From.FirstName))
|
2023-03-17 16:07:49 +00:00
|
|
|
if isAdmin {
|
2023-05-20 23:00:26 +00:00
|
|
|
msg += t.I18nBot("tgbot.commands.welcome", "Hostname=="+hostname)
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2023-05-20 23:00:26 +00:00
|
|
|
msg += "\n\n" + t.I18nBot("tgbot.commands.pleaseChoose")
|
2023-03-17 16:07:49 +00:00
|
|
|
case "status":
|
2023-05-30 22:01:00 +00:00
|
|
|
onlyMessage = true
|
2023-05-20 23:00:26 +00:00
|
|
|
msg += t.I18nBot("tgbot.commands.status")
|
2023-05-30 22:01:00 +00:00
|
|
|
case "id":
|
|
|
|
|
onlyMessage = true
|
|
|
|
|
msg += t.I18nBot("tgbot.commands.getID", "ID=="+strconv.FormatInt(message.From.ID, 10))
|
2023-03-17 16:07:49 +00:00
|
|
|
case "usage":
|
2023-05-30 22:01:00 +00:00
|
|
|
onlyMessage = true
|
2023-05-14 15:20:01 +00:00
|
|
|
if len(commandArgs) > 0 {
|
2023-03-24 13:10:56 +00:00
|
|
|
if isAdmin {
|
2023-05-14 15:20:01 +00:00
|
|
|
t.searchClient(chatId, commandArgs[0])
|
2023-03-24 13:10:56 +00:00
|
|
|
} else {
|
2024-10-16 12:39:25 +00:00
|
|
|
t.getClientUsage(chatId, int64(message.From.ID), commandArgs[0])
|
2023-03-24 13:10:56 +00:00
|
|
|
}
|
2023-03-17 16:07:49 +00:00
|
|
|
} else {
|
2023-05-20 23:00:26 +00:00
|
|
|
msg += t.I18nBot("tgbot.commands.usage")
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2023-03-24 13:44:26 +00:00
|
|
|
case "inbound":
|
2023-05-30 22:01:00 +00:00
|
|
|
onlyMessage = true
|
2023-05-14 15:20:01 +00:00
|
|
|
if isAdmin && len(commandArgs) > 0 {
|
|
|
|
|
t.searchInbound(chatId, commandArgs[0])
|
2023-03-24 13:44:26 +00:00
|
|
|
} else {
|
2024-10-16 12:39:25 +00:00
|
|
|
handleUnknownCommand()
|
|
|
|
|
}
|
|
|
|
|
case "restart":
|
|
|
|
|
onlyMessage = true
|
|
|
|
|
if isAdmin {
|
|
|
|
|
if len(commandArgs) == 0 {
|
|
|
|
|
if t.xrayService.IsXrayRunning() {
|
|
|
|
|
err := t.xrayService.RestartXray(true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
msg += t.I18nBot("tgbot.commands.restartFailed", "Error=="+err.Error())
|
|
|
|
|
} else {
|
|
|
|
|
msg += t.I18nBot("tgbot.commands.restartSuccess")
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
msg += t.I18nBot("tgbot.commands.xrayNotRunning")
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
handleUnknownCommand()
|
|
|
|
|
msg += t.I18nBot("tgbot.commands.restartUsage")
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
handleUnknownCommand()
|
2023-03-24 13:44:26 +00:00
|
|
|
}
|
2023-03-17 16:07:49 +00:00
|
|
|
default:
|
2024-10-16 12:39:25 +00:00
|
|
|
handleUnknownCommand()
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2023-05-30 22:01:00 +00:00
|
|
|
|
2024-01-29 20:06:03 +00:00
|
|
|
if msg != "" {
|
2024-10-16 12:39:25 +00:00
|
|
|
t.sendResponse(chatId, msg, onlyMessage, isAdmin)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// sendResponse sends the response message based on the onlyMessage flag.
|
2024-10-16 12:39:25 +00:00
|
|
|
func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool) {
|
|
|
|
|
if onlyMessage {
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
|
|
|
|
} else {
|
|
|
|
|
t.SendAnswer(chatId, msg, isAdmin)
|
2023-05-30 22:01:00 +00:00
|
|
|
}
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// randomLowerAndNum generates a random string of lowercase letters and numbers.
|
2025-03-26 18:16:35 +00:00
|
|
|
func (t *Tgbot) randomLowerAndNum(length int) string {
|
|
|
|
|
charset := "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
|
|
|
bytes := make([]byte, length)
|
|
|
|
|
for i := range bytes {
|
|
|
|
|
randomIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
|
|
|
|
bytes[i] = charset[randomIndex.Int64()]
|
|
|
|
|
}
|
|
|
|
|
return string(bytes)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// randomShadowSocksPassword generates a random password for Shadowsocks.
|
2025-03-26 18:16:35 +00:00
|
|
|
func (t *Tgbot) randomShadowSocksPassword() string {
|
|
|
|
|
array := make([]byte, 32)
|
|
|
|
|
_, err := rand.Read(array)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return t.randomLowerAndNum(32)
|
|
|
|
|
}
|
|
|
|
|
return base64.StdEncoding.EncodeToString(array)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 07:35:50 +00:00
|
|
|
// answerCallback processes callback queries from inline keyboards.
|
2024-07-07 09:55:59 +00:00
|
|
|
func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
|
2024-02-17 17:45:53 +00:00
|
|
|
chatId := callbackQuery.Message.GetChat().ID
|
2026-05-12 21:25:35 +00:00
|
|
|
state := t.getClientState(chatId)
|
2025-04-06 22:45:52 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
// get query from hash storage
|
|
|
|
|
decodedQuery, err := t.decodeQuery(callbackQuery.Data)
|
2023-05-20 17:16:42 +00:00
|
|
|
if err != nil {
|
2023-05-21 01:03:01 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noQuery"))
|
2023-05-20 17:16:42 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
dataArray := strings.Split(decodedQuery, " ")
|
|
|
|
|
cmd := dataArray[0]
|
|
|
|
|
|
|
|
|
|
if isAdmin {
|
|
|
|
|
switch cmd {
|
|
|
|
|
case "admin_client_sub_links":
|
|
|
|
|
inbounds, _ := t.inboundService.GetAllInbounds()
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(inbound.Remark).WithCallbackData(t.encodeQuery("get_clients_for_sub "+fmt.Sprint(inbound.Id))))
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), tu.InlineKeyboard(tu.InlineKeyboardCols(1, buttons...)...))
|
|
|
|
|
return
|
|
|
|
|
case "admin_client_tg_user_links":
|
|
|
|
|
inbounds, _ := t.inboundService.GetAllInbounds()
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(inbound.Remark).WithCallbackData(t.encodeQuery("get_clients_for_tg_user "+fmt.Sprint(inbound.Id))))
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), tu.InlineKeyboard(tu.InlineKeyboardCols(1, buttons...)...))
|
|
|
|
|
return
|
|
|
|
|
case "admin_client_individual_links":
|
|
|
|
|
inbounds, _ := t.inboundService.GetAllInbounds()
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(inbound.Remark).WithCallbackData(t.encodeQuery("get_clients_for_individual "+fmt.Sprint(inbound.Id))))
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), tu.InlineKeyboard(tu.InlineKeyboardCols(1, buttons...)...))
|
|
|
|
|
return
|
|
|
|
|
case "admin_client_qr_links":
|
|
|
|
|
inbounds, _ := t.inboundService.GetAllInbounds()
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(inbound.Remark).WithCallbackData(t.encodeQuery("get_clients_for_qr "+fmt.Sprint(inbound.Id))))
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), tu.InlineKeyboard(tu.InlineKeyboardCols(1, buttons...)...))
|
|
|
|
|
return
|
|
|
|
|
case "get_inbounds":
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.allClients"))
|
|
|
|
|
inbounds, _ := t.inboundService.GetAllInbounds()
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(inbound.Remark).WithCallbackData(t.encodeQuery("get_clients "+fmt.Sprint(inbound.Id))))
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), tu.InlineKeyboard(tu.InlineKeyboardCols(1, buttons...)...))
|
|
|
|
|
return
|
|
|
|
|
case "get_sorted_traffic_usage_report":
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
emails, err := t.inboundService.getAllEmails()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
traffics := make([]*xray.ClientTraffic, 0, len(emails))
|
|
|
|
|
for _, email := range emails {
|
|
|
|
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
|
|
|
|
if err == nil && traffic != nil {
|
|
|
|
|
traffics = append(traffics, traffic)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(traffics, func(i, j int) bool {
|
|
|
|
|
return traffics[i].Up+traffics[i].Down > traffics[j].Up+traffics[j].Down
|
|
|
|
|
})
|
|
|
|
|
var output strings.Builder
|
|
|
|
|
output.WriteString("📊 " + t.I18nBot("tgbot.buttons.SortedTrafficUsageReport") + ":\n\n")
|
|
|
|
|
for i, traffic := range traffics {
|
|
|
|
|
if i >= 50 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
output.WriteString(t.clientInfoMsg(traffic, true, false, false, false, false, false))
|
|
|
|
|
output.WriteString("\n")
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, output.String())
|
|
|
|
|
return
|
|
|
|
|
case "reset_all_traffics":
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("reset_all_traffics_cancel")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_all_traffics_c")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.AreYouSure"), inlineKeyboard)
|
|
|
|
|
return
|
|
|
|
|
case "reset_all_traffics_c":
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
emails, err := t.inboundService.getAllEmails()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for _, email := range emails {
|
|
|
|
|
_ = t.inboundService.ResetClientTrafficByEmail(email)
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.FinishProcess"), tu.ReplyKeyboardRemove())
|
|
|
|
|
return
|
|
|
|
|
case "reset_all_traffics_cancel":
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
t.SendMsgToTgbotDeleteAfter(chatId, t.I18nBot("tgbot.messages.cancel"), 1, tu.ReplyKeyboardRemove())
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-05-20 15:59:28 +00:00
|
|
|
|
2023-05-04 21:46:43 +00:00
|
|
|
if len(dataArray) >= 2 && len(dataArray[1]) > 0 {
|
|
|
|
|
email := dataArray[1]
|
|
|
|
|
switch dataArray[0] {
|
2025-09-16 11:41:48 +00:00
|
|
|
case "get_clients_for_sub":
|
|
|
|
|
inboundId := dataArray[1]
|
|
|
|
|
inboundIdInt, err := strconv.Atoi(inboundId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
clientsKB, err := t.getInboundClientsFor(inboundIdInt, "client_sub_links")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
inbound, _ := t.inboundService.GetInbound(inboundIdInt)
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clientsKB)
|
2026-05-12 21:25:35 +00:00
|
|
|
case "get_clients_for_tg_user":
|
|
|
|
|
inboundId := dataArray[1]
|
|
|
|
|
inboundIdInt, err := strconv.Atoi(inboundId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
clientsKB, err := t.getInboundClientsFor(inboundIdInt, "tg_user")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
inbound, _ := t.inboundService.GetInbound(inboundIdInt)
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clientsKB)
|
2025-09-16 11:41:48 +00:00
|
|
|
case "get_clients_for_individual":
|
|
|
|
|
inboundId := dataArray[1]
|
|
|
|
|
inboundIdInt, err := strconv.Atoi(inboundId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
clientsKB, err := t.getInboundClientsFor(inboundIdInt, "client_individual_links")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
inbound, _ := t.inboundService.GetInbound(inboundIdInt)
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clientsKB)
|
|
|
|
|
case "get_clients_for_qr":
|
|
|
|
|
inboundId := dataArray[1]
|
|
|
|
|
inboundIdInt, err := strconv.Atoi(inboundId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
clientsKB, err := t.getInboundClientsFor(inboundIdInt, "client_qr_links")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
inbound, _ := t.inboundService.GetInbound(inboundIdInt)
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clientsKB)
|
|
|
|
|
case "client_sub_links":
|
|
|
|
|
t.sendClientSubLinks(chatId, email)
|
|
|
|
|
return
|
|
|
|
|
case "client_individual_links":
|
|
|
|
|
t.sendClientIndividualLinks(chatId, email)
|
|
|
|
|
return
|
|
|
|
|
case "client_qr_links":
|
|
|
|
|
t.sendClientQRLinks(chatId, email)
|
|
|
|
|
return
|
2024-01-01 15:07:56 +00:00
|
|
|
case "client_get_usage":
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.messages.email", "Email=="+email))
|
|
|
|
|
t.searchClient(chatId, email)
|
2023-05-05 14:50:56 +00:00
|
|
|
case "client_refresh":
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-05 14:50:56 +00:00
|
|
|
case "client_cancel":
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-05 14:50:56 +00:00
|
|
|
case "ips_refresh":
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.IpRefreshSuccess", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-05 14:50:56 +00:00
|
|
|
case "ips_cancel":
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-14 19:13:23 +00:00
|
|
|
case "tgid_refresh":
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.TGIdRefreshSuccess", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-14 19:13:23 +00:00
|
|
|
case "tgid_cancel":
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-04 21:46:43 +00:00
|
|
|
case "reset_traffic":
|
2023-05-14 15:20:01 +00:00
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
|
2023-05-04 21:46:43 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic_c "+email)),
|
2023-05-04 21:46:43 +00:00
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2023-05-05 12:32:16 +00:00
|
|
|
case "reset_traffic_c":
|
2023-05-05 01:04:39 +00:00
|
|
|
err := t.inboundService.ResetClientTrafficByEmail(email)
|
|
|
|
|
if err == nil {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetTrafficSuccess", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-04 23:17:26 +00:00
|
|
|
} else {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2023-05-04 23:17:26 +00:00
|
|
|
}
|
2023-11-20 14:17:59 +00:00
|
|
|
case "limit_traffic":
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 0")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" 0")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("1 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 1")),
|
|
|
|
|
tu.InlineKeyboardButton("5 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 5")),
|
|
|
|
|
tu.InlineKeyboardButton("10 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 10")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("20 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 20")),
|
|
|
|
|
tu.InlineKeyboardButton("30 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 30")),
|
|
|
|
|
tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2024-01-29 20:06:03 +00:00
|
|
|
tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 50")),
|
2023-11-20 14:17:59 +00:00
|
|
|
tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
|
|
|
|
tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("100 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 100")),
|
|
|
|
|
tu.InlineKeyboardButton("150 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 150")),
|
|
|
|
|
tu.InlineKeyboardButton("200 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 200")),
|
|
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2023-11-20 14:17:59 +00:00
|
|
|
case "limit_traffic_c":
|
|
|
|
|
if len(dataArray) == 3 {
|
|
|
|
|
limitTraffic, err := strconv.Atoi(dataArray[2])
|
|
|
|
|
if err == nil {
|
2024-03-15 18:13:20 +00:00
|
|
|
needRestart, err := t.inboundService.ResetClientTrafficLimitByEmail(email, limitTraffic)
|
|
|
|
|
if needRestart {
|
2023-11-20 14:17:59 +00:00
|
|
|
t.xrayService.SetToNeedRestart()
|
2024-03-15 18:13:20 +00:00
|
|
|
}
|
|
|
|
|
if err == nil {
|
2023-11-20 14:17:59 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.setTrafficLimitSuccess", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-11-20 14:17:59 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-11-20 14:17:59 +00:00
|
|
|
case "limit_traffic_in":
|
|
|
|
|
if len(dataArray) >= 3 {
|
|
|
|
|
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
|
|
|
|
inputNumber := oldInputNumber
|
|
|
|
|
if err == nil {
|
|
|
|
|
if len(dataArray) == 4 {
|
|
|
|
|
num, err := strconv.Atoi(dataArray[3])
|
|
|
|
|
if err == nil {
|
2025-08-17 11:37:49 +00:00
|
|
|
switch num {
|
|
|
|
|
case -2:
|
2023-11-20 14:17:59 +00:00
|
|
|
inputNumber = 0
|
2025-08-17 11:37:49 +00:00
|
|
|
case -1:
|
2023-11-20 14:17:59 +00:00
|
|
|
if inputNumber > 0 {
|
2024-01-01 15:07:56 +00:00
|
|
|
inputNumber = (inputNumber / 10)
|
2023-11-20 14:17:59 +00:00
|
|
|
}
|
2025-08-17 11:37:49 +00:00
|
|
|
default:
|
2023-11-20 14:17:59 +00:00
|
|
|
inputNumber = (inputNumber * 10) + num
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if inputNumber == oldInputNumber {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if inputNumber >= 999999 {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2024-01-01 15:07:56 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumberAdd", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" "+strconv.Itoa(inputNumber))),
|
2023-11-20 14:17:59 +00:00
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 1")),
|
|
|
|
|
tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 2")),
|
|
|
|
|
tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 3")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 4")),
|
|
|
|
|
tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 5")),
|
|
|
|
|
tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 6")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 7")),
|
|
|
|
|
tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 8")),
|
|
|
|
|
tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 9")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" -2")),
|
|
|
|
|
tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 0")),
|
|
|
|
|
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
|
|
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2023-11-20 14:17:59 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2025-03-26 18:16:35 +00:00
|
|
|
case "add_client_limit_traffic_c":
|
2025-12-03 13:58:54 +00:00
|
|
|
limitTraffic, _ := strconv.ParseInt(dataArray[1], 10, 64)
|
2026-05-12 21:25:35 +00:00
|
|
|
state.TotalGB = limitTraffic * 1024 * 1024 * 1024
|
2025-03-26 18:16:35 +00:00
|
|
|
messageId := callbackQuery.Message.GetMessageID()
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, err := t.inboundService.GetInbound(state.ReceiverInboundID)
|
2025-03-26 18:16:35 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
message_text, err := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-08-17 11:37:49 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2025-08-08 18:41:06 +00:00
|
|
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
2025-03-26 18:16:35 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
case "add_client_limit_traffic_in":
|
|
|
|
|
if len(dataArray) >= 2 {
|
|
|
|
|
oldInputNumber, err := strconv.Atoi(dataArray[1])
|
|
|
|
|
inputNumber := oldInputNumber
|
|
|
|
|
if err == nil {
|
|
|
|
|
if len(dataArray) == 3 {
|
|
|
|
|
num, err := strconv.Atoi(dataArray[2])
|
|
|
|
|
if err == nil {
|
2025-08-17 11:37:49 +00:00
|
|
|
switch num {
|
|
|
|
|
case -2:
|
2025-03-26 18:16:35 +00:00
|
|
|
inputNumber = 0
|
2025-08-17 11:37:49 +00:00
|
|
|
case -1:
|
2025-03-26 18:16:35 +00:00
|
|
|
if inputNumber > 0 {
|
|
|
|
|
inputNumber = (inputNumber / 10)
|
|
|
|
|
}
|
2025-08-17 11:37:49 +00:00
|
|
|
default:
|
2025-03-26 18:16:35 +00:00
|
|
|
inputNumber = (inputNumber * 10) + num
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if inputNumber == oldInputNumber {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if inputNumber >= 999999 {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_traffic_exp")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumberAdd", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("add_client_limit_traffic_c "+strconv.Itoa(inputNumber))),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 1")),
|
|
|
|
|
tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 2")),
|
|
|
|
|
tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 3")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 4")),
|
|
|
|
|
tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 5")),
|
|
|
|
|
tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 6")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 7")),
|
|
|
|
|
tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 8")),
|
|
|
|
|
tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 9")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" -2")),
|
|
|
|
|
tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" 0")),
|
|
|
|
|
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("add_client_limit_traffic_in "+strconv.Itoa(inputNumber)+" -1")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-05 12:32:16 +00:00
|
|
|
case "reset_exp":
|
2023-05-20 23:00:26 +00:00
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
|
2023-05-04 21:46:43 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 0")),
|
2023-11-20 14:17:59 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("reset_exp_in "+email+" 0")),
|
2023-05-04 22:18:37 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2024-01-01 15:07:56 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 7 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 7")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 10 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 10")),
|
2023-05-04 21:46:43 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2024-01-01 15:07:56 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 14 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 14")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 20 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 20")),
|
2023-05-04 21:46:43 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2024-01-01 15:07:56 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 1 "+t.I18nBot("tgbot.month")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 30")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 3 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 90")),
|
2023-05-04 21:46:43 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2024-01-01 15:07:56 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 6 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 180")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 365")),
|
2023-05-04 21:46:43 +00:00
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2023-05-05 12:32:16 +00:00
|
|
|
case "reset_exp_c":
|
2023-05-05 01:04:39 +00:00
|
|
|
if len(dataArray) == 3 {
|
2025-12-03 13:58:54 +00:00
|
|
|
days, err := strconv.ParseInt(dataArray[2], 10, 64)
|
2023-05-05 01:04:39 +00:00
|
|
|
if err == nil {
|
2025-09-19 08:46:49 +00:00
|
|
|
var date int64
|
2023-05-04 22:18:37 +00:00
|
|
|
if days > 0 {
|
2024-01-01 15:07:56 +00:00
|
|
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Warning(err)
|
|
|
|
|
msg := t.I18nBot("tgbot.wentWrong")
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if traffic == nil {
|
|
|
|
|
msg := t.I18nBot("tgbot.noResult")
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if traffic.ExpiryTime > 0 {
|
|
|
|
|
if traffic.ExpiryTime-time.Now().Unix()*1000 < 0 {
|
|
|
|
|
date = -int64(days * 24 * 60 * 60000)
|
|
|
|
|
} else {
|
|
|
|
|
date = traffic.ExpiryTime + int64(days*24*60*60000)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
date = traffic.ExpiryTime - int64(days*24*60*60000)
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-04 22:18:37 +00:00
|
|
|
}
|
2024-03-15 18:13:20 +00:00
|
|
|
needRestart, err := t.inboundService.ResetClientExpiryTimeByEmail(email, date)
|
|
|
|
|
if needRestart {
|
2023-05-04 23:17:26 +00:00
|
|
|
t.xrayService.SetToNeedRestart()
|
2024-03-15 18:13:20 +00:00
|
|
|
}
|
|
|
|
|
if err == nil {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.expireResetSuccess", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-05 01:04:39 +00:00
|
|
|
return
|
2023-05-04 23:17:26 +00:00
|
|
|
}
|
2023-05-04 21:46:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-11-20 14:17:59 +00:00
|
|
|
case "reset_exp_in":
|
|
|
|
|
if len(dataArray) >= 3 {
|
|
|
|
|
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
|
|
|
|
inputNumber := oldInputNumber
|
|
|
|
|
if err == nil {
|
|
|
|
|
if len(dataArray) == 4 {
|
|
|
|
|
num, err := strconv.Atoi(dataArray[3])
|
|
|
|
|
if err == nil {
|
2025-08-17 11:37:49 +00:00
|
|
|
switch num {
|
|
|
|
|
case -2:
|
2023-11-20 14:17:59 +00:00
|
|
|
inputNumber = 0
|
2025-08-17 11:37:49 +00:00
|
|
|
case -1:
|
2023-11-20 14:17:59 +00:00
|
|
|
if inputNumber > 0 {
|
2024-01-01 15:07:56 +00:00
|
|
|
inputNumber = (inputNumber / 10)
|
2023-11-20 14:17:59 +00:00
|
|
|
}
|
2025-08-17 11:37:49 +00:00
|
|
|
default:
|
2023-11-20 14:17:59 +00:00
|
|
|
inputNumber = (inputNumber * 10) + num
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if inputNumber == oldInputNumber {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if inputNumber >= 999999 {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumber", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" "+strconv.Itoa(inputNumber))),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 1")),
|
|
|
|
|
tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 2")),
|
|
|
|
|
tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 3")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 4")),
|
|
|
|
|
tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 5")),
|
|
|
|
|
tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 6")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 7")),
|
|
|
|
|
tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 8")),
|
|
|
|
|
tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 9")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" -2")),
|
|
|
|
|
tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 0")),
|
|
|
|
|
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
|
|
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2023-11-20 14:17:59 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2025-03-26 18:16:35 +00:00
|
|
|
case "add_client_reset_exp_c":
|
2026-05-12 21:25:35 +00:00
|
|
|
state.ExpiryTime = 0
|
2025-12-03 13:58:54 +00:00
|
|
|
days, _ := strconv.ParseInt(dataArray[1], 10, 64)
|
2025-09-19 08:46:49 +00:00
|
|
|
var date int64
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.ExpiryTime > 0 {
|
|
|
|
|
if state.ExpiryTime-time.Now().Unix()*1000 < 0 {
|
2025-03-26 18:16:35 +00:00
|
|
|
date = -int64(days * 24 * 60 * 60000)
|
|
|
|
|
} else {
|
2026-05-12 21:25:35 +00:00
|
|
|
date = state.ExpiryTime + int64(days*24*60*60000)
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
2026-05-12 21:25:35 +00:00
|
|
|
date = state.ExpiryTime - int64(days*24*60*60000)
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
state.ExpiryTime = date
|
2025-04-06 22:45:52 +00:00
|
|
|
|
2025-03-26 18:16:35 +00:00
|
|
|
messageId := callbackQuery.Message.GetMessageID()
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, err := t.inboundService.GetInbound(state.ReceiverInboundID)
|
2025-03-26 18:16:35 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
message_text, err := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-08-17 11:37:49 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2025-08-08 18:41:06 +00:00
|
|
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
2025-03-26 18:16:35 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
case "add_client_reset_exp_in":
|
|
|
|
|
if len(dataArray) >= 2 {
|
|
|
|
|
oldInputNumber, err := strconv.Atoi(dataArray[1])
|
|
|
|
|
inputNumber := oldInputNumber
|
|
|
|
|
if err == nil {
|
|
|
|
|
if len(dataArray) == 3 {
|
|
|
|
|
num, err := strconv.Atoi(dataArray[2])
|
|
|
|
|
if err == nil {
|
2025-08-17 11:37:49 +00:00
|
|
|
switch num {
|
|
|
|
|
case -2:
|
2025-03-26 18:16:35 +00:00
|
|
|
inputNumber = 0
|
2025-08-17 11:37:49 +00:00
|
|
|
case -1:
|
2025-03-26 18:16:35 +00:00
|
|
|
if inputNumber > 0 {
|
|
|
|
|
inputNumber = (inputNumber / 10)
|
|
|
|
|
}
|
2025-08-17 11:37:49 +00:00
|
|
|
default:
|
2025-03-26 18:16:35 +00:00
|
|
|
inputNumber = (inputNumber * 10) + num
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if inputNumber == oldInputNumber {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if inputNumber >= 999999 {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_traffic_exp")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2025-05-28 08:26:29 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumberAdd", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("add_client_reset_exp_c "+strconv.Itoa(inputNumber))),
|
2025-03-26 18:16:35 +00:00
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 1")),
|
|
|
|
|
tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 2")),
|
|
|
|
|
tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 3")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 4")),
|
|
|
|
|
tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 5")),
|
|
|
|
|
tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 6")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 7")),
|
|
|
|
|
tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 8")),
|
|
|
|
|
tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 9")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" -2")),
|
|
|
|
|
tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" 0")),
|
|
|
|
|
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("add_client_reset_exp_in "+strconv.Itoa(inputNumber)+" -1")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-05 14:50:56 +00:00
|
|
|
case "ip_limit":
|
2023-05-14 15:20:01 +00:00
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelIpLimit")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
|
2023-05-05 14:50:56 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 0")),
|
2023-11-20 14:17:59 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("ip_limit_in "+email+" 0")),
|
2023-05-05 14:50:56 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 1")),
|
|
|
|
|
tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 2")),
|
2023-05-05 14:50:56 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 3")),
|
|
|
|
|
tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 4")),
|
2023-05-05 14:50:56 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 5")),
|
|
|
|
|
tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 6")),
|
|
|
|
|
tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 7")),
|
2023-05-05 14:50:56 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 8")),
|
|
|
|
|
tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 9")),
|
|
|
|
|
tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 10")),
|
2023-05-05 14:50:56 +00:00
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2023-05-05 14:50:56 +00:00
|
|
|
case "ip_limit_c":
|
|
|
|
|
if len(dataArray) == 3 {
|
|
|
|
|
count, err := strconv.Atoi(dataArray[2])
|
|
|
|
|
if err == nil {
|
2024-03-15 18:13:20 +00:00
|
|
|
needRestart, err := t.inboundService.ResetClientIpLimitByEmail(email, count)
|
|
|
|
|
if needRestart {
|
2023-05-05 14:50:56 +00:00
|
|
|
t.xrayService.SetToNeedRestart()
|
2024-03-15 18:13:20 +00:00
|
|
|
}
|
|
|
|
|
if err == nil {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetIpSuccess", "Email=="+email, "Count=="+strconv.Itoa(count)))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-05 14:50:56 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-11-20 14:17:59 +00:00
|
|
|
case "ip_limit_in":
|
|
|
|
|
if len(dataArray) >= 3 {
|
|
|
|
|
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
|
|
|
|
inputNumber := oldInputNumber
|
|
|
|
|
if err == nil {
|
|
|
|
|
if len(dataArray) == 4 {
|
|
|
|
|
num, err := strconv.Atoi(dataArray[3])
|
|
|
|
|
if err == nil {
|
2025-08-17 11:37:49 +00:00
|
|
|
switch num {
|
|
|
|
|
case -2:
|
2023-11-20 14:17:59 +00:00
|
|
|
inputNumber = 0
|
2025-08-17 11:37:49 +00:00
|
|
|
case -1:
|
2023-11-20 14:17:59 +00:00
|
|
|
if inputNumber > 0 {
|
2024-01-01 15:07:56 +00:00
|
|
|
inputNumber = (inputNumber / 10)
|
2023-11-20 14:17:59 +00:00
|
|
|
}
|
2025-08-17 11:37:49 +00:00
|
|
|
default:
|
2023-11-20 14:17:59 +00:00
|
|
|
inputNumber = (inputNumber * 10) + num
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if inputNumber == oldInputNumber {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if inputNumber >= 999999 {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumber", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("ip_limit_c "+email+" "+strconv.Itoa(inputNumber))),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 1")),
|
|
|
|
|
tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 2")),
|
|
|
|
|
tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 3")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 4")),
|
|
|
|
|
tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 5")),
|
|
|
|
|
tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 6")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 7")),
|
|
|
|
|
tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 8")),
|
|
|
|
|
tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 9")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" -2")),
|
|
|
|
|
tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 0")),
|
|
|
|
|
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
|
|
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2023-11-20 14:17:59 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2025-05-06 16:27:17 +00:00
|
|
|
case "add_client_ip_limit_c":
|
|
|
|
|
if len(dataArray) == 2 {
|
|
|
|
|
count, _ := strconv.Atoi(dataArray[1])
|
2026-05-12 21:25:35 +00:00
|
|
|
state.LimitIP = count
|
2025-05-06 16:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
messageId := callbackQuery.Message.GetMessageID()
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, err := t.inboundService.GetInbound(state.ReceiverInboundID)
|
2025-05-06 16:27:17 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
message_text, err := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-08-17 11:37:49 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-05-06 16:27:17 +00:00
|
|
|
|
2025-08-08 18:41:06 +00:00
|
|
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
2025-05-06 16:27:17 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
case "add_client_ip_limit_in":
|
|
|
|
|
if len(dataArray) >= 2 {
|
|
|
|
|
oldInputNumber, err := strconv.Atoi(dataArray[1])
|
|
|
|
|
inputNumber := oldInputNumber
|
|
|
|
|
if err == nil {
|
|
|
|
|
if len(dataArray) == 3 {
|
|
|
|
|
num, err := strconv.Atoi(dataArray[2])
|
|
|
|
|
if err == nil {
|
2025-08-17 11:37:49 +00:00
|
|
|
switch num {
|
|
|
|
|
case -2:
|
2025-05-06 16:27:17 +00:00
|
|
|
inputNumber = 0
|
2025-08-17 11:37:49 +00:00
|
|
|
case -1:
|
2025-05-06 16:27:17 +00:00
|
|
|
if inputNumber > 0 {
|
|
|
|
|
inputNumber = (inputNumber / 10)
|
|
|
|
|
}
|
2025-08-17 11:37:49 +00:00
|
|
|
default:
|
2025-05-06 16:27:17 +00:00
|
|
|
inputNumber = (inputNumber * 10) + num
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if inputNumber == oldInputNumber {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if inputNumber >= 999999 {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_ip_limit")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumber", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("add_client_ip_limit_c "+strconv.Itoa(inputNumber))),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 1")),
|
|
|
|
|
tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 2")),
|
|
|
|
|
tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 3")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 4")),
|
|
|
|
|
tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 5")),
|
|
|
|
|
tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 6")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 7")),
|
|
|
|
|
tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 8")),
|
|
|
|
|
tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 9")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" -2")),
|
|
|
|
|
tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 0")),
|
|
|
|
|
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" -1")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-05 14:50:56 +00:00
|
|
|
case "clear_ips":
|
2023-05-14 15:20:01 +00:00
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("ips_cancel "+email)),
|
2023-05-05 14:50:56 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmClearIps")).WithCallbackData(t.encodeQuery("clear_ips_c "+email)),
|
2023-05-05 14:50:56 +00:00
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2023-05-05 14:50:56 +00:00
|
|
|
case "clear_ips_c":
|
|
|
|
|
err := t.inboundService.ClearClientIps(email)
|
|
|
|
|
if err == nil {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clearIpSuccess", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-05 14:50:56 +00:00
|
|
|
} else {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2023-05-05 14:50:56 +00:00
|
|
|
}
|
|
|
|
|
case "ip_log":
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getIpLog", "Email=="+email))
|
2023-05-14 15:20:01 +00:00
|
|
|
t.searchClientIps(chatId, email)
|
2023-05-14 18:37:49 +00:00
|
|
|
case "tg_user":
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getUserInfo", "Email=="+email))
|
2023-05-14 18:37:49 +00:00
|
|
|
t.clientTelegramUserInfo(chatId, email)
|
2023-05-14 19:13:23 +00:00
|
|
|
case "tgid_remove":
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("tgid_cancel "+email)),
|
2023-05-14 19:13:23 +00:00
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2023-05-21 04:03:08 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmRemoveTGUser")).WithCallbackData(t.encodeQuery("tgid_remove_c "+email)),
|
2023-05-14 19:13:23 +00:00
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2023-05-14 19:13:23 +00:00
|
|
|
case "tgid_remove_c":
|
|
|
|
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
|
|
|
|
if err != nil || traffic == nil {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2023-05-14 19:13:23 +00:00
|
|
|
return
|
|
|
|
|
}
|
2024-04-02 11:34:44 +00:00
|
|
|
needRestart, err := t.inboundService.SetClientTelegramUserID(traffic.Id, EmptyTelegramUserID)
|
2024-03-15 18:13:20 +00:00
|
|
|
if needRestart {
|
|
|
|
|
t.xrayService.SetToNeedRestart()
|
|
|
|
|
}
|
2023-05-14 19:13:23 +00:00
|
|
|
if err == nil {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.removedTGUserSuccess", "Email=="+email))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-14 19:13:23 +00:00
|
|
|
} else {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2023-05-14 19:13:23 +00:00
|
|
|
}
|
2023-05-05 14:50:56 +00:00
|
|
|
case "toggle_enable":
|
2024-01-01 15:07:56 +00:00
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmToggle")).WithCallbackData(t.encodeQuery("toggle_enable_c "+email)),
|
|
|
|
|
),
|
|
|
|
|
)
|
2024-02-17 17:45:53 +00:00
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2024-01-01 15:07:56 +00:00
|
|
|
case "toggle_enable_c":
|
2024-03-15 18:13:20 +00:00
|
|
|
enabled, needRestart, err := t.inboundService.ToggleClientEnableByEmail(email)
|
|
|
|
|
if needRestart {
|
2023-05-05 14:50:56 +00:00
|
|
|
t.xrayService.SetToNeedRestart()
|
2024-03-15 18:13:20 +00:00
|
|
|
}
|
|
|
|
|
if err == nil {
|
2023-05-05 16:20:40 +00:00
|
|
|
if enabled {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.enableSuccess", "Email=="+email))
|
2023-05-05 14:50:56 +00:00
|
|
|
} else {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.disableSuccess", "Email=="+email))
|
2023-05-05 14:50:56 +00:00
|
|
|
}
|
2024-02-17 17:45:53 +00:00
|
|
|
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
2023-05-05 14:50:56 +00:00
|
|
|
} else {
|
2023-05-20 23:00:26 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
2023-05-05 14:50:56 +00:00
|
|
|
}
|
2024-08-18 21:30:56 +00:00
|
|
|
case "get_clients":
|
|
|
|
|
inboundId := dataArray[1]
|
|
|
|
|
inboundIdInt, err := strconv.Atoi(inboundId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
inbound, err := t.inboundService.GetInbound(inboundIdInt)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
clients, err := t.getInboundClients(inboundIdInt)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clients)
|
2025-03-26 18:16:35 +00:00
|
|
|
case "add_client_to":
|
|
|
|
|
// assign default values to clients variables
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Id = uuid.New().String()
|
|
|
|
|
state.Flow = ""
|
|
|
|
|
state.Email = t.randomLowerAndNum(8)
|
|
|
|
|
state.LimitIP = 0
|
|
|
|
|
state.TotalGB = 0
|
|
|
|
|
state.ExpiryTime = 0
|
|
|
|
|
state.Enable = true
|
|
|
|
|
state.TgID = ""
|
|
|
|
|
state.SubID = t.randomLowerAndNum(16)
|
|
|
|
|
state.Comment = ""
|
|
|
|
|
state.Reset = 0
|
|
|
|
|
state.Security = "auto"
|
|
|
|
|
state.ShPassword = t.randomShadowSocksPassword()
|
|
|
|
|
state.TrPassword = t.randomLowerAndNum(10)
|
|
|
|
|
state.Method = ""
|
2024-08-18 21:30:56 +00:00
|
|
|
|
2025-03-26 18:16:35 +00:00
|
|
|
inboundId := dataArray[1]
|
|
|
|
|
inboundIdInt, err := strconv.Atoi(inboundId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
state.ReceiverInboundID = inboundIdInt
|
2025-03-26 18:16:35 +00:00
|
|
|
inbound, err := t.inboundService.GetInbound(inboundIdInt)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-04-06 22:45:52 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
message_text, err := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-08-17 11:37:49 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-04-06 22:45:52 +00:00
|
|
|
|
2025-08-08 18:41:06 +00:00
|
|
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text)
|
2023-05-04 21:46:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
switch cmd {
|
2023-03-17 16:07:49 +00:00
|
|
|
case "get_usage":
|
2024-01-01 15:07:56 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.serverUsage"))
|
2024-05-14 12:00:10 +00:00
|
|
|
t.getServerUsage(chatId)
|
|
|
|
|
case "usage_refresh":
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
t.getServerUsage(chatId, callbackQuery.Message.GetMessageID())
|
2023-03-17 16:07:49 +00:00
|
|
|
case "inbounds":
|
2024-01-01 15:07:56 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getInbounds"))
|
2023-05-14 15:20:01 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.getInboundUsages())
|
2023-04-09 19:43:18 +00:00
|
|
|
case "deplete_soon":
|
2024-01-01 15:07:56 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.depleteSoon"))
|
|
|
|
|
t.getExhausted(chatId)
|
2023-03-17 16:07:49 +00:00
|
|
|
case "get_backup":
|
2024-01-01 15:07:56 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.dbBackup"))
|
2023-05-14 15:20:01 +00:00
|
|
|
t.sendBackup(chatId)
|
2024-01-01 15:07:56 +00:00
|
|
|
case "get_banlogs":
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getBanLogs"))
|
|
|
|
|
t.sendBanLogs(chatId, true)
|
2023-03-17 16:07:49 +00:00
|
|
|
case "client_traffic":
|
2024-04-02 11:34:44 +00:00
|
|
|
tgUserID := callbackQuery.From.ID
|
2024-01-01 15:07:56 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.clientUsage"))
|
2024-04-02 11:34:44 +00:00
|
|
|
t.getClientUsage(chatId, tgUserID)
|
2023-03-17 16:07:49 +00:00
|
|
|
case "client_commands":
|
2024-01-01 15:07:56 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
2023-05-20 23:00:26 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpClientCommands"))
|
2025-09-14 17:51:57 +00:00
|
|
|
case "client_sub_links":
|
|
|
|
|
// show user's own clients to choose one for sub links
|
|
|
|
|
tgUserID := callbackQuery.From.ID
|
|
|
|
|
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// fallback to message
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if len(traffics) == 0 {
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10)))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, tr := range traffics {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(tr.Email).WithCallbackData(t.encodeQuery("client_sub_links "+tr.Email)))
|
|
|
|
|
}
|
|
|
|
|
cols := 1
|
|
|
|
|
if len(buttons) >= 6 {
|
|
|
|
|
cols = 2
|
|
|
|
|
}
|
|
|
|
|
keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), keyboard)
|
|
|
|
|
case "client_individual_links":
|
|
|
|
|
// show user's clients to choose for individual links
|
|
|
|
|
tgUserID := callbackQuery.From.ID
|
|
|
|
|
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if len(traffics) == 0 {
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10)))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var buttons2 []telego.InlineKeyboardButton
|
|
|
|
|
for _, tr := range traffics {
|
|
|
|
|
buttons2 = append(buttons2, tu.InlineKeyboardButton(tr.Email).WithCallbackData(t.encodeQuery("client_individual_links "+tr.Email)))
|
|
|
|
|
}
|
|
|
|
|
cols2 := 1
|
|
|
|
|
if len(buttons2) >= 6 {
|
|
|
|
|
cols2 = 2
|
|
|
|
|
}
|
|
|
|
|
keyboard2 := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols2, buttons2...))
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), keyboard2)
|
|
|
|
|
case "client_qr_links":
|
|
|
|
|
// show user's clients to choose for QR codes
|
|
|
|
|
tgUserID := callbackQuery.From.ID
|
|
|
|
|
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
|
|
|
|
|
if err != nil {
|
2026-05-12 21:25:35 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
|
2025-09-14 17:51:57 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if len(traffics) == 0 {
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10)))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var buttons3 []telego.InlineKeyboardButton
|
|
|
|
|
for _, tr := range traffics {
|
|
|
|
|
buttons3 = append(buttons3, tu.InlineKeyboardButton(tr.Email).WithCallbackData(t.encodeQuery("client_qr_links "+tr.Email)))
|
|
|
|
|
}
|
|
|
|
|
cols3 := 1
|
|
|
|
|
if len(buttons3) >= 6 {
|
|
|
|
|
cols3 = 2
|
|
|
|
|
}
|
|
|
|
|
keyboard3 := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols3, buttons3...))
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), keyboard3)
|
2024-01-01 15:07:56 +00:00
|
|
|
case "onlines":
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.onlines"))
|
|
|
|
|
t.onlineClients(chatId)
|
|
|
|
|
case "onlines_refresh":
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
2024-02-17 17:45:53 +00:00
|
|
|
t.onlineClients(chatId, callbackQuery.Message.GetMessageID())
|
2023-03-17 16:07:49 +00:00
|
|
|
case "commands":
|
2024-01-01 15:07:56 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
2023-05-20 23:00:26 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
2025-03-26 18:16:35 +00:00
|
|
|
case "add_client":
|
|
|
|
|
// assign default values to clients variables
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Id = uuid.New().String()
|
|
|
|
|
state.Flow = ""
|
|
|
|
|
state.Email = t.randomLowerAndNum(8)
|
|
|
|
|
state.LimitIP = 0
|
|
|
|
|
state.TotalGB = 0
|
|
|
|
|
state.ExpiryTime = 0
|
|
|
|
|
state.Enable = true
|
|
|
|
|
state.TgID = ""
|
|
|
|
|
state.SubID = t.randomLowerAndNum(16)
|
|
|
|
|
state.Comment = ""
|
|
|
|
|
state.Reset = 0
|
|
|
|
|
state.Security = "auto"
|
|
|
|
|
state.ShPassword = t.randomShadowSocksPassword()
|
|
|
|
|
state.TrPassword = t.randomLowerAndNum(10)
|
|
|
|
|
state.Method = ""
|
2025-03-26 18:16:35 +00:00
|
|
|
|
|
|
|
|
inbounds, err := t.getInboundsAddClient()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.addClient"))
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds)
|
|
|
|
|
case "add_client_ch_default_email":
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
userStates[chatId] = "awaiting_email"
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
2026-05-12 21:25:35 +00:00
|
|
|
prompt_message := t.I18nBot("tgbot.messages.email_prompt", "ClientEmail=="+state.Email)
|
2025-03-26 18:16:35 +00:00
|
|
|
t.SendMsgToTgbot(chatId, prompt_message, cancel_btn_markup)
|
2026-05-12 00:52:02 +00:00
|
|
|
case "add_client_ch_default_subid":
|
2026-03-03 23:41:13 +00:00
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
userStates[chatId] = "awaiting_subid"
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
2026-05-12 21:25:35 +00:00
|
|
|
prompt_message := t.I18nBot("tgbot.messages.subid_prompt", "ClientSubId=="+state.SubID)
|
2026-03-03 23:41:13 +00:00
|
|
|
t.SendMsgToTgbot(chatId, prompt_message, cancel_btn_markup)
|
|
|
|
|
|
|
|
|
|
case "add_client_ch_default_flow":
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("xtls-rprx-vision").WithCallbackData("set_flow_vision"),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 21:25:35 +00:00
|
|
|
tu.InlineKeyboardButton("xtls-rprx-vision_udp443").WithCallbackData("set_flow_vision_udp443"),
|
2026-03-03 23:41:13 +00:00
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2026-03-04 20:07:58 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.flow_none")).WithCallbackData("set_flow_none"),
|
2026-03-03 23:41:13 +00:00
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
|
|
|
|
|
|
|
|
|
case "set_flow_vision":
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Flow = "xtls-rprx-vision"
|
2026-03-04 20:07:58 +00:00
|
|
|
messageId := callbackQuery.Message.GetMessageID()
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2026-03-04 20:07:58 +00:00
|
|
|
t.addClient(chatId, message_text, messageId)
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
2026-03-03 23:41:13 +00:00
|
|
|
|
|
|
|
|
case "set_flow_vision_udp443":
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Flow = "xtls-rprx-vision-udp443"
|
2026-03-04 20:07:58 +00:00
|
|
|
messageId := callbackQuery.Message.GetMessageID()
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2026-03-04 20:07:58 +00:00
|
|
|
t.addClient(chatId, message_text, messageId)
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
2026-03-03 23:41:13 +00:00
|
|
|
|
2026-03-04 20:07:58 +00:00
|
|
|
case "set_flow_none":
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Flow = ""
|
2026-03-04 20:07:58 +00:00
|
|
|
messageId := callbackQuery.Message.GetMessageID()
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2026-03-04 20:07:58 +00:00
|
|
|
t.addClient(chatId, message_text, messageId)
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
2025-03-26 18:16:35 +00:00
|
|
|
case "add_client_ch_default_id":
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
userStates[chatId] = "awaiting_id"
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
2026-05-12 21:25:35 +00:00
|
|
|
prompt_message := t.I18nBot("tgbot.messages.id_prompt", "ClientId=="+state.Id)
|
2025-03-26 18:16:35 +00:00
|
|
|
t.SendMsgToTgbot(chatId, prompt_message, cancel_btn_markup)
|
|
|
|
|
case "add_client_ch_default_pass_tr":
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
userStates[chatId] = "awaiting_password_tr"
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
2026-05-12 21:25:35 +00:00
|
|
|
prompt_message := t.I18nBot("tgbot.messages.pass_prompt", "ClientPassword=="+state.TrPassword)
|
2025-03-26 18:16:35 +00:00
|
|
|
t.SendMsgToTgbot(chatId, prompt_message, cancel_btn_markup)
|
|
|
|
|
case "add_client_ch_default_pass_sh":
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
userStates[chatId] = "awaiting_password_sh"
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
2026-05-12 21:25:35 +00:00
|
|
|
prompt_message := t.I18nBot("tgbot.messages.pass_prompt", "ClientPassword=="+state.ShPassword)
|
2025-03-26 18:16:35 +00:00
|
|
|
t.SendMsgToTgbot(chatId, prompt_message, cancel_btn_markup)
|
|
|
|
|
case "add_client_ch_default_comment":
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
userStates[chatId] = "awaiting_comment"
|
|
|
|
|
cancel_btn_markup := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
|
|
|
|
|
),
|
|
|
|
|
)
|
2026-05-12 21:25:35 +00:00
|
|
|
prompt_message := t.I18nBot("tgbot.messages.comment_prompt", "ClientComment=="+state.Comment)
|
2025-04-06 22:45:52 +00:00
|
|
|
t.SendMsgToTgbot(chatId, prompt_message, cancel_btn_markup)
|
2025-03-26 18:16:35 +00:00
|
|
|
case "add_client_ch_default_traffic":
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_traffic_exp")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 0")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("add_client_limit_traffic_in 0")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("1 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 1")),
|
|
|
|
|
tu.InlineKeyboardButton("5 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 5")),
|
|
|
|
|
tu.InlineKeyboardButton("10 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 10")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("20 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 20")),
|
|
|
|
|
tu.InlineKeyboardButton("30 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 30")),
|
|
|
|
|
tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 40")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 50")),
|
|
|
|
|
tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 60")),
|
|
|
|
|
tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 80")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("100 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 100")),
|
|
|
|
|
tu.InlineKeyboardButton("150 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 150")),
|
|
|
|
|
tu.InlineKeyboardButton("200 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 200")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
|
|
|
|
case "add_client_ch_default_exp":
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_traffic_exp")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 0")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("add_client_reset_exp_in 0")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 7 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 7")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 10 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 10")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 14 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 14")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 20 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 20")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 1 "+t.I18nBot("tgbot.month")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 30")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 3 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 90")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 6 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 180")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 365")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2025-05-06 16:27:17 +00:00
|
|
|
case "add_client_ch_default_ip_limit":
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_ip_limit")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("add_client_ip_limit_c 0")),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("add_client_ip_limit_in 0")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 1")),
|
|
|
|
|
tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 2")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 3")),
|
|
|
|
|
tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 4")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 5")),
|
|
|
|
|
tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 6")),
|
|
|
|
|
tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 7")),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 8")),
|
|
|
|
|
tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 9")),
|
|
|
|
|
tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 10")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
2025-03-26 18:16:35 +00:00
|
|
|
case "add_client_default_info":
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
t.SendMsgToTgbotDeleteAfter(chatId, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
delete(userStates, chatId)
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, _ := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
message_text, _ := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-03-26 18:16:35 +00:00
|
|
|
t.addClient(chatId, message_text)
|
|
|
|
|
case "add_client_cancel":
|
|
|
|
|
delete(userStates, chatId)
|
|
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
|
|
|
|
t.SendMsgToTgbotDeleteAfter(chatId, t.I18nBot("tgbot.messages.cancel"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
case "add_client_default_traffic_exp":
|
|
|
|
|
messageId := callbackQuery.Message.GetMessageID()
|
2026-05-12 21:25:35 +00:00
|
|
|
inbound, err := t.inboundService.GetInbound(state.ReceiverInboundID)
|
2025-03-26 18:16:35 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
message_text, err := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
|
|
|
|
case "add_client_default_ip_limit":
|
|
|
|
|
messageId := callbackQuery.Message.GetMessageID()
|
|
|
|
|
inbound, err := t.inboundService.GetInbound(state.ReceiverInboundID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
message_text, err := t.BuildInboundClientDataMessage(chatId, inbound.Remark, inbound.Protocol)
|
2025-08-17 11:37:49 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
|
|
|
|
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
|
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
2025-04-16 08:16:55 +00:00
|
|
|
case "add_client_submit_enable":
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Enable = true
|
2026-05-12 00:52:02 +00:00
|
|
|
t.submitAddClient(callbackQuery)
|
|
|
|
|
case "add_client_submit_disable":
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Enable = false
|
2026-05-12 00:52:02 +00:00
|
|
|
t.submitAddClient(callbackQuery)
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
func (t *Tgbot) submitAddClient(callbackQuery *telego.CallbackQuery) {
|
|
|
|
|
chatId := callbackQuery.Message.GetChat().ID
|
2026-05-12 21:25:35 +00:00
|
|
|
needRestart, err := t.SubmitAddClient(chatId)
|
|
|
|
|
if err == nil {
|
2026-05-12 00:52:02 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
2025-05-06 16:27:17 +00:00
|
|
|
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
2026-05-12 21:25:35 +00:00
|
|
|
t.SendMsgToTgbotDeleteAfter(chatId, t.I18nBot("tgbot.answers.successfulOperation"), 3, tu.ReplyKeyboardRemove())
|
|
|
|
|
if needRestart {
|
|
|
|
|
t.xrayService.SetToNeedRestart()
|
|
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
} else {
|
2026-05-12 21:25:35 +00:00
|
|
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
|
|
|
|
errMsg := ""
|
|
|
|
|
if err != nil {
|
|
|
|
|
errMsg = "\r\n" + err.Error()
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+errMsg)
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// getCommonClientButtons returns the shared inline keyboard rows for client configuration
|
|
|
|
|
func (t *Tgbot) getCommonClientButtons() [][]telego.InlineKeyboardButton {
|
|
|
|
|
return [][]telego.InlineKeyboardButton{
|
2025-03-26 18:16:35 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 21:25:35 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_subid")).WithCallbackData("add_client_ch_default_subid"),
|
2023-03-17 16:07:49 +00:00
|
|
|
),
|
2025-09-16 11:41:48 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 00:52:02 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData("add_client_ch_default_traffic"),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData("add_client_ch_default_exp"),
|
2025-09-16 11:41:48 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 00:52:02 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_comment")).WithCallbackData("add_client_ch_default_comment"),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLimit")).WithCallbackData("add_client_ch_default_ip_limit"),
|
2023-03-17 16:07:49 +00:00
|
|
|
),
|
2025-09-14 17:51:57 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 00:52:02 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitEnable")).WithCallbackData("add_client_submit_enable"),
|
2025-09-14 17:51:57 +00:00
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 00:52:02 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
|
2025-09-14 17:51:57 +00:00
|
|
|
),
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// addClient handles the process of adding a new client to an inbound.
|
|
|
|
|
func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
|
2026-05-12 21:25:35 +00:00
|
|
|
state := t.getClientState(chatId)
|
|
|
|
|
inbound, err := t.inboundService.GetInbound(state.ReceiverInboundID)
|
2026-05-12 00:52:02 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.SendMsgToTgbot(chatId, err.Error())
|
2023-05-20 15:09:01 +00:00
|
|
|
return
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
protocol := inbound.Protocol
|
2025-05-06 16:27:17 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
var protocolRows [][]telego.InlineKeyboardButton
|
2025-04-06 22:45:52 +00:00
|
|
|
switch protocol {
|
|
|
|
|
case model.VMESS, model.VLESS:
|
2026-05-12 00:52:02 +00:00
|
|
|
protocolRows = [][]telego.InlineKeyboardButton{
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_id")).WithCallbackData("add_client_ch_default_id"),
|
|
|
|
|
),
|
2023-05-04 21:46:43 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
if protocol == model.VLESS {
|
|
|
|
|
canUseFlow := false
|
2026-03-04 20:07:58 +00:00
|
|
|
var streamSettings map[string]interface{}
|
|
|
|
|
if err := json.Unmarshal([]byte(inbound.StreamSettings), &streamSettings); err == nil {
|
|
|
|
|
network, _ := streamSettings["network"].(string)
|
|
|
|
|
security, _ := streamSettings["security"].(string)
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// Strict: only TCP and only with TLS or REALITY
|
2026-03-04 20:07:58 +00:00
|
|
|
if network == "tcp" && (security == "tls" || security == "reality") {
|
2026-05-12 00:52:02 +00:00
|
|
|
canUseFlow = true
|
2026-03-04 20:07:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
if canUseFlow {
|
|
|
|
|
protocolRows = append(protocolRows, tu.InlineKeyboardRow(
|
2026-05-12 21:25:35 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_flow")).WithCallbackData("add_client_ch_default_flow"),
|
2026-05-12 00:52:02 +00:00
|
|
|
))
|
2026-02-14 21:49:19 +00:00
|
|
|
} else {
|
2026-05-12 21:25:35 +00:00
|
|
|
state.Flow = ""
|
2026-02-14 21:49:19 +00:00
|
|
|
}
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
case model.Trojan:
|
|
|
|
|
protocolRows = [][]telego.InlineKeyboardButton{
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_password")).WithCallbackData("add_client_ch_default_pass_tr"),
|
|
|
|
|
),
|
2025-09-21 17:27:05 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
case model.Shadowsocks:
|
|
|
|
|
protocolRows = [][]telego.InlineKeyboardButton{
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_password")).WithCallbackData("add_client_ch_default_pass_sh"),
|
|
|
|
|
),
|
2026-03-04 20:07:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
commonRows := t.getCommonClientButtons()
|
|
|
|
|
inlineKeyboard := tu.InlineKeyboard(append(protocolRows, commonRows...)...)
|
|
|
|
|
|
|
|
|
|
if len(messageID) > 0 {
|
|
|
|
|
t.editMessageTgBot(chatId, messageID[0], msg, inlineKeyboard)
|
2026-03-04 20:07:58 +00:00
|
|
|
} else {
|
2026-05-12 00:52:02 +00:00
|
|
|
t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
|
2026-03-04 20:07:58 +00:00
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// 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 {
|
|
|
|
|
logger.Warning(err)
|
|
|
|
|
msg := t.I18nBot("tgbot.wentWrong")
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
|
|
|
|
return
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
if len(inbounds) == 0 {
|
|
|
|
|
msg := t.I18nBot("tgbot.noInbounds")
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
|
|
|
|
return
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
info := ""
|
|
|
|
|
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
|
|
|
|
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
|
|
|
|
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
if inbound.ExpiryTime == 0 {
|
|
|
|
|
info += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited"))
|
|
|
|
|
} else {
|
|
|
|
|
info += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, info)
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
if len(inbound.ClientStats) > 0 {
|
|
|
|
|
var output strings.Builder
|
|
|
|
|
for _, traffic := range inbound.ClientStats {
|
|
|
|
|
output.WriteString(t.clientInfoMsg(&traffic, true, true, true, true, true, true))
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, output.String())
|
2026-01-19 11:33:17 +00:00
|
|
|
}
|
2025-04-06 22:45:52 +00:00
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// getExhausted retrieves and sends information about exhausted clients.
|
|
|
|
|
func (t *Tgbot) getExhausted(chatId int64) {
|
|
|
|
|
trDiff := int64(0)
|
|
|
|
|
exDiff := int64(0)
|
|
|
|
|
now := time.Now().Unix() * 1000
|
|
|
|
|
var exhaustedInbounds []model.Inbound
|
|
|
|
|
var exhaustedClients []xray.ClientTraffic
|
|
|
|
|
var disabledInbounds []model.Inbound
|
|
|
|
|
var disabledClients []xray.ClientTraffic
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
TrafficThreshold, err := t.settingService.GetTrafficDiff()
|
|
|
|
|
if err == nil && TrafficThreshold > 0 {
|
|
|
|
|
trDiff = int64(TrafficThreshold) * 1073741824
|
|
|
|
|
}
|
|
|
|
|
ExpireThreshold, err := t.settingService.GetExpireDiff()
|
|
|
|
|
if err == nil && ExpireThreshold > 0 {
|
|
|
|
|
exDiff = int64(ExpireThreshold) * 86400000
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
inbounds, _ := t.inboundService.GetAllInbounds()
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
if !inbound.Enable {
|
2026-05-12 21:25:35 +00:00
|
|
|
disabledInbounds = append(disabledInbounds, *inbound)
|
2026-05-12 00:52:02 +00:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if (inbound.Total > 0 && inbound.Total-(inbound.Up+inbound.Down) < trDiff) || (inbound.ExpiryTime > 0 && inbound.ExpiryTime-now < exDiff) {
|
2026-05-12 21:25:35 +00:00
|
|
|
exhaustedInbounds = append(exhaustedInbounds, *inbound)
|
2026-01-19 11:33:17 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
clients, _ := t.inboundService.GetClients(inbound)
|
|
|
|
|
for _, client := range clients {
|
|
|
|
|
if !client.Enable {
|
2026-05-12 21:25:35 +00:00
|
|
|
disabledClients = append(disabledClients, xray.ClientTraffic{Email: client.Email})
|
2026-05-12 00:52:02 +00:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
msg := ""
|
|
|
|
|
if len(exhaustedInbounds) > 0 {
|
|
|
|
|
msg += "⚠️ " + t.I18nBot("tgbot.messages.exhaustedInbounds") + ":\n"
|
|
|
|
|
for _, inbound := range exhaustedInbounds {
|
|
|
|
|
msg += fmt.Sprintf("- %s (Port: %d)\n", inbound.Remark, inbound.Port)
|
|
|
|
|
}
|
|
|
|
|
msg += "\n"
|
|
|
|
|
}
|
|
|
|
|
if len(exhaustedClients) > 0 {
|
|
|
|
|
msg += "⚠️ " + t.I18nBot("tgbot.messages.exhaustedClients") + ":\n"
|
|
|
|
|
for _, client := range exhaustedClients {
|
|
|
|
|
msg += fmt.Sprintf("- %s\n", client.Email)
|
|
|
|
|
}
|
|
|
|
|
msg += "\n"
|
|
|
|
|
}
|
|
|
|
|
if len(disabledInbounds) > 0 {
|
|
|
|
|
msg += "🚫 " + t.I18nBot("tgbot.messages.disabledInbounds") + ":\n"
|
|
|
|
|
for _, inbound := range disabledInbounds {
|
|
|
|
|
msg += fmt.Sprintf("- %s (Port: %d)\n", inbound.Remark, inbound.Port)
|
|
|
|
|
}
|
|
|
|
|
msg += "\n"
|
|
|
|
|
}
|
|
|
|
|
if len(disabledClients) > 0 {
|
|
|
|
|
msg += "🚫 " + t.I18nBot("tgbot.messages.disabledClients") + ":\n"
|
|
|
|
|
for _, client := range disabledClients {
|
|
|
|
|
msg += fmt.Sprintf("- %s\n", client.Email)
|
|
|
|
|
}
|
2025-08-17 11:37:49 +00:00
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
if msg == "" {
|
|
|
|
|
msg = t.I18nBot("tgbot.messages.noExhausted")
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// searchClient searches for a client by email and sends its status.
|
|
|
|
|
func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
|
|
|
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
2025-09-14 17:51:57 +00:00
|
|
|
if err != nil {
|
2026-05-12 00:52:02 +00:00
|
|
|
logger.Warning(err)
|
|
|
|
|
msg := t.I18nBot("tgbot.wentWrong")
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
2025-09-14 17:51:57 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
if traffic == nil {
|
|
|
|
|
msg := t.I18nBot("tgbot.noResult")
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
|
|
|
|
return
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
2023-03-17 16:07:49 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
output := t.clientInfoMsg(traffic, true, true, true, true, true, len(messageID) > 0)
|
|
|
|
|
|
2025-09-14 17:51:57 +00:00
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
2025-05-06 16:27:17 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 00:52:02 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("client_refresh "+email)),
|
2025-05-06 16:27:17 +00:00
|
|
|
),
|
2023-05-14 15:20:01 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 00:52:02 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData(t.encodeQuery("limit_traffic "+email)),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData(t.encodeQuery("reset_exp "+email)),
|
2024-01-01 15:07:56 +00:00
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 00:52:02 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic "+email)),
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLimit")).WithCallbackData(t.encodeQuery("ip_limit "+email)),
|
2023-03-17 16:07:49 +00:00
|
|
|
),
|
2025-09-14 17:51:57 +00:00
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 00:52:02 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.setTGUser")).WithCallbackData(t.encodeQuery("tg_user "+email)),
|
2025-09-14 17:51:57 +00:00
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 00:52:02 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.toggle")).WithCallbackData(t.encodeQuery("toggle_enable "+email)),
|
2025-09-14 17:51:57 +00:00
|
|
|
),
|
2023-03-17 16:07:49 +00:00
|
|
|
)
|
2023-05-20 23:00:26 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
if len(messageID) > 0 {
|
|
|
|
|
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
|
2023-03-17 16:07:49 +00:00
|
|
|
} else {
|
2026-05-12 00:52:02 +00:00
|
|
|
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// getInboundsFor creates an inline keyboard with inbounds for a specific action.
|
|
|
|
|
func (t *Tgbot) getInboundsFor(action string) (*telego.InlineKeyboardMarkup, error) {
|
|
|
|
|
inbounds, err := t.inboundService.GetAllInbounds()
|
2025-09-14 17:51:57 +00:00
|
|
|
if err != nil {
|
2026-05-12 00:52:02 +00:00
|
|
|
return nil, err
|
2023-05-20 15:09:01 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(inbound.Remark).WithCallbackData(t.encodeQuery(action+" "+strconv.Itoa(inbound.Id))))
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2026-02-14 21:49:19 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
cols := 1
|
|
|
|
|
if len(buttons) >= 6 {
|
|
|
|
|
cols = 2
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
|
|
|
|
|
return keyboard, nil
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// getInboundClientsFor creates an inline keyboard with clients of a specific inbound for a specific action.
|
|
|
|
|
func (t *Tgbot) getInboundClientsFor(id int, action string) (*telego.InlineKeyboardMarkup, error) {
|
|
|
|
|
inbound, err := t.inboundService.GetInbound(id)
|
2025-09-14 17:51:57 +00:00
|
|
|
if err != nil {
|
2026-05-12 00:52:02 +00:00
|
|
|
return nil, err
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
clients, err := t.inboundService.GetClients(inbound)
|
2025-09-14 17:51:57 +00:00
|
|
|
if err != nil {
|
2026-05-12 00:52:02 +00:00
|
|
|
return nil, err
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, client := range clients {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(client.Email).WithCallbackData(t.encodeQuery(action+" "+client.Email)))
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
cols := 2
|
|
|
|
|
if len(buttons) >= 10 {
|
|
|
|
|
cols = 3
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
|
|
|
|
|
return keyboard, nil
|
|
|
|
|
}
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// sendClientSubLinks sends subscription links for a client.
|
|
|
|
|
func (t *Tgbot) sendClientSubLinks(chatId int64, email string) {
|
|
|
|
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
|
|
|
|
if err != nil || traffic == nil {
|
2025-09-14 17:51:57 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noResult"))
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-01-19 11:33:17 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
subUrl, err := t.settingService.GetSubURI()
|
2026-05-12 00:52:02 +00:00
|
|
|
if err != nil || subUrl == "" {
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.subUrlNotSet"))
|
|
|
|
|
return
|
2026-01-19 11:33:17 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
msg := t.I18nBot("tgbot.messages.subLinks", "Email=="+email)
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += fmt.Sprintf("\n`%s%s`", subUrl, traffic.SubId)
|
2026-05-12 00:52:02 +00:00
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
2026-01-19 11:33:17 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// sendClientIndividualLinks sends individual xray links for a client.
|
|
|
|
|
func (t *Tgbot) sendClientIndividualLinks(chatId int64, email string) {
|
2026-05-12 21:25:35 +00:00
|
|
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
|
|
|
|
if err != nil || traffic == nil {
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noResult"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
host, _ := t.settingService.GetWebDomain()
|
|
|
|
|
links, err := t.inboundService.GetClientLinks(host, traffic.InboundId, email)
|
2026-05-12 00:52:02 +00:00
|
|
|
if err != nil || len(links) == 0 {
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noResult"))
|
2025-09-14 17:51:57 +00:00
|
|
|
return
|
2026-01-19 11:33:17 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
msg := t.I18nBot("tgbot.messages.individualLinks", "Email=="+email)
|
|
|
|
|
for _, link := range links {
|
|
|
|
|
msg += fmt.Sprintf("\n`%s`", link)
|
2025-09-18 11:56:04 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
t.SendMsgToTgbot(chatId, msg)
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// sendClientQRLinks sends QR codes for a client's links.
|
|
|
|
|
func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
|
2026-05-12 21:25:35 +00:00
|
|
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
|
|
|
|
if err != nil || traffic == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
host, _ := t.settingService.GetWebDomain()
|
|
|
|
|
links, err := t.inboundService.GetClientLinks(host, traffic.InboundId, email)
|
2026-05-12 00:52:02 +00:00
|
|
|
if err != nil || len(links) == 0 {
|
2025-09-14 17:51:57 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
for _, link := range links {
|
|
|
|
|
qr, err := qrcode.Encode(link, qrcode.Medium, 256)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
2025-09-18 11:56:04 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
t.SendPhotoToTgbot(chatId, qr, link)
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
}
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// onlineClients retrieves and sends information about currently online clients.
|
|
|
|
|
func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
2026-05-12 21:25:35 +00:00
|
|
|
if !p.IsRunning() {
|
|
|
|
|
return
|
2025-09-18 11:56:04 +00:00
|
|
|
}
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
onlines := p.GetOnlineClients()
|
|
|
|
|
onlinesCount := len(onlines)
|
|
|
|
|
output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(onlinesCount))
|
|
|
|
|
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh"))))
|
|
|
|
|
|
|
|
|
|
if onlinesCount > 0 {
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, online := range onlines {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(online).WithCallbackData(t.encodeQuery("client_get_usage "+online)))
|
|
|
|
|
}
|
|
|
|
|
cols := 2
|
|
|
|
|
if onlinesCount >= 10 {
|
|
|
|
|
cols = 3
|
|
|
|
|
}
|
|
|
|
|
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, tu.InlineKeyboardCols(cols, buttons...)...)
|
|
|
|
|
}
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
if len(messageID) > 0 {
|
2026-05-12 21:25:35 +00:00
|
|
|
t.editMessageTgBot(chatId, messageID[0], output, keyboard)
|
2024-01-01 15:07:56 +00:00
|
|
|
} else {
|
2026-05-12 21:25:35 +00:00
|
|
|
t.SendMsgToTgbot(chatId, output, keyboard)
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// getServerUsage retrieves and sends the server's usage statistics.
|
|
|
|
|
func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) {
|
|
|
|
|
status, exists := t.getCachedStatus()
|
|
|
|
|
if !exists {
|
|
|
|
|
status = t.serverService.GetStatus(t.lastStatus)
|
|
|
|
|
t.setCachedStatus(status)
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
msg := "📊 " + t.I18nBot("tgbot.buttons.serverUsage") + ":\n"
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.cpu", "Usage=="+fmt.Sprintf("%.2f", status.Cpu))
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += t.I18nBot("tgbot.messages.mem", "Usage=="+common.FormatTraffic(int64(status.Mem.Current)), "Total=="+common.FormatTraffic(int64(status.Mem.Total)))
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.swap", "Usage=="+common.FormatTraffic(int64(status.Swap.Current)), "Total=="+common.FormatTraffic(int64(status.Swap.Total)))
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.disk", "Usage=="+common.FormatTraffic(int64(status.Disk.Current)), "Total=="+common.FormatTraffic(int64(status.Disk.Total)))
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.uptime", "Time=="+strconv.FormatUint(status.Uptime/86400, 10))
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("usage_refresh")),
|
|
|
|
|
),
|
|
|
|
|
)
|
2023-05-20 23:00:26 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
if len(messageID) > 0 {
|
|
|
|
|
t.editMessageTgBot(chatId, messageID[0], msg, inlineKeyboard)
|
|
|
|
|
} else {
|
|
|
|
|
t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// getInboundUsages retrieves and sends the usage statistics for all inbounds.
|
|
|
|
|
func (t *Tgbot) getInboundUsages() string {
|
|
|
|
|
inbounds, _ := t.inboundService.GetAllInbounds()
|
|
|
|
|
var msg strings.Builder
|
|
|
|
|
msg.WriteString("📥 " + t.I18nBot("tgbot.buttons.getInbounds") + ":\n")
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
msg.WriteString(fmt.Sprintf("- %s: %s (Port: %d)\n", inbound.Remark, common.FormatTraffic(inbound.Up+inbound.Down), inbound.Port))
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
return msg.String()
|
|
|
|
|
}
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// getClientUsage retrieves and sends the usage statistics for a client by its Telegram ID or email.
|
|
|
|
|
func (t *Tgbot) getClientUsage(chatId int64, tgUserID int64, email ...string) {
|
2026-05-12 21:25:35 +00:00
|
|
|
var traffics []*xray.ClientTraffic
|
2026-05-12 00:52:02 +00:00
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
if len(email) > 0 {
|
|
|
|
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email[0])
|
|
|
|
|
if err == nil && traffic != nil {
|
2026-05-12 21:25:35 +00:00
|
|
|
traffics = append(traffics, traffic)
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
2026-05-12 00:52:02 +00:00
|
|
|
traffics, err = t.inboundService.GetClientTrafficTgBot(tgUserID)
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
if err != nil || len(traffics) == 0 {
|
2025-09-14 17:51:57 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noResult"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
for _, traffic := range traffics {
|
2026-05-12 21:25:35 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.clientInfoMsg(traffic, true, true, true, true, true, false))
|
2025-09-14 17:51:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// searchClientIps retrieves and sends the IP log for a client by email.
|
|
|
|
|
func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) {
|
2026-05-12 21:25:35 +00:00
|
|
|
ips, err := t.inboundService.GetInboundClientIps(email)
|
2025-09-14 17:51:57 +00:00
|
|
|
if err != nil {
|
2026-05-12 00:52:02 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.wentWrong"))
|
2025-09-14 17:51:57 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
output := t.I18nBot("tgbot.messages.email", "Email=="+email) + "\n" + ips
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("ips_refresh "+email)),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
2026-05-12 21:25:35 +00:00
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clearIPs")).WithCallbackData(t.encodeQuery("clear_ips "+email)),
|
2026-05-12 00:52:02 +00:00
|
|
|
),
|
|
|
|
|
)
|
2025-09-14 17:51:57 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
if len(messageID) > 0 {
|
2026-05-12 21:25:35 +00:00
|
|
|
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
|
2024-01-01 15:07:56 +00:00
|
|
|
} else {
|
2026-05-12 21:25:35 +00:00
|
|
|
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// clientTelegramUserInfo retrieves and sends Telegram user info for a client.
|
|
|
|
|
func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...int) {
|
2026-05-12 21:25:35 +00:00
|
|
|
traffic, client, err := t.inboundService.GetClientByEmail(email)
|
|
|
|
|
if err != nil || client == nil {
|
2026-05-12 00:52:02 +00:00
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noResult"))
|
|
|
|
|
return
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2023-05-20 23:00:26 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
tgId := "None"
|
|
|
|
|
if client.TgID != 0 {
|
|
|
|
|
tgId = strconv.FormatInt(client.TgID, 10)
|
2025-09-21 17:27:05 +00:00
|
|
|
}
|
2023-05-20 23:00:26 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
output := t.I18nBot("tgbot.messages.email", "Email=="+email) + "\nTelegram ID: " + tgId
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
inlineKeyboard := tu.InlineKeyboard(
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("tgid_refresh "+email)),
|
|
|
|
|
),
|
|
|
|
|
tu.InlineKeyboardRow(
|
|
|
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.removeTGUser")).WithCallbackData(t.encodeQuery("tgid_remove "+email)),
|
|
|
|
|
),
|
|
|
|
|
)
|
2023-05-20 23:00:26 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
if len(messageID) > 0 {
|
2026-05-12 21:25:35 +00:00
|
|
|
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
|
2023-03-17 16:07:49 +00:00
|
|
|
} else {
|
2026-05-12 21:25:35 +00:00
|
|
|
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
|
|
|
|
|
requestUsers := &telego.KeyboardButtonRequestUsers{
|
|
|
|
|
RequestID: int32(traffic.Id),
|
|
|
|
|
UserIsBot: new(bool), // false
|
|
|
|
|
}
|
|
|
|
|
keyboard := tu.Keyboard(
|
|
|
|
|
tu.KeyboardRow(
|
|
|
|
|
tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUsers(requestUsers),
|
|
|
|
|
),
|
|
|
|
|
tu.KeyboardRow(
|
|
|
|
|
tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")),
|
|
|
|
|
),
|
|
|
|
|
).WithIsPersistent().WithResizeKeyboard()
|
|
|
|
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.buttons.selectOneTGUser"), keyboard)
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// sendBackup sends a database backup file to the specified chat.
|
|
|
|
|
func (t *Tgbot) sendBackup(chatId int64) {
|
|
|
|
|
dbPath := config.GetDBPath()
|
|
|
|
|
file, err := os.Open(dbPath)
|
2026-05-12 21:25:35 +00:00
|
|
|
if err == nil {
|
|
|
|
|
defer file.Close()
|
|
|
|
|
doc := tu.Document(tu.ID(chatId), tu.FileFromReader(file, "x-ui.db"))
|
|
|
|
|
_, _ = bot.SendDocument(context.Background(), doc)
|
2024-01-01 15:07:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// sendBanLogs sends the ban logs to the specified chat.
|
2026-05-12 21:25:35 +00:00
|
|
|
func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
|
|
|
|
file, err := os.Open(xray.GetIPLimitBannedLogPath())
|
|
|
|
|
if err == nil {
|
|
|
|
|
defer file.Close()
|
|
|
|
|
stat, _ := file.Stat()
|
|
|
|
|
if stat.Size() > 0 {
|
|
|
|
|
doc := tu.Document(tu.ID(chatId), tu.FileFromReader(file, "ban.log"))
|
|
|
|
|
_, _ = bot.SendDocument(context.Background(), doc)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(chatId, "🚫 "+t.I18nBot("tgbot.noResult"))
|
2024-01-01 15:07:56 +00:00
|
|
|
}
|
2024-05-14 12:00:10 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// SendPhotoToTgbot sends a photo to the Telegram bot.
|
|
|
|
|
func (t *Tgbot) SendPhotoToTgbot(chatId int64, photo []byte, caption string) {
|
2026-05-12 21:25:35 +00:00
|
|
|
reader := strings.NewReader(string(photo))
|
|
|
|
|
photoMsg := tu.Photo(tu.ID(chatId), tu.FileFromReader(reader, "qr.png"))
|
2026-05-12 00:52:02 +00:00
|
|
|
photoMsg.Caption = caption
|
2026-05-12 21:25:35 +00:00
|
|
|
photoMsg.ParseMode = "HTML"
|
|
|
|
|
_, _ = bot.SendPhoto(context.Background(), photoMsg)
|
2024-05-14 12:00:10 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// sendCallbackAnswerTgBot sends an answer to a callback query.
|
|
|
|
|
func (t *Tgbot) sendCallbackAnswerTgBot(callbackQueryID string, text string) {
|
2026-05-12 21:25:35 +00:00
|
|
|
_ = bot.AnswerCallbackQuery(context.Background(), &telego.AnswerCallbackQueryParams{
|
|
|
|
|
CallbackQueryID: callbackQueryID,
|
|
|
|
|
Text: text,
|
|
|
|
|
})
|
2024-05-14 12:00:10 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// editMessageTgBot edits an existing message in the Telegram bot.
|
2026-05-12 21:25:35 +00:00
|
|
|
func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, replyMarkup ...*telego.InlineKeyboardMarkup) {
|
|
|
|
|
editMsg := &telego.EditMessageTextParams{
|
|
|
|
|
ChatID: tu.ID(chatId),
|
|
|
|
|
MessageID: messageID,
|
|
|
|
|
Text: text,
|
|
|
|
|
ParseMode: "HTML",
|
|
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
if len(replyMarkup) > 0 {
|
2026-05-12 21:25:35 +00:00
|
|
|
editMsg.ReplyMarkup = replyMarkup[0]
|
2025-09-21 17:27:05 +00:00
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
_, _ = bot.EditMessageText(context.Background(), editMsg)
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2025-09-21 22:20:05 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// editMessageCallbackTgBot edits a message's reply markup based on a callback query.
|
2026-05-12 21:25:35 +00:00
|
|
|
func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, replyMarkup *telego.InlineKeyboardMarkup) {
|
|
|
|
|
editMsg := &telego.EditMessageReplyMarkupParams{
|
|
|
|
|
ChatID: tu.ID(chatId),
|
|
|
|
|
MessageID: messageID,
|
|
|
|
|
ReplyMarkup: replyMarkup,
|
|
|
|
|
}
|
|
|
|
|
_, _ = bot.EditMessageReplyMarkup(context.Background(), editMsg)
|
2026-05-12 00:52:02 +00:00
|
|
|
}
|
2024-04-29 06:44:16 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// deleteMessageTgBot deletes a message in the Telegram bot.
|
|
|
|
|
func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) {
|
2026-05-12 21:25:35 +00:00
|
|
|
_ = bot.DeleteMessage(context.Background(), &telego.DeleteMessageParams{ChatID: tu.ID(chatId), MessageID: messageID})
|
2026-05-12 00:52:02 +00:00
|
|
|
}
|
2023-05-20 23:00:26 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// SendMsgToTgbotDeleteAfter sends a message and deletes it after a specified time.
|
|
|
|
|
func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, seconds int, replyMarkup ...telego.ReplyMarkup) {
|
2026-05-12 21:25:35 +00:00
|
|
|
var replyMarkupParam telego.ReplyMarkup
|
|
|
|
|
if len(replyMarkup) > 0 {
|
|
|
|
|
replyMarkupParam = replyMarkup[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sentMsg, err := bot.SendMessage(context.Background(), &telego.SendMessageParams{
|
|
|
|
|
ChatID: tu.ID(chatId),
|
|
|
|
|
Text: msg,
|
|
|
|
|
ParseMode: "HTML",
|
|
|
|
|
ReplyMarkup: replyMarkupParam,
|
|
|
|
|
})
|
2024-01-01 15:07:56 +00:00
|
|
|
if err == nil {
|
2026-05-12 00:52:02 +00:00
|
|
|
go func() {
|
|
|
|
|
time.Sleep(time.Duration(seconds) * time.Second)
|
|
|
|
|
t.deleteMessageTgBot(chatId, sentMsg.MessageID)
|
2026-05-12 21:25:35 +00:00
|
|
|
delete(userStates, chatId)
|
2026-05-12 00:52:02 +00:00
|
|
|
}()
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// isSingleWord checks if a string consists of a single word.
|
|
|
|
|
func (t *Tgbot) isSingleWord(s string) bool {
|
|
|
|
|
return len(strings.Fields(s)) > 1
|
|
|
|
|
}
|
2023-05-20 15:09:01 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
// BuildInboundClientDataMessage builds a message with the current client configuration.
|
2026-05-12 21:25:35 +00:00
|
|
|
func (t *Tgbot) BuildInboundClientDataMessage(chatId int64, remark string, protocol model.Protocol) (string, error) {
|
|
|
|
|
state := t.getClientState(chatId)
|
2026-05-12 00:52:02 +00:00
|
|
|
msg := fmt.Sprintf("📝 *%s* (%s)\n\n", remark, protocol)
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += fmt.Sprintf("📧 %s: `%s`\n", t.I18nBot("pages.inbounds.email"), state.Email)
|
2023-05-20 15:09:01 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
switch protocol {
|
|
|
|
|
case model.VMESS, model.VLESS:
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += fmt.Sprintf("🆔 %s: `%s`\n", "ID", state.Id)
|
|
|
|
|
if protocol == model.VLESS && state.Flow != "" {
|
|
|
|
|
msg += fmt.Sprintf("🌊 %s `%s`\n", t.I18nBot("tgbot.messages.client_flow"), state.Flow)
|
2024-01-01 15:07:56 +00:00
|
|
|
}
|
2026-05-12 00:52:02 +00:00
|
|
|
case model.Trojan:
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += fmt.Sprintf("🔑 %s: `%s`\n", t.I18nBot("password"), state.TrPassword)
|
2026-05-12 00:52:02 +00:00
|
|
|
case model.Shadowsocks:
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += fmt.Sprintf("🔑 %s: `%s`\n", t.I18nBot("password"), state.ShPassword)
|
2023-06-17 15:41:16 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += fmt.Sprintf("📝 %s `%s`\n", t.I18nBot("tgbot.messages.client_subid"), state.SubID)
|
2023-03-17 16:07:49 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
trafficLimit := t.I18nBot("tgbot.unlimited")
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.TotalGB > 0 {
|
|
|
|
|
trafficLimit = common.FormatTraffic(state.TotalGB)
|
2023-03-17 16:07:49 +00:00
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += fmt.Sprintf("📊 %s: %s\n", t.I18nBot("tgbot.buttons.limitTraffic"), trafficLimit)
|
2025-09-20 07:35:50 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
expireTime := t.I18nBot("tgbot.unlimited")
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.ExpiryTime < 0 {
|
|
|
|
|
expireTime = fmt.Sprintf("%d %s", -state.ExpiryTime/(24*60*60*1000), t.I18nBot("tgbot.days"))
|
|
|
|
|
} else if state.ExpiryTime > 0 {
|
|
|
|
|
expireTime = time.Unix(state.ExpiryTime/1000, 0).Format("2006-01-02 15:04:05")
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += fmt.Sprintf("📅 %s: %s\n", t.I18nBot("pages.client.expireDate"), expireTime)
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
ipLimit := t.I18nBot("tgbot.unlimited")
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.LimitIP > 0 {
|
|
|
|
|
ipLimit = strconv.Itoa(state.LimitIP)
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
msg += fmt.Sprintf("🚫 %s: %s\n", t.I18nBot("pages.inbounds.IPLimit"), ipLimit)
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 21:25:35 +00:00
|
|
|
if state.Comment != "" {
|
|
|
|
|
msg += fmt.Sprintf("💬 %s: %s\n", t.I18nBot("comment"), state.Comment)
|
2025-04-06 22:45:52 +00:00
|
|
|
}
|
2025-03-26 18:16:35 +00:00
|
|
|
|
2026-05-12 00:52:02 +00:00
|
|
|
return msg, nil
|
2025-03-26 18:16:35 +00:00
|
|
|
}
|
2026-05-12 21:25:35 +00:00
|
|
|
|
|
|
|
|
func (t *Tgbot) getInbounds() (*telego.InlineKeyboardMarkup, error) {
|
|
|
|
|
inbounds, err := t.inboundService.GetAllInbounds()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(inbound.Remark).WithCallbackData(t.encodeQuery("get_clients "+strconv.Itoa(inbound.Id))))
|
|
|
|
|
}
|
|
|
|
|
cols := 1
|
|
|
|
|
if len(buttons) >= 6 {
|
|
|
|
|
cols = 2
|
|
|
|
|
}
|
|
|
|
|
keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
|
|
|
|
|
return keyboard, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) getInboundClients(id int) (*telego.InlineKeyboardMarkup, error) {
|
|
|
|
|
inbound, err := t.inboundService.GetInbound(id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
clients, err := t.inboundService.GetClients(inbound)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, client := range clients {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(client.Email).WithCallbackData(t.encodeQuery("client_get_usage "+client.Email)))
|
|
|
|
|
}
|
|
|
|
|
cols := 2
|
|
|
|
|
if len(buttons) >= 10 {
|
|
|
|
|
cols = 3
|
|
|
|
|
}
|
|
|
|
|
keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
|
|
|
|
|
return keyboard, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) getInboundsAddClient() (*telego.InlineKeyboardMarkup, error) {
|
|
|
|
|
inbounds, err := t.inboundService.GetAllInbounds()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var buttons []telego.InlineKeyboardButton
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
buttons = append(buttons, tu.InlineKeyboardButton(inbound.Remark).WithCallbackData(t.encodeQuery("add_client_to "+strconv.Itoa(inbound.Id))))
|
|
|
|
|
}
|
|
|
|
|
cols := 1
|
|
|
|
|
if len(buttons) >= 6 {
|
|
|
|
|
cols = 2
|
|
|
|
|
}
|
|
|
|
|
keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
|
|
|
|
|
return keyboard, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, showTraffic, showExpiry, showIP, showTG, showOnline, showRefresh bool) string {
|
|
|
|
|
msg := ""
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
|
|
|
if showTraffic {
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic(traffic.Up+traffic.Down), "Upload=="+common.FormatTraffic(traffic.Up), "Download=="+common.FormatTraffic(traffic.Down))
|
|
|
|
|
limit := t.I18nBot("tgbot.unlimited")
|
|
|
|
|
if traffic.Total > 0 {
|
|
|
|
|
limit = common.FormatTraffic(traffic.Total)
|
|
|
|
|
}
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic(traffic.Up+traffic.Down), "Total=="+limit)
|
|
|
|
|
}
|
|
|
|
|
if showExpiry {
|
|
|
|
|
if traffic.ExpiryTime == 0 {
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited"))
|
|
|
|
|
} else {
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix(traffic.ExpiryTime/1000, 0).Format("2006-01-02 15:04:05"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if showOnline {
|
|
|
|
|
status := t.I18nBot("tgbot.offline")
|
|
|
|
|
if p != nil && slices.Contains(p.GetOnlineClients(), traffic.Email) {
|
|
|
|
|
status = t.I18nBot("tgbot.online")
|
|
|
|
|
}
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.online", "Status=="+status)
|
|
|
|
|
}
|
|
|
|
|
return msg + "\r\n"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.ReplyMarkup) {
|
|
|
|
|
if !isRunning {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
params := &telego.SendMessageParams{
|
|
|
|
|
ChatID: tu.ID(chatId),
|
|
|
|
|
Text: msg,
|
|
|
|
|
ParseMode: "HTML",
|
|
|
|
|
}
|
|
|
|
|
if len(replyMarkup) > 0 {
|
|
|
|
|
params.ReplyMarkup = replyMarkup[0]
|
|
|
|
|
}
|
|
|
|
|
_, _ = bot.SendMessage(context.Background(), params)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) SubmitAddClient(chatId int64) (bool, error) {
|
|
|
|
|
state := t.getClientState(chatId)
|
|
|
|
|
client := model.Client{
|
|
|
|
|
ID: state.Id,
|
|
|
|
|
Flow: state.Flow,
|
|
|
|
|
Email: state.Email,
|
|
|
|
|
LimitIP: state.LimitIP,
|
|
|
|
|
TotalGB: state.TotalGB,
|
|
|
|
|
ExpiryTime: state.ExpiryTime,
|
|
|
|
|
Enable: state.Enable,
|
|
|
|
|
TgID: 0,
|
|
|
|
|
SubID: state.SubID,
|
|
|
|
|
Comment: state.Comment,
|
|
|
|
|
Reset: state.Reset,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
settings, _ := json.Marshal(map[string][]model.Client{
|
|
|
|
|
"clients": {client},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
inbound := &model.Inbound{
|
|
|
|
|
Id: state.ReceiverInboundID,
|
|
|
|
|
Settings: string(settings),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return t.inboundService.AddInboundClient(inbound)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) SendMsgToTgbotAdmins(msg string, replyMarkup ...telego.ReplyMarkup) {
|
|
|
|
|
if len(replyMarkup) > 0 {
|
|
|
|
|
for _, adminId := range adminIds {
|
|
|
|
|
t.SendMsgToTgbot(adminId, msg, replyMarkup[0])
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for _, adminId := range adminIds {
|
|
|
|
|
t.SendMsgToTgbot(adminId, msg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) SendReport() {
|
|
|
|
|
runTime, err := t.settingService.GetTgbotRuntime()
|
|
|
|
|
if err == nil && len(runTime) > 0 {
|
|
|
|
|
msg := ""
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.report", "RunTime=="+runTime)
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
|
|
|
t.SendMsgToTgbotAdmins(msg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info := t.sendServerUsage()
|
|
|
|
|
t.SendMsgToTgbotAdmins(info)
|
|
|
|
|
|
|
|
|
|
t.sendExhaustedToAdmins()
|
|
|
|
|
t.notifyExhausted()
|
|
|
|
|
|
|
|
|
|
backupEnable, err := t.settingService.GetTgBotBackup()
|
|
|
|
|
if err == nil && backupEnable {
|
|
|
|
|
t.SendBackupToAdmins()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) SendBackupToAdmins() {
|
|
|
|
|
if !t.IsRunning() {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for i, adminId := range adminIds {
|
|
|
|
|
t.sendBackup(int64(adminId))
|
|
|
|
|
if i < len(adminIds)-1 {
|
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) sendExhaustedToAdmins() {
|
|
|
|
|
if !t.IsRunning() {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for _, adminId := range adminIds {
|
|
|
|
|
t.getExhausted(int64(adminId))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) sendServerUsage() string {
|
|
|
|
|
return t.prepareServerUsageInfo()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) prepareServerUsageInfo() string {
|
|
|
|
|
if cachedStats, found := t.getCachedServerStats(); found {
|
|
|
|
|
return cachedStats
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info, ipv4, ipv6 := "", "", ""
|
|
|
|
|
|
|
|
|
|
if cachedStatus, found := t.getCachedStatus(); found {
|
|
|
|
|
t.lastStatus = cachedStatus
|
|
|
|
|
} else {
|
|
|
|
|
t.lastStatus = t.serverService.GetStatus(t.lastStatus)
|
|
|
|
|
t.setCachedStatus(t.lastStatus)
|
|
|
|
|
}
|
|
|
|
|
onlines := p.GetOnlineClients()
|
|
|
|
|
|
|
|
|
|
info += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
|
|
|
|
info += t.I18nBot("tgbot.messages.version", "Version=="+config.GetVersion())
|
|
|
|
|
info += t.I18nBot("tgbot.messages.xrayVersion", "XrayVersion=="+fmt.Sprint(t.lastStatus.Xray.Version))
|
|
|
|
|
|
|
|
|
|
interfaces, err := net.Interfaces()
|
|
|
|
|
if err == nil {
|
|
|
|
|
for _, i := range interfaces {
|
|
|
|
|
if (i.Flags & net.FlagUp) != 0 {
|
|
|
|
|
addrs, _ := i.Addrs()
|
|
|
|
|
for _, address := range addrs {
|
|
|
|
|
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
|
|
|
|
if ipnet.IP.To4() != nil {
|
|
|
|
|
ipv4 += ipnet.IP.String() + " "
|
|
|
|
|
} else if ipnet.IP.To16() != nil && !ipnet.IP.IsLinkLocalUnicast() {
|
|
|
|
|
ipv6 += ipnet.IP.String() + " "
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
info += t.I18nBot("tgbot.messages.ipv4", "IPv4=="+ipv4)
|
|
|
|
|
info += t.I18nBot("tgbot.messages.ipv6", "IPv6=="+ipv6)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info += t.I18nBot("tgbot.messages.serverUpTime", "UpTime=="+strconv.FormatUint(t.lastStatus.Uptime/86400, 10), "Unit=="+t.I18nBot("tgbot.days"))
|
|
|
|
|
info += t.I18nBot("tgbot.messages.serverLoad", "Load1=="+strconv.FormatFloat(t.lastStatus.Loads[0], 'f', 2, 64), "Load2=="+strconv.FormatFloat(t.lastStatus.Loads[1], 'f', 2, 64), "Load3=="+strconv.FormatFloat(t.lastStatus.Loads[2], 'f', 2, 64))
|
|
|
|
|
info += t.I18nBot("tgbot.messages.serverMemory", "Current=="+common.FormatTraffic(int64(t.lastStatus.Mem.Current)), "Total=="+common.FormatTraffic(int64(t.lastStatus.Mem.Total)))
|
|
|
|
|
info += t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(len(onlines)))
|
|
|
|
|
info += t.I18nBot("tgbot.messages.tcpCount", "Count=="+strconv.Itoa(t.lastStatus.TcpCount))
|
|
|
|
|
info += t.I18nBot("tgbot.messages.udpCount", "Count=="+strconv.Itoa(t.lastStatus.UdpCount))
|
|
|
|
|
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent+t.lastStatus.NetTraffic.Recv)), "Upload=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent)), "Download=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Recv)))
|
|
|
|
|
info += t.I18nBot("tgbot.messages.xrayStatus", "State=="+fmt.Sprint(t.lastStatus.Xray.State))
|
|
|
|
|
|
|
|
|
|
t.setCachedServerStats(info)
|
|
|
|
|
return info
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) UserLoginNotify(attempt LoginAttempt) {
|
|
|
|
|
if !t.IsRunning() {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
loginNotifyEnabled, err := t.settingService.GetTgBotLoginNotify()
|
|
|
|
|
if err != nil || !loginNotifyEnabled {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
msg := ""
|
|
|
|
|
switch attempt.Status {
|
|
|
|
|
case LoginSuccess:
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.loginSuccess")
|
|
|
|
|
case LoginFail:
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.loginFailed")
|
|
|
|
|
if attempt.Reason != "" {
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.reason", "Reason=="+attempt.Reason)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.username", "Username=="+attempt.Username)
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.ip", "IP=="+attempt.IP)
|
|
|
|
|
msg += t.I18nBot("tgbot.messages.time", "Time=="+attempt.Time)
|
|
|
|
|
t.SendMsgToTgbotAdmins(msg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tgbot) notifyExhausted() {
|
|
|
|
|
trDiff := int64(0)
|
|
|
|
|
exDiff := int64(0)
|
|
|
|
|
now := time.Now().Unix() * 1000
|
|
|
|
|
TrafficThreshold, err := t.settingService.GetTrafficDiff()
|
|
|
|
|
if err == nil && TrafficThreshold > 0 {
|
|
|
|
|
trDiff = int64(TrafficThreshold) * 1073741824
|
|
|
|
|
}
|
|
|
|
|
ExpireThreshold, err := t.settingService.GetExpireDiff()
|
|
|
|
|
if err == nil && ExpireThreshold > 0 {
|
|
|
|
|
exDiff = int64(ExpireThreshold) * 86400000
|
|
|
|
|
}
|
|
|
|
|
inbounds, _ := t.inboundService.GetAllInbounds()
|
|
|
|
|
|
|
|
|
|
var chatIDsDone []int64
|
|
|
|
|
for _, inbound := range inbounds {
|
|
|
|
|
if inbound.Enable {
|
|
|
|
|
clients, err := t.inboundService.GetClients(inbound)
|
|
|
|
|
if err == nil {
|
|
|
|
|
for _, client := range clients {
|
|
|
|
|
if client.TgID != 0 && !int64Contains(chatIDsDone, client.TgID) && !checkAdmin(client.TgID) {
|
|
|
|
|
traffics, err := t.inboundService.GetClientTrafficTgBot(client.TgID)
|
|
|
|
|
if err == nil && len(traffics) > 0 {
|
|
|
|
|
var exhausted []xray.ClientTraffic
|
|
|
|
|
for _, tr := range traffics {
|
|
|
|
|
if tr.Enable {
|
|
|
|
|
if (tr.ExpiryTime > 0 && tr.ExpiryTime-now < exDiff) || (tr.Total > 0 && tr.Total-(tr.Up+tr.Down) < trDiff) {
|
|
|
|
|
exhausted = append(exhausted, *tr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(exhausted) > 0 {
|
|
|
|
|
output := t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients"))
|
|
|
|
|
for _, ex := range exhausted {
|
|
|
|
|
output += t.clientInfoMsg(&ex, true, false, false, true, true, false)
|
|
|
|
|
}
|
|
|
|
|
t.SendMsgToTgbot(client.TgID, output)
|
|
|
|
|
}
|
|
|
|
|
chatIDsDone = append(chatIDsDone, client.TgID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func int64Contains(slice []int64, item int64) bool {
|
|
|
|
|
for _, s := range slice {
|
|
|
|
|
if s == item {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|