mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-10 06:36:06 +00:00
fix(tgbot): find clients by tgId regardless of settings JSON formatting
The Telegram-bot usage lookup prefiltered inbounds with settings LIKE '%"tgId": N%', which requires the exact space the panel's MarshalIndent happens to emit. Inbounds whose settings were serialized compactly (node sync, imports, external edits) never matched, so the bot reported no configuration even though the client and traffic rows exist. Replace the string match with the driver-portable JSON helpers already used by GetAllEmails, which read the actual clients array on SQLite and Postgres alike. Closes #5805
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
)
|
||||
|
||||
// TestGetClientTrafficTgBot_SettingsSerializationStyles guards against the
|
||||
// prefilter regressing into a formatting-sensitive string match (#5805): the
|
||||
// lookup must find clients whether inbounds.settings stores compact JSON
|
||||
// ("tgId":N, as written by node sync/import) or indented JSON ("tgId": N, as
|
||||
// written by the panel's MarshalIndent).
|
||||
func TestGetClientTrafficTgBot_SettingsSerializationStyles(t *testing.T) {
|
||||
dbDir := t.TempDir()
|
||||
t.Setenv("XUI_DB_FOLDER", dbDir)
|
||||
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
|
||||
t.Fatalf("InitDB: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = database.CloseDB() })
|
||||
|
||||
db := database.GetDB()
|
||||
|
||||
const tgId int64 = 123456789
|
||||
cases := []struct {
|
||||
name string
|
||||
settings string
|
||||
email string
|
||||
port int
|
||||
}{
|
||||
{"compact", `{"clients":[{"id":"u1","email":"compact-user","tgId":123456789}]}`, "compact-user", 41001},
|
||||
{"spaced", `{"clients": [{"id": "u2", "email": "spaced-user", "tgId": 123456789}]}`, "spaced-user", 41002},
|
||||
}
|
||||
for _, c := range cases {
|
||||
inbound := &model.Inbound{UserId: 1, Tag: "tg-" + c.name, Enable: true, Port: c.port, Protocol: model.VLESS, Settings: c.settings}
|
||||
if err := db.Create(inbound).Error; err != nil {
|
||||
t.Fatalf("create %s inbound: %v", c.name, err)
|
||||
}
|
||||
if err := db.Create(&xray.ClientTraffic{InboundId: inbound.Id, Email: c.email, Enable: true, Up: 10, Down: 20}).Error; err != nil {
|
||||
t.Fatalf("create %s client_traffics: %v", c.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
svc := InboundService{}
|
||||
traffics, err := svc.GetClientTrafficTgBot(tgId)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientTrafficTgBot: %v", err)
|
||||
}
|
||||
got := make(map[string]bool, len(traffics))
|
||||
for _, tr := range traffics {
|
||||
got[tr.Email] = true
|
||||
}
|
||||
if len(traffics) != 2 || !got["compact-user"] || !got["spaced-user"] {
|
||||
t.Fatalf("expected traffic for compact-user and spaced-user, got %v", got)
|
||||
}
|
||||
|
||||
other, err := svc.GetClientTrafficTgBot(42)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientTrafficTgBot(42): %v", err)
|
||||
}
|
||||
if len(other) != 0 {
|
||||
t.Fatalf("expected no traffic for unknown tgId, got %d rows", len(other))
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -837,15 +838,27 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||
|
||||
func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffic, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
|
||||
// Retrieve inbounds where settings contain the given tgId
|
||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
idQuery := fmt.Sprintf(
|
||||
"SELECT DISTINCT inbounds.id %s WHERE %s = ?",
|
||||
database.JSONClientsFromInbound(),
|
||||
database.JSONFieldText("client.value", "tgId"),
|
||||
)
|
||||
var inboundIds []int
|
||||
if err := db.Raw(idQuery, strconv.FormatInt(tgId, 10)).Scan(&inboundIds).Error; err != nil {
|
||||
logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var inbounds []*model.Inbound
|
||||
if len(inboundIds) > 0 {
|
||||
err := db.Model(model.Inbound{}).Where("id IN ?", inboundIds).Find(&inbounds).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var emails []string
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.GetClients(inbound)
|
||||
@@ -866,7 +879,7 @@ func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffi
|
||||
traffics := make([]*xray.ClientTraffic, 0, len(uniqEmails))
|
||||
for _, batch := range chunkStrings(uniqEmails, sqliteMaxVars) {
|
||||
var page []*xray.ClientTraffic
|
||||
if err = db.Model(xray.ClientTraffic{}).Where("email IN ?", batch).Find(&page).Error; err != nil {
|
||||
if err := db.Model(xray.ClientTraffic{}).Where("email IN ?", batch).Find(&page).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user