tgbot_client.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. package tgbot
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "html"
  9. "io"
  10. "net/http"
  11. "slices"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  16. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  17. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  18. "github.com/mhsanaei/3x-ui/v3/internal/web/service"
  19. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  20. "github.com/mymmrac/telego"
  21. tu "github.com/mymmrac/telego/telegoutil"
  22. "github.com/skip2/go-qrcode"
  23. )
  24. // BuildClientDraftMessage builds a protocol-neutral summary of the in-progress
  25. // client (email, attached inbounds, traffic limit, expiry, ip limit, comment)
  26. // shown in the multi-inbound add flow. Per-protocol secrets (UUID, password,
  27. // flow, method) are generated by fillProtocolDefaults on submit, so the bot
  28. // never has to track them per inbound itself.
  29. func (t *Tgbot) BuildClientDraftMessage() string {
  30. now := time.Now().UnixMilli()
  31. expiry := ""
  32. switch {
  33. case client_ExpiryTime == 0:
  34. expiry = t.I18nBot("tgbot.unlimited")
  35. case client_ExpiryTime < 0:
  36. expiry = fmt.Sprintf("%d %s", client_ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
  37. default:
  38. diff := client_ExpiryTime - now
  39. if diff > 172800000 {
  40. expiry = time.UnixMilli(client_ExpiryTime).Format("2006-01-02 15:04:05")
  41. } else {
  42. expiry = fmt.Sprintf("%d %s", diff/3600000, t.I18nBot("tgbot.hours"))
  43. }
  44. }
  45. traffic := "♾️ Unlimited(Reset)"
  46. if client_TotalGB > 0 {
  47. traffic = common.FormatTraffic(client_TotalGB)
  48. }
  49. ipLimit := "♾️ Unlimited(Reset)"
  50. if client_LimitIP > 0 {
  51. ipLimit = fmt.Sprint(client_LimitIP)
  52. }
  53. attached := t.describeAttachedInbounds(receiver_inbound_IDs)
  54. if attached == "" {
  55. attached = "—"
  56. }
  57. comment := client_Comment
  58. if comment == "" {
  59. comment = "—"
  60. }
  61. tgID := client_TgID
  62. if tgID == "" {
  63. tgID = "—"
  64. }
  65. var b strings.Builder
  66. b.WriteString("📝 <b>New client draft</b>\r\n")
  67. fmt.Fprintf(&b, "📧 Email: <code>%s</code>\r\n", html.EscapeString(client_Email))
  68. fmt.Fprintf(&b, "🔗 Attached: %s\r\n", html.EscapeString(attached))
  69. fmt.Fprintf(&b, "📊 Traffic: %s\r\n", traffic)
  70. fmt.Fprintf(&b, "📅 Expire: %s\r\n", expiry)
  71. fmt.Fprintf(&b, "🔢 IP limit: %s\r\n", ipLimit)
  72. fmt.Fprintf(&b, "👤 TG user: %s\r\n", html.EscapeString(tgID))
  73. fmt.Fprintf(&b, "💬 Comment: %s\r\n", html.EscapeString(comment))
  74. return b.String()
  75. }
  76. // describeAttachedInbounds returns a short "remark1, remark2" list for the given
  77. // inbound ids, falling back to "#id" when an inbound can't be loaded.
  78. func (t *Tgbot) describeAttachedInbounds(ids []int) string {
  79. if len(ids) == 0 {
  80. return ""
  81. }
  82. parts := make([]string, 0, len(ids))
  83. for _, id := range ids {
  84. ib, err := t.inboundService.GetInbound(id)
  85. if err != nil || ib == nil {
  86. parts = append(parts, fmt.Sprintf("#%d", id))
  87. continue
  88. }
  89. label := ib.Remark
  90. if label == "" {
  91. label = fmt.Sprintf("#%d", id)
  92. }
  93. parts = append(parts, label)
  94. }
  95. return strings.Join(parts, ", ")
  96. }
  97. // SubmitAddClient sends the in-progress client to ClientService.Create with
  98. // the full set of attached inbound ids. Per-inbound fillProtocolDefaults on
  99. // the panel generates UUID/password/auth per protocol, so the bot only
  100. // supplies the universal fields it actually collected.
  101. func (t *Tgbot) SubmitAddClient() (bool, error) {
  102. inboundIDs := receiver_inbound_IDs
  103. if len(inboundIDs) == 0 && receiver_inbound_ID > 0 {
  104. inboundIDs = []int{receiver_inbound_ID}
  105. }
  106. if len(inboundIDs) == 0 {
  107. return false, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
  108. }
  109. tgIDInt, _ := strconv.ParseInt(client_TgID, 10, 64)
  110. client := model.Client{
  111. Email: client_Email,
  112. Enable: client_Enable,
  113. LimitIP: client_LimitIP,
  114. TotalGB: client_TotalGB,
  115. ExpiryTime: client_ExpiryTime,
  116. SubID: client_SubID,
  117. Comment: client_Comment,
  118. Reset: client_Reset,
  119. TgID: tgIDInt,
  120. }
  121. return t.clientService.Create(&t.inboundService, &service.ClientCreatePayload{
  122. Client: client,
  123. InboundIds: inboundIDs,
  124. })
  125. }
  126. // buildSubscriptionURLs builds the HTML sub page URL and JSON subscription URL for a client email
  127. func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
  128. // Resolve subId from client email
  129. traffic, client, err := t.inboundService.GetClientByEmail(email)
  130. _ = traffic
  131. if err != nil || client == nil {
  132. return "", "", errors.New("client not found")
  133. }
  134. // Gather settings to construct absolute URLs
  135. subURI, _ := t.settingService.GetSubURI()
  136. subJsonURI, _ := t.settingService.GetSubJsonURI()
  137. subDomain, _ := t.settingService.GetSubDomain()
  138. subPort, _ := t.settingService.GetSubPort()
  139. subPath, _ := t.settingService.GetSubPath()
  140. subJsonPath, _ := t.settingService.GetSubJsonPath()
  141. subJsonEnable, _ := t.settingService.GetSubJsonEnable()
  142. subKeyFile, _ := t.settingService.GetSubKeyFile()
  143. subCertFile, _ := t.settingService.GetSubCertFile()
  144. tls := (subKeyFile != "" && subCertFile != "")
  145. scheme := "http"
  146. if tls {
  147. scheme = "https"
  148. }
  149. // Fallbacks
  150. if subDomain == "" {
  151. // try panel domain, otherwise OS hostname
  152. if d, err := t.settingService.GetWebDomain(); err == nil && d != "" {
  153. subDomain = d
  154. } else if hostname != "" {
  155. subDomain = hostname
  156. } else {
  157. subDomain = "localhost"
  158. }
  159. }
  160. host := subDomain
  161. if (subPort == 443 && tls) || (subPort == 80 && !tls) {
  162. // standard ports: no port in host
  163. } else {
  164. host = fmt.Sprintf("%s:%d", subDomain, subPort)
  165. }
  166. // Ensure paths
  167. if !strings.HasPrefix(subPath, "/") {
  168. subPath = "/" + subPath
  169. }
  170. if !strings.HasSuffix(subPath, "/") {
  171. subPath = subPath + "/"
  172. }
  173. if !strings.HasPrefix(subJsonPath, "/") {
  174. subJsonPath = "/" + subJsonPath
  175. }
  176. if !strings.HasSuffix(subJsonPath, "/") {
  177. subJsonPath = subJsonPath + "/"
  178. }
  179. var subURL string
  180. var subJsonURL string
  181. // If pre-configured URIs are available, use them directly
  182. if subURI != "" {
  183. if !strings.HasSuffix(subURI, "/") {
  184. subURI = subURI + "/"
  185. }
  186. subURL = fmt.Sprintf("%s%s", subURI, client.SubID)
  187. } else {
  188. subURL = fmt.Sprintf("%s://%s%s%s", scheme, host, subPath, client.SubID)
  189. }
  190. if subJsonURI != "" {
  191. if !strings.HasSuffix(subJsonURI, "/") {
  192. subJsonURI = subJsonURI + "/"
  193. }
  194. subJsonURL = fmt.Sprintf("%s%s", subJsonURI, client.SubID)
  195. } else {
  196. subJsonURL = fmt.Sprintf("%s://%s%s%s", scheme, host, subJsonPath, client.SubID)
  197. }
  198. if !subJsonEnable {
  199. subJsonURL = ""
  200. }
  201. return subURL, subJsonURL, nil
  202. }
  203. // sendClientSubLinks sends the subscription links for the client to the chat.
  204. func (t *Tgbot) sendClientSubLinks(chatId int64, email string) {
  205. subURL, subJsonURL, err := t.buildSubscriptionURLs(email)
  206. if err != nil {
  207. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  208. return
  209. }
  210. msg := "Subscription URL:\r\n<code>" + subURL + "</code>"
  211. if subJsonURL != "" {
  212. msg += "\r\n\r\nJSON URL:\r\n<code>" + subJsonURL + "</code>"
  213. }
  214. inlineKeyboard := tu.InlineKeyboard(
  215. tu.InlineKeyboardRow(
  216. tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("client_individual_links "+email)),
  217. ),
  218. tu.InlineKeyboardRow(
  219. tu.InlineKeyboardButton(t.I18nBot("qrCode")).WithCallbackData(t.encodeQuery("client_qr_links "+email)),
  220. ),
  221. )
  222. t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
  223. }
  224. // sendClientIndividualLinks fetches the subscription content (individual links) and sends it to the user
  225. func (t *Tgbot) sendClientIndividualLinks(chatId int64, email string) {
  226. // Build the HTML sub page URL; we'll call it with header Accept to get raw content
  227. subURL, _, err := t.buildSubscriptionURLs(email)
  228. if err != nil {
  229. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  230. return
  231. }
  232. // Try to fetch raw subscription links. Prefer plain text response.
  233. req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, subURL, nil)
  234. if err != nil {
  235. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  236. return
  237. }
  238. // Force plain text to avoid HTML page; controller respects Accept header
  239. req.Header.Set("Accept", "text/plain, */*;q=0.1")
  240. // Use optimized client with connection pooling
  241. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  242. defer cancel()
  243. req = req.WithContext(ctx)
  244. resp, err := optimizedHTTPClient.Do(req)
  245. if err != nil {
  246. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  247. return
  248. }
  249. defer resp.Body.Close()
  250. bodyBytes, err := io.ReadAll(resp.Body)
  251. if err != nil {
  252. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  253. return
  254. }
  255. // If service is configured to encode (Base64), decode it
  256. encoded, _ := t.settingService.GetSubEncrypt()
  257. var content string
  258. if encoded {
  259. decoded, err := base64.StdEncoding.DecodeString(string(bodyBytes))
  260. if err != nil {
  261. // fallback to raw text
  262. content = string(bodyBytes)
  263. } else {
  264. content = string(decoded)
  265. }
  266. } else {
  267. content = string(bodyBytes)
  268. }
  269. // Normalize line endings and trim
  270. lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n")
  271. var cleaned []string
  272. for _, l := range lines {
  273. l = strings.TrimSpace(l)
  274. if l != "" {
  275. cleaned = append(cleaned, l)
  276. }
  277. }
  278. if len(cleaned) == 0 {
  279. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noResult"))
  280. return
  281. }
  282. // Send in chunks to respect message length; use monospace formatting
  283. const maxPerMessage = 50
  284. for i := 0; i < len(cleaned); i += maxPerMessage {
  285. j := min(i+maxPerMessage, len(cleaned))
  286. chunk := cleaned[i:j]
  287. var msg strings.Builder
  288. msg.WriteString(t.I18nBot("subscription.individualLinks"))
  289. msg.WriteString(":\r\n")
  290. for _, link := range chunk {
  291. // wrap each link in <code>
  292. msg.WriteString("<code>")
  293. msg.WriteString(link)
  294. msg.WriteString("</code>\r\n")
  295. }
  296. t.SendMsgToTgbot(chatId, msg.String())
  297. }
  298. }
  299. // sendClientQRLinks generates QR images for subscription URL, JSON URL, and a few individual links, then sends them
  300. func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
  301. subURL, subJsonURL, err := t.buildSubscriptionURLs(email)
  302. if err != nil {
  303. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  304. return
  305. }
  306. // Helper to create QR PNG bytes from content
  307. createQR := func(content string, size int) ([]byte, error) {
  308. if size <= 0 {
  309. size = 256
  310. }
  311. return qrcode.Encode(content, qrcode.Medium, size)
  312. }
  313. // Inform user
  314. t.SendMsgToTgbot(chatId, "QRCode for client "+email+":")
  315. // Send sub URL QR (filename: sub.png)
  316. if png, err := createQR(subURL, 320); err == nil {
  317. document := tu.Document(
  318. tu.ID(chatId),
  319. tu.FileFromBytes(png, "sub.png"),
  320. )
  321. _, _ = bot.SendDocument(context.Background(), document)
  322. } else {
  323. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  324. }
  325. // Send JSON URL QR (filename: subjson.png) when available
  326. if subJsonURL != "" {
  327. if png, err := createQR(subJsonURL, 320); err == nil {
  328. document := tu.Document(
  329. tu.ID(chatId),
  330. tu.FileFromBytes(png, "subjson.png"),
  331. )
  332. _, _ = bot.SendDocument(context.Background(), document)
  333. } else {
  334. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  335. }
  336. }
  337. // Also generate a few individual links' QRs (first up to 5)
  338. subPageURL := subURL
  339. req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, subPageURL, nil)
  340. if err == nil {
  341. req.Header.Set("Accept", "text/plain, */*;q=0.1")
  342. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  343. defer cancel()
  344. req = req.WithContext(ctx)
  345. if resp, err := optimizedHTTPClient.Do(req); err == nil {
  346. body, _ := io.ReadAll(resp.Body)
  347. _ = resp.Body.Close()
  348. encoded, _ := t.settingService.GetSubEncrypt()
  349. var content string
  350. if encoded {
  351. if dec, err := base64.StdEncoding.DecodeString(string(body)); err == nil {
  352. content = string(dec)
  353. } else {
  354. content = string(body)
  355. }
  356. } else {
  357. content = string(body)
  358. }
  359. lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n")
  360. var cleaned []string
  361. for _, l := range lines {
  362. l = strings.TrimSpace(l)
  363. if l != "" {
  364. cleaned = append(cleaned, l)
  365. }
  366. }
  367. if len(cleaned) > 0 {
  368. max := min(len(cleaned), 5)
  369. for i := range max {
  370. if png, err := createQR(cleaned[i], 320); err == nil {
  371. // Use the email as filename for individual link QR
  372. filename := email + ".png"
  373. document := tu.Document(
  374. tu.ID(chatId),
  375. tu.FileFromBytes(png, filename),
  376. )
  377. _, _ = bot.SendDocument(context.Background(), document)
  378. // Reduced delay for better performance
  379. if i < max-1 { // Only delay between documents, not after the last one
  380. time.Sleep(50 * time.Millisecond)
  381. }
  382. }
  383. }
  384. }
  385. }
  386. }
  387. }
  388. // clientInfoMsg formats client information message based on traffic and flags.
  389. func (t *Tgbot) clientInfoMsg(
  390. traffic *xray.ClientTraffic,
  391. printEnabled bool,
  392. printOnline bool,
  393. printActive bool,
  394. printDate bool,
  395. printTraffic bool,
  396. printRefreshed bool,
  397. ) string {
  398. now := time.Now().Unix()
  399. expiryTime := ""
  400. flag := false
  401. diff := traffic.ExpiryTime/1000 - now
  402. if traffic.ExpiryTime == 0 {
  403. expiryTime = t.I18nBot("tgbot.unlimited")
  404. } else if diff > 172800 || !traffic.Enable {
  405. expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
  406. if diff > 0 {
  407. days := diff / 86400
  408. hours := (diff % 86400) / 3600
  409. minutes := (diff % 3600) / 60
  410. remainingTime := ""
  411. if days > 0 {
  412. remainingTime += fmt.Sprintf("%d %s ", days, t.I18nBot("tgbot.days"))
  413. }
  414. if hours > 0 {
  415. remainingTime += fmt.Sprintf("%d %s ", hours, t.I18nBot("tgbot.hours"))
  416. }
  417. if minutes > 0 {
  418. remainingTime += fmt.Sprintf("%d %s", minutes, t.I18nBot("tgbot.minutes"))
  419. }
  420. expiryTime += fmt.Sprintf(" (%s)", remainingTime)
  421. }
  422. } else if traffic.ExpiryTime < 0 {
  423. expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
  424. flag = true
  425. } else {
  426. expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours"))
  427. flag = true
  428. }
  429. total := ""
  430. if traffic.Total == 0 {
  431. total = t.I18nBot("tgbot.unlimited")
  432. } else {
  433. total = common.FormatTraffic(traffic.Total)
  434. }
  435. enabled := ""
  436. isEnabled, err := t.clientService.CheckIsEnabledByEmail(&t.inboundService, traffic.Email)
  437. if err != nil {
  438. logger.Warning(err)
  439. enabled = t.I18nBot("tgbot.wentWrong")
  440. } else if isEnabled {
  441. enabled = t.I18nBot("tgbot.messages.yes")
  442. } else {
  443. enabled = t.I18nBot("tgbot.messages.no")
  444. }
  445. active := ""
  446. if traffic.Enable {
  447. active = t.I18nBot("tgbot.messages.yes")
  448. } else {
  449. active = t.I18nBot("tgbot.messages.no")
  450. }
  451. status := t.I18nBot("tgbot.offline")
  452. isOnline := false
  453. if process := service.XrayProcess(); process != nil && process.IsRunning() {
  454. if slices.Contains(process.GetOnlineClients(), traffic.Email) {
  455. status = t.I18nBot("tgbot.online")
  456. isOnline = true
  457. }
  458. }
  459. output := ""
  460. output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
  461. if attachIds, err := t.clientService.GetInboundIdsForEmail(nil, traffic.Email); err == nil && len(attachIds) > 0 {
  462. output += fmt.Sprintf("🔗 Inbounds: %s\r\n", t.describeAttachedInbounds(attachIds))
  463. }
  464. if printEnabled {
  465. output += t.I18nBot("tgbot.messages.enabled", "Enable=="+enabled)
  466. }
  467. if printOnline {
  468. output += t.I18nBot("tgbot.messages.online", "Status=="+status)
  469. if !isOnline && traffic.LastOnline > 0 {
  470. output += t.I18nBot("tgbot.messages.lastOnline", "Time=="+time.UnixMilli(traffic.LastOnline).Format("2006-01-02 15:04:05"))
  471. }
  472. }
  473. if printActive {
  474. output += t.I18nBot("tgbot.messages.active", "Enable=="+active)
  475. }
  476. if printDate {
  477. if flag {
  478. output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
  479. } else {
  480. output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
  481. }
  482. }
  483. if printTraffic {
  484. output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
  485. output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
  486. output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
  487. }
  488. if printRefreshed {
  489. output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
  490. }
  491. return output
  492. }
  493. // clientOwnedByTgUser reports whether email belongs to a client bound to this
  494. // Telegram account, the same list the self-service usage command reads.
  495. func (t *Tgbot) clientOwnedByTgUser(tgUserID int64, email string) bool {
  496. traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
  497. if err != nil {
  498. return false
  499. }
  500. for _, traffic := range traffics {
  501. if traffic.Email == email {
  502. return true
  503. }
  504. }
  505. return false
  506. }
  507. // getClientUsage retrieves and sends client usage information to the chat.
  508. func (t *Tgbot) getClientUsage(chatId int64, tgUserID int64, email ...string) {
  509. traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
  510. if err != nil {
  511. logger.Warning(err)
  512. msg := t.I18nBot("tgbot.wentWrong")
  513. t.SendMsgToTgbot(chatId, msg)
  514. return
  515. }
  516. if len(traffics) == 0 {
  517. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10)))
  518. return
  519. }
  520. output := ""
  521. if len(traffics) > 0 {
  522. if len(email) > 0 {
  523. for _, traffic := range traffics {
  524. if traffic.Email == email[0] {
  525. output := t.clientInfoMsg(traffic, true, true, true, true, true, true)
  526. t.SendMsgToTgbot(chatId, output)
  527. return
  528. }
  529. }
  530. msg := t.I18nBot("tgbot.noResult")
  531. t.SendMsgToTgbot(chatId, msg)
  532. return
  533. } else {
  534. for _, traffic := range traffics {
  535. output += t.clientInfoMsg(traffic, true, true, true, true, true, false)
  536. output += "\r\n"
  537. }
  538. }
  539. }
  540. output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
  541. t.SendMsgToTgbot(chatId, output)
  542. output = t.I18nBot("tgbot.commands.pleaseChoose")
  543. t.SendAnswer(chatId, output, false)
  544. }
  545. // searchClientIps searches and sends client IP addresses for the given email.
  546. func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) {
  547. ips, err := t.inboundService.GetInboundClientIps(email)
  548. if err != nil || len(ips) == 0 {
  549. ips = t.I18nBot("tgbot.noIpRecord")
  550. }
  551. formattedIps := ips
  552. if err == nil && len(ips) > 0 {
  553. type ipWithTimestamp struct {
  554. IP string `json:"ip"`
  555. Timestamp int64 `json:"timestamp"`
  556. }
  557. var ipsWithTime []ipWithTimestamp
  558. if json.Unmarshal([]byte(ips), &ipsWithTime) == nil && len(ipsWithTime) > 0 {
  559. lines := make([]string, 0, len(ipsWithTime))
  560. for _, item := range ipsWithTime {
  561. if item.IP == "" {
  562. continue
  563. }
  564. if item.Timestamp > 0 {
  565. ts := time.Unix(item.Timestamp, 0).Format("2006-01-02 15:04:05")
  566. lines = append(lines, fmt.Sprintf("%s (%s)", item.IP, ts))
  567. continue
  568. }
  569. lines = append(lines, item.IP)
  570. }
  571. if len(lines) > 0 {
  572. formattedIps = strings.Join(lines, "\n")
  573. }
  574. } else {
  575. var oldIps []string
  576. if json.Unmarshal([]byte(ips), &oldIps) == nil && len(oldIps) > 0 {
  577. formattedIps = strings.Join(oldIps, "\n")
  578. }
  579. }
  580. }
  581. output := ""
  582. output += t.I18nBot("tgbot.messages.email", "Email=="+email)
  583. output += t.I18nBot("tgbot.messages.ips", "IPs=="+formattedIps)
  584. output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
  585. inlineKeyboard := tu.InlineKeyboard(
  586. tu.InlineKeyboardRow(
  587. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("ips_refresh "+email)),
  588. ),
  589. tu.InlineKeyboardRow(
  590. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clearIPs")).WithCallbackData(t.encodeQuery("clear_ips "+email)),
  591. ),
  592. )
  593. if len(messageID) > 0 {
  594. t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
  595. } else {
  596. t.SendMsgToTgbot(chatId, output, inlineKeyboard)
  597. }
  598. }
  599. // clientTelegramUserInfo retrieves and sends Telegram user info for the client.
  600. func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...int) {
  601. traffic, client, err := t.inboundService.GetClientByEmail(email)
  602. if err != nil {
  603. logger.Warning(err)
  604. msg := t.I18nBot("tgbot.wentWrong")
  605. t.SendMsgToTgbot(chatId, msg)
  606. return
  607. }
  608. if client == nil {
  609. msg := t.I18nBot("tgbot.noResult")
  610. t.SendMsgToTgbot(chatId, msg)
  611. return
  612. }
  613. tgId := "None"
  614. if client.TgID != 0 {
  615. tgId = strconv.FormatInt(client.TgID, 10)
  616. }
  617. output := ""
  618. output += t.I18nBot("tgbot.messages.email", "Email=="+email)
  619. output += t.I18nBot("tgbot.messages.TGUser", "TelegramID=="+tgId)
  620. output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
  621. inlineKeyboard := tu.InlineKeyboard(
  622. tu.InlineKeyboardRow(
  623. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("tgid_refresh "+email)),
  624. ),
  625. tu.InlineKeyboardRow(
  626. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.removeTGUser")).WithCallbackData(t.encodeQuery("tgid_remove "+email)),
  627. ),
  628. )
  629. if len(messageID) > 0 {
  630. t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
  631. } else {
  632. t.SendMsgToTgbot(chatId, output, inlineKeyboard)
  633. requestUser := telego.KeyboardButtonRequestUsers{
  634. RequestID: int32(traffic.Id),
  635. UserIsBot: new(bool),
  636. }
  637. keyboard := tu.Keyboard(
  638. tu.KeyboardRow(
  639. tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUsers(&requestUser),
  640. ),
  641. tu.KeyboardRow(
  642. tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")),
  643. ),
  644. ).WithIsPersistent().WithResizeKeyboard()
  645. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.buttons.selectOneTGUser"), keyboard)
  646. }
  647. }
  648. // searchClient searches for a client by email and sends the information.
  649. func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
  650. traffic, err := t.inboundService.GetClientTrafficByEmail(email)
  651. if err != nil {
  652. logger.Warning(err)
  653. msg := t.I18nBot("tgbot.wentWrong")
  654. t.SendMsgToTgbot(chatId, msg)
  655. return
  656. }
  657. if traffic == nil {
  658. msg := t.I18nBot("tgbot.noResult")
  659. t.SendMsgToTgbot(chatId, msg)
  660. return
  661. }
  662. output := t.clientInfoMsg(traffic, true, true, true, true, true, true)
  663. inlineKeyboard := tu.InlineKeyboard(
  664. tu.InlineKeyboardRow(
  665. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("client_refresh "+email)),
  666. ),
  667. tu.InlineKeyboardRow(
  668. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic "+email)),
  669. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData(t.encodeQuery("limit_traffic "+email)),
  670. ),
  671. tu.InlineKeyboardRow(
  672. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData(t.encodeQuery("reset_exp "+email)),
  673. ),
  674. tu.InlineKeyboardRow(
  675. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLog")).WithCallbackData(t.encodeQuery("ip_log "+email)),
  676. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLimit")).WithCallbackData(t.encodeQuery("ip_limit "+email)),
  677. ),
  678. tu.InlineKeyboardRow(
  679. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.setTGUser")).WithCallbackData(t.encodeQuery("tg_user "+email)),
  680. ),
  681. tu.InlineKeyboardRow(
  682. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.toggle")).WithCallbackData(t.encodeQuery("toggle_enable "+email)),
  683. ),
  684. )
  685. if len(messageID) > 0 {
  686. t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
  687. } else {
  688. t.SendMsgToTgbot(chatId, output, inlineKeyboard)
  689. }
  690. }
  691. // getCommonClientButtons returns the shared inline keyboard rows for the
  692. // client-first multi-inbound add flow. Per-protocol secrets (UUID, password,
  693. // flow, method) are generated by fillProtocolDefaults on submit, so the bot
  694. // only exposes the universal client fields here.
  695. func (t *Tgbot) getCommonClientButtons() [][]telego.InlineKeyboardButton {
  696. attachLabel := fmt.Sprintf("➕ Attach inbound (%d)", len(receiver_inbound_IDs))
  697. return [][]telego.InlineKeyboardButton{
  698. tu.InlineKeyboardRow(
  699. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"),
  700. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_comment")).WithCallbackData("add_client_ch_default_comment"),
  701. ),
  702. tu.InlineKeyboardRow(
  703. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData("add_client_ch_default_traffic"),
  704. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData("add_client_ch_default_exp"),
  705. ),
  706. tu.InlineKeyboardRow(
  707. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLimit")).WithCallbackData("add_client_ch_default_ip_limit"),
  708. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.setTGUser")).WithCallbackData("add_client_ch_default_tg_id"),
  709. ),
  710. tu.InlineKeyboardRow(
  711. tu.InlineKeyboardButton(attachLabel).WithCallbackData("add_client_attach_more"),
  712. ),
  713. tu.InlineKeyboardRow(
  714. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
  715. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitEnable")).WithCallbackData("add_client_submit_enable"),
  716. ),
  717. tu.InlineKeyboardRow(
  718. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
  719. ),
  720. }
  721. }
  722. // addClient renders the draft message + shared client-first keyboard.
  723. func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
  724. inlineKeyboard := tu.InlineKeyboard(t.getCommonClientButtons()...)
  725. if len(messageID) > 0 {
  726. t.editMessageTgBot(chatId, messageID[0], msg, inlineKeyboard)
  727. } else {
  728. t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
  729. }
  730. }