mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-20 12:55:00 +03:00
4df570b3b0
The bot's "individual links" and QR actions fetched the client's own subscription over HTTP from the public URL it builds for display. With no sub or web domain set that URL falls back to the machine's hostname, which usually does not resolve (`lookup exhausted-reply: no such host`), and even a resolvable name fails behind NAT, a firewall or a disabled sub server. Both actions now ask InboundService.GetSubLinks, the in-process provider the panel's links API already uses, with the host taken from that same URL so the link addresses are unchanged. The now-unused pooled HTTP client goes too. Closes #6597
90 lines
3.0 KiB
Go
90 lines
3.0 KiB
Go
package tgbot
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/sub"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
|
)
|
|
|
|
// recordingBotServer answers sendMessage and records every text it was asked to send.
|
|
func recordingBotServer(t *testing.T) func() []string {
|
|
t.Helper()
|
|
var mu sync.Mutex
|
|
var texts []string
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/bot"+testBotToken+"/sendMessage" {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
var body struct {
|
|
Text string `json:"text"`
|
|
}
|
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
|
mu.Lock()
|
|
texts = append(texts, body.Text)
|
|
mu.Unlock()
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true, "result": map[string]any{
|
|
"message_id": 1, "date": 0, "chat": map[string]any{"id": ownerTgID, "type": "private"},
|
|
}})
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
swapTestBot(t, srv.URL)
|
|
return func() []string {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return append([]string(nil), texts...)
|
|
}
|
|
}
|
|
|
|
// With no sub or web domain set, the only host the bot knows is the machine
|
|
// name, which need not resolve; the links must not depend on reaching it.
|
|
func TestIndividualLinksDoNotNeedAResolvableHost(t *testing.T) {
|
|
sent := recordingBotServer(t)
|
|
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
|
|
service.RegisterSubLinkProvider(sub.NewLinkProvider())
|
|
|
|
const uuid = "11111111-2222-4333-8444-555555555555"
|
|
db := database.GetDB()
|
|
ib := &model.Inbound{
|
|
UserId: 1, Tag: "in-443", Enable: true, Listen: "203.0.113.5", Port: 443,
|
|
Protocol: model.VLESS, Remark: "in",
|
|
Settings: `{"clients":[{"id":"` + uuid + `","email":"` + ownerMail + `","tgId":4242,"subId":"sub-owned","enable":true}],"decryption":"none"}`,
|
|
}
|
|
if err := db.Create(ib).Error; err != nil {
|
|
t.Fatalf("seed inbound: %v", err)
|
|
}
|
|
rec := &model.ClientRecord{Email: ownerMail, SubID: "sub-owned", UUID: uuid, TgID: ownerTgID, Enable: true}
|
|
if err := db.Create(rec).Error; err != nil {
|
|
t.Fatalf("seed client: %v", err)
|
|
}
|
|
if err := db.Create(&model.ClientInbound{ClientId: rec.Id, InboundId: ib.Id}).Error; err != nil {
|
|
t.Fatalf("seed client_inbound: %v", err)
|
|
}
|
|
if err := db.Create(&xray.ClientTraffic{InboundId: ib.Id, Email: ownerMail, Enable: true}).Error; err != nil {
|
|
t.Fatalf("seed traffic: %v", err)
|
|
}
|
|
|
|
origHost, origRunning := hostname, isRunning
|
|
t.Cleanup(func() { hostname, isRunning = origHost, origRunning })
|
|
hostname, isRunning = "unresolvable-panel-host.invalid", true
|
|
|
|
tapClientLinks(t, &Tgbot{}, ownerTgID, "client_individual_links "+ownerMail)
|
|
|
|
got := strings.Join(sent(), "\n")
|
|
if !strings.Contains(got, "vless://"+uuid+"@203.0.113.5:443") {
|
|
t.Fatalf("bot sent %q, want the client's vless link", got)
|
|
}
|
|
}
|