Compare commits

..

No commits in common. "5b796672e90934b34186b0a6809857c417304f5f" and "e5c0fe3edf3bc8ee44c13503cc39d4caba735ae9" have entirely different histories.

16 changed files with 57 additions and 207 deletions

View file

@ -143,11 +143,11 @@ func (a *SUBController) subs(c *gin.Context) {
// Add headers // Add headers
header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000) header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
profileUrl := a.subProfileUrl profileUrl := a.subProfileUrl
if profileUrl == "" { if profileUrl == "" {
profileUrl = fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI) profileUrl = fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
} }
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, profileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules) a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, profileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
if a.subEncrypt { if a.subEncrypt {
c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))

View file

@ -2141,25 +2141,25 @@ func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error)
if err != nil { if err != nil {
return "", err return "", err
} }
if InboundClientIps.Ips == "" { if InboundClientIps.Ips == "" {
return "", nil return "", nil
} }
// Try to parse as new format (with timestamps) // Try to parse as new format (with timestamps)
type IPWithTimestamp struct { type IPWithTimestamp struct {
IP string `json:"ip"` IP string `json:"ip"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
} }
var ipsWithTime []IPWithTimestamp var ipsWithTime []IPWithTimestamp
err = json.Unmarshal([]byte(InboundClientIps.Ips), &ipsWithTime) err = json.Unmarshal([]byte(InboundClientIps.Ips), &ipsWithTime)
// If successfully parsed as new format, return with timestamps // If successfully parsed as new format, return with timestamps
if err == nil && len(ipsWithTime) > 0 { if err == nil && len(ipsWithTime) > 0 {
return InboundClientIps.Ips, nil return InboundClientIps.Ips, nil
} }
// Otherwise, assume it's old format (simple string array) // Otherwise, assume it's old format (simple string array)
// Try to parse as simple array and convert to new format // Try to parse as simple array and convert to new format
var oldIps []string var oldIps []string
@ -2176,7 +2176,7 @@ func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error)
result, _ := json.Marshal(newIpsWithTime) result, _ := json.Marshal(newIpsWithTime)
return string(result), nil return string(result), nil
} }
// Return as-is if parsing fails // Return as-is if parsing fails
return InboundClientIps.Ips, nil return InboundClientIps.Ips, nil
} }

View file

@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -719,25 +719,25 @@ func (s *SettingService) GetDefaultXrayConfig() (any, error) {
} }
func extractHostname(host string) string { func extractHostname(host string) string {
h, _, err := net.SplitHostPort(host) h, _, err := net.SplitHostPort(host)
// Err is not nil means host does not contain port // Err is not nil means host does not contain port
if err != nil { if err != nil {
h = host h = host
} }
ip := net.ParseIP(h) ip := net.ParseIP(h)
// If it's not an IP, return as is // If it's not an IP, return as is
if ip == nil { if ip == nil {
return h return h
} }
// If it's an IPv4, return as is // If it's an IPv4, return as is
if ip.To4() != nil { if ip.To4() != nil {
return h return h
} }
// IPv6 needs bracketing // IPv6 needs bracketing
return "[" + h + "]" return "[" + h + "]"
} }
func (s *SettingService) GetDefaultSettings(host string) (any, error) { func (s *SettingService) GetDefaultSettings(host string) (any, error) {

View file

@ -272,78 +272,41 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
return nil return nil
} }
// 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)
}
return client
}
// NewBot creates a new Telegram bot instance with optional proxy and API server settings. // 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) { func (t *Tgbot) NewBot(token string, proxyUrl string, apiServerUrl string) (*telego.Bot, error) {
// Validate proxy URL if provided if proxyUrl == "" && apiServerUrl == "" {
return telego.NewBot(token)
}
if proxyUrl != "" { if proxyUrl != "" {
if !strings.HasPrefix(proxyUrl, "socks5://") { if !strings.HasPrefix(proxyUrl, "socks5://") {
logger.Warning("Invalid socks5 URL, ignoring proxy") logger.Warning("Invalid socks5 URL, using default")
proxyUrl = "" // Clear invalid proxy return telego.NewBot(token)
} else {
_, err := url.Parse(proxyUrl)
if err != nil {
logger.Warningf("Can't parse proxy URL, ignoring proxy: %v", err)
proxyUrl = ""
}
} }
}
// Validate API server URL if provided _, err := url.Parse(proxyUrl)
if apiServerUrl != "" { if err != nil {
if !strings.HasPrefix(apiServerUrl, "http") { logger.Warningf("Can't parse proxy URL, using default instance for tgbot: %v", err)
logger.Warning("Invalid http(s) URL for API server, using default") return telego.NewBot(token)
apiServerUrl = ""
} else {
_, err := url.Parse(apiServerUrl)
if err != nil {
logger.Warningf("Can't parse API server URL, using default: %v", err)
apiServerUrl = ""
}
} }
return telego.NewBot(token, telego.WithFastHTTPClient(&fasthttp.Client{
Dial: fasthttpproxy.FasthttpSocksDialer(proxyUrl),
}))
} }
// Create robust fasthttp client if !strings.HasPrefix(apiServerUrl, "http") {
client := t.createRobustFastHTTPClient(proxyUrl) logger.Warning("Invalid http(s) URL, using default")
return telego.NewBot(token)
// Build bot options
var options []telego.BotOption
options = append(options, telego.WithFastHTTPClient(client))
if apiServerUrl != "" {
options = append(options, telego.WithAPIServer(apiServerUrl))
} }
return telego.NewBot(token, options...) _, err := url.Parse(apiServerUrl)
if err != nil {
logger.Warningf("Can't parse API server URL, using default instance for tgbot: %v", err)
return telego.NewBot(token)
}
return telego.NewBot(token, telego.WithAPIServer(apiServerUrl))
} }
// IsRunning checks if the Telegram bot is currently running. // IsRunning checks if the Telegram bot is currently running.
@ -427,7 +390,7 @@ func (t *Tgbot) decodeQuery(query string) (string, error) {
// OnReceive starts the message receiving loop for the Telegram bot. // OnReceive starts the message receiving loop for the Telegram bot.
func (t *Tgbot) OnReceive() { func (t *Tgbot) OnReceive() {
params := telego.GetUpdatesParams{ params := telego.GetUpdatesParams{
Timeout: 20, // Reduced timeout to detect connection issues faster Timeout: 30, // Increased timeout to reduce API calls
} }
// Strict singleton: never start a second long-polling loop. // Strict singleton: never start a second long-polling loop.
tgBotMutex.Lock() tgBotMutex.Lock()
@ -445,7 +408,7 @@ func (t *Tgbot) OnReceive() {
botWG.Add(1) botWG.Add(1)
tgBotMutex.Unlock() tgBotMutex.Unlock()
// Get updates channel using the context with shorter timeout for better error recovery // Get updates channel using the context.
updates, _ := bot.UpdatesViaLongPolling(ctx, &params) updates, _ := bot.UpdatesViaLongPolling(ctx, &params)
go func() { go func() {
defer botWG.Done() defer botWG.Done()
@ -2284,36 +2247,10 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R
if len(replyMarkup) > 0 && n == (len(allMessages)-1) { if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
params.ReplyMarkup = replyMarkup[0] params.ReplyMarkup = replyMarkup[0]
} }
_, err := bot.SendMessage(context.Background(), &params)
// Retry logic with exponential backoff for connection errors if err != nil {
maxRetries := 3 logger.Warning("Error sending telegram message :", err)
for attempt := range maxRetries {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
_, err := bot.SendMessage(ctx, &params)
cancel()
if err == nil {
break // Success
}
// Check if error is a connection error
errStr := err.Error()
isConnectionError := strings.Contains(errStr, "connection") ||
strings.Contains(errStr, "timeout") ||
strings.Contains(errStr, "closed")
if isConnectionError && attempt < maxRetries-1 {
// Exponential backoff: 1s, 2s, 4s
backoff := time.Duration(1<<uint(attempt)) * time.Second
logger.Warningf("Connection error sending telegram message (attempt %d/%d), retrying in %v: %v",
attempt+1, maxRetries, backoff, err)
time.Sleep(backoff)
} else {
logger.Warning("Error sending telegram message:", err)
break
}
} }
// Reduced delay to improve performance (only needed for rate limiting) // Reduced delay to improve performance (only needed for rate limiting)
if n < len(allMessages)-1 { // Only delay between messages, not after the last one if n < len(allMessages)-1 { // Only delay between messages, not after the last one
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@ -2648,12 +2585,8 @@ func (t *Tgbot) SendBackupToAdmins() {
if !t.IsRunning() { if !t.IsRunning() {
return return
} }
for i, adminId := range adminIds { for _, adminId := range adminIds {
t.sendBackup(int64(adminId)) t.sendBackup(int64(adminId))
// Add delay between sends to avoid Telegram rate limits
if i < len(adminIds)-1 {
time.Sleep(1 * time.Second)
}
} }
} }
@ -3663,17 +3596,13 @@ func (t *Tgbot) sendBackup(chatId int64) {
logger.Error("Error in trigger a checkpoint operation: ", err) logger.Error("Error in trigger a checkpoint operation: ", err)
} }
// Send database backup
file, err := os.Open(config.GetDBPath()) file, err := os.Open(config.GetDBPath())
if err == nil { if err == nil {
defer file.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
document := tu.Document( document := tu.Document(
tu.ID(chatId), tu.ID(chatId),
tu.File(file), tu.File(file),
) )
_, err = bot.SendDocument(ctx, document) _, err = bot.SendDocument(context.Background(), document)
if err != nil { if err != nil {
logger.Error("Error in uploading backup: ", err) logger.Error("Error in uploading backup: ", err)
} }
@ -3681,20 +3610,13 @@ func (t *Tgbot) sendBackup(chatId int64) {
logger.Error("Error in opening db file for backup: ", err) logger.Error("Error in opening db file for backup: ", err)
} }
// Small delay between file sends
time.Sleep(500 * time.Millisecond)
// Send config.json backup
file, err = os.Open(xray.GetConfigPath()) file, err = os.Open(xray.GetConfigPath())
if err == nil { if err == nil {
defer file.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
document := tu.Document( document := tu.Document(
tu.ID(chatId), tu.ID(chatId),
tu.File(file), tu.File(file),
) )
_, err = bot.SendDocument(ctx, document) _, err = bot.SendDocument(context.Background(), document)
if err != nil { if err != nil {
logger.Error("Error in uploading config.json: ", err) logger.Error("Error in uploading config.json: ", err)
} }

View file

@ -525,12 +525,6 @@
"accountInfo" = "معلومات الحساب" "accountInfo" = "معلومات الحساب"
"outboundStatus" = "حالة المخرج" "outboundStatus" = "حالة المخرج"
"sendThrough" = "أرسل من خلال" "sendThrough" = "أرسل من خلال"
"test" = "اختبار"
"testResult" = "نتيجة الاختبار"
"testing" = "جاري اختبار الاتصال..."
"testSuccess" = "الاختبار ناجح"
"testFailed" = "فشل الاختبار"
"testError" = "فشل اختبار المخرج"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "أضف موازن تحميل" "addBalancer" = "أضف موازن تحميل"

View file

@ -525,12 +525,6 @@
"accountInfo" = "Información de la Cuenta" "accountInfo" = "Información de la Cuenta"
"outboundStatus" = "Estado de Salida" "outboundStatus" = "Estado de Salida"
"sendThrough" = "Enviar a través de" "sendThrough" = "Enviar a través de"
"test" = "Probar"
"testResult" = "Resultado de la prueba"
"testing" = "Probando conexión..."
"testSuccess" = "Prueba exitosa"
"testFailed" = "Prueba fallida"
"testError" = "Error al probar la salida"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "Agregar equilibrador" "addBalancer" = "Agregar equilibrador"

View file

@ -525,12 +525,6 @@
"accountInfo" = "اطلاعات حساب" "accountInfo" = "اطلاعات حساب"
"outboundStatus" = "وضعیت خروجی" "outboundStatus" = "وضعیت خروجی"
"sendThrough" = "ارسال با" "sendThrough" = "ارسال با"
"test" = "تست"
"testResult" = "نتیجه تست"
"testing" = "در حال تست اتصال..."
"testSuccess" = "تست موفقیت‌آمیز"
"testFailed" = "تست ناموفق"
"testError" = "خطا در تست خروجی"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "افزودن بالانسر" "addBalancer" = "افزودن بالانسر"

View file

@ -525,12 +525,6 @@
"accountInfo" = "Informasi Akun" "accountInfo" = "Informasi Akun"
"outboundStatus" = "Status Keluar" "outboundStatus" = "Status Keluar"
"sendThrough" = "Kirim Melalui" "sendThrough" = "Kirim Melalui"
"test" = "Tes"
"testResult" = "Hasil Tes"
"testing" = "Menguji koneksi..."
"testSuccess" = "Tes berhasil"
"testFailed" = "Tes gagal"
"testError" = "Gagal menguji outbound"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "Tambahkan Penyeimbang" "addBalancer" = "Tambahkan Penyeimbang"

View file

@ -525,12 +525,6 @@
"accountInfo" = "アカウント情報" "accountInfo" = "アカウント情報"
"outboundStatus" = "アウトバウンドステータス" "outboundStatus" = "アウトバウンドステータス"
"sendThrough" = "送信経路" "sendThrough" = "送信経路"
"test" = "テスト"
"testResult" = "テスト結果"
"testing" = "接続をテスト中..."
"testSuccess" = "テスト成功"
"testFailed" = "テスト失敗"
"testError" = "アウトバウンドのテストに失敗しました"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "負荷分散追加" "addBalancer" = "負荷分散追加"

View file

@ -525,12 +525,6 @@
"accountInfo" = "Informações da Conta" "accountInfo" = "Informações da Conta"
"outboundStatus" = "Status de Saída" "outboundStatus" = "Status de Saída"
"sendThrough" = "Enviar Através de" "sendThrough" = "Enviar Através de"
"test" = "Testar"
"testResult" = "Resultado do teste"
"testing" = "Testando conexão..."
"testSuccess" = "Teste bem-sucedido"
"testFailed" = "Teste falhou"
"testError" = "Falha ao testar saída"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "Adicionar Balanceador" "addBalancer" = "Adicionar Balanceador"

View file

@ -525,12 +525,6 @@
"accountInfo" = "Информация об учетной записи" "accountInfo" = "Информация об учетной записи"
"outboundStatus" = "Статус исходящего подключения" "outboundStatus" = "Статус исходящего подключения"
"sendThrough" = "Отправить через" "sendThrough" = "Отправить через"
"test" = "Тест"
"testResult" = "Результат теста"
"testing" = "Тестирование соединения..."
"testSuccess" = "Тест успешен"
"testFailed" = "Тест не пройден"
"testError" = "Не удалось протестировать исходящее подключение"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "Создать балансировщик" "addBalancer" = "Создать балансировщик"

View file

@ -525,12 +525,6 @@
"accountInfo" = "Hesap Bilgileri" "accountInfo" = "Hesap Bilgileri"
"outboundStatus" = "Giden Durumu" "outboundStatus" = "Giden Durumu"
"sendThrough" = "Üzerinden Gönder" "sendThrough" = "Üzerinden Gönder"
"test" = "Test"
"testResult" = "Test Sonucu"
"testing" = "Bağlantı test ediliyor..."
"testSuccess" = "Test başarılı"
"testFailed" = "Test başarısız"
"testError" = "Giden test edilemedi"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "Dengeleyici Ekle" "addBalancer" = "Dengeleyici Ekle"

View file

@ -525,12 +525,6 @@
"accountInfo" = "Інформація про обліковий запис" "accountInfo" = "Інформація про обліковий запис"
"outboundStatus" = "Статус виходу" "outboundStatus" = "Статус виходу"
"sendThrough" = "Надіслати через" "sendThrough" = "Надіслати через"
"test" = "Тест"
"testResult" = "Результат тесту"
"testing" = "Тестування з'єднання..."
"testSuccess" = "Тест успішний"
"testFailed" = "Тест не пройдено"
"testError" = "Не вдалося протестувати вихідне з'єднання"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "Додати балансир" "addBalancer" = "Додати балансир"

View file

@ -525,12 +525,6 @@
"accountInfo" = "Thông tin tài khoản" "accountInfo" = "Thông tin tài khoản"
"outboundStatus" = "Trạng thái đầu ra" "outboundStatus" = "Trạng thái đầu ra"
"sendThrough" = "Gửi qua" "sendThrough" = "Gửi qua"
"test" = "Kiểm tra"
"testResult" = "Kết quả kiểm tra"
"testing" = "Đang kiểm tra kết nối..."
"testSuccess" = "Kiểm tra thành công"
"testFailed" = "Kiểm tra thất bại"
"testError" = "Không thể kiểm tra đầu ra"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "Thêm cân bằng" "addBalancer" = "Thêm cân bằng"

View file

@ -525,12 +525,6 @@
"accountInfo" = "帐户信息" "accountInfo" = "帐户信息"
"outboundStatus" = "出站状态" "outboundStatus" = "出站状态"
"sendThrough" = "发送通过" "sendThrough" = "发送通过"
"test" = "测试"
"testResult" = "测试结果"
"testing" = "正在测试连接..."
"testSuccess" = "测试成功"
"testFailed" = "测试失败"
"testError" = "测试出站失败"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "添加负载均衡" "addBalancer" = "添加负载均衡"

View file

@ -525,12 +525,6 @@
"accountInfo" = "帳戶資訊" "accountInfo" = "帳戶資訊"
"outboundStatus" = "出站狀態" "outboundStatus" = "出站狀態"
"sendThrough" = "傳送通過" "sendThrough" = "傳送通過"
"test" = "測試"
"testResult" = "測試結果"
"testing" = "正在測試連接..."
"testSuccess" = "測試成功"
"testFailed" = "測試失敗"
"testError" = "測試出站失敗"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "新增負載均衡" "addBalancer" = "新增負載均衡"