docs: add comments for all functions

This commit is contained in:
mhsanaei
2025-09-20 09:35:50 +02:00
parent f60682a6b7
commit 6ced549dea
63 changed files with 624 additions and 113 deletions

View File

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