| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783 |
- package tgbot
- import (
- "context"
- "encoding/base64"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "slices"
- "strconv"
- "strings"
- "time"
- "github.com/mhsanaei/3x-ui/v3/internal/database/model"
- "github.com/mhsanaei/3x-ui/v3/internal/logger"
- "github.com/mhsanaei/3x-ui/v3/internal/util/common"
- "github.com/mhsanaei/3x-ui/v3/internal/web/service"
- "github.com/mhsanaei/3x-ui/v3/internal/xray"
- "github.com/mymmrac/telego"
- tu "github.com/mymmrac/telego/telegoutil"
- "github.com/skip2/go-qrcode"
- )
- // BuildClientDraftMessage builds a protocol-neutral summary of the in-progress
- // client (email, attached inbounds, traffic limit, expiry, ip limit, comment)
- // shown in the multi-inbound add flow. Per-protocol secrets (UUID, password,
- // flow, method) are generated by fillProtocolDefaults on submit, so the bot
- // never has to track them per inbound itself.
- func (t *Tgbot) BuildClientDraftMessage() string {
- now := time.Now().UnixMilli()
- expiry := ""
- switch {
- case client_ExpiryTime == 0:
- expiry = t.I18nBot("tgbot.unlimited")
- case client_ExpiryTime < 0:
- expiry = fmt.Sprintf("%d %s", client_ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
- default:
- diff := client_ExpiryTime - now
- if diff > 172800000 {
- expiry = time.UnixMilli(client_ExpiryTime).Format("2006-01-02 15:04:05")
- } else {
- expiry = fmt.Sprintf("%d %s", diff/3600000, t.I18nBot("tgbot.hours"))
- }
- }
- traffic := "♾️ Unlimited(Reset)"
- if client_TotalGB > 0 {
- traffic = common.FormatTraffic(client_TotalGB)
- }
- ipLimit := "♾️ Unlimited(Reset)"
- if client_LimitIP > 0 {
- ipLimit = fmt.Sprint(client_LimitIP)
- }
- attached := t.describeAttachedInbounds(receiver_inbound_IDs)
- if attached == "" {
- attached = "—"
- }
- comment := client_Comment
- if comment == "" {
- comment = "—"
- }
- tgID := client_TgID
- if tgID == "" {
- tgID = "—"
- }
- var b strings.Builder
- b.WriteString("📝 *New client draft*\r\n")
- b.WriteString(fmt.Sprintf("📧 Email: `%s`\r\n", client_Email))
- b.WriteString(fmt.Sprintf("🔗 Attached: %s\r\n", attached))
- b.WriteString(fmt.Sprintf("📊 Traffic: %s\r\n", traffic))
- b.WriteString(fmt.Sprintf("📅 Expire: %s\r\n", expiry))
- b.WriteString(fmt.Sprintf("🔢 IP limit: %s\r\n", ipLimit))
- b.WriteString(fmt.Sprintf("👤 TG user: %s\r\n", tgID))
- b.WriteString(fmt.Sprintf("💬 Comment: %s\r\n", comment))
- return b.String()
- }
- // describeAttachedInbounds returns a short "remark1, remark2" list for the given
- // inbound ids, falling back to "#id" when an inbound can't be loaded.
- func (t *Tgbot) describeAttachedInbounds(ids []int) string {
- if len(ids) == 0 {
- return ""
- }
- parts := make([]string, 0, len(ids))
- for _, id := range ids {
- ib, err := t.inboundService.GetInbound(id)
- if err != nil || ib == nil {
- parts = append(parts, fmt.Sprintf("#%d", id))
- continue
- }
- label := ib.Remark
- if label == "" {
- label = fmt.Sprintf("#%d", id)
- }
- parts = append(parts, label)
- }
- return strings.Join(parts, ", ")
- }
- // SubmitAddClient sends the in-progress client to ClientService.Create with
- // the full set of attached inbound ids. Per-inbound fillProtocolDefaults on
- // the panel generates UUID/password/auth per protocol, so the bot only
- // supplies the universal fields it actually collected.
- func (t *Tgbot) SubmitAddClient() (bool, error) {
- inboundIDs := receiver_inbound_IDs
- if len(inboundIDs) == 0 && receiver_inbound_ID > 0 {
- inboundIDs = []int{receiver_inbound_ID}
- }
- if len(inboundIDs) == 0 {
- return false, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
- }
- tgIDInt, _ := strconv.ParseInt(client_TgID, 10, 64)
- client := model.Client{
- Email: client_Email,
- Enable: client_Enable,
- LimitIP: client_LimitIP,
- TotalGB: client_TotalGB,
- ExpiryTime: client_ExpiryTime,
- SubID: client_SubID,
- Comment: client_Comment,
- Reset: client_Reset,
- TgID: tgIDInt,
- }
- return t.clientService.Create(&t.inboundService, &service.ClientCreatePayload{
- Client: client,
- InboundIds: inboundIDs,
- })
- }
- // buildSubscriptionURLs builds the HTML sub page URL and JSON subscription URL for a client email
- func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
- // Resolve subId from client email
- traffic, client, err := t.inboundService.GetClientByEmail(email)
- _ = traffic
- if err != nil || client == nil {
- return "", "", errors.New("client not found")
- }
- // Gather settings to construct absolute URLs
- subURI, _ := t.settingService.GetSubURI()
- subJsonURI, _ := t.settingService.GetSubJsonURI()
- subDomain, _ := t.settingService.GetSubDomain()
- subPort, _ := t.settingService.GetSubPort()
- subPath, _ := t.settingService.GetSubPath()
- subJsonPath, _ := t.settingService.GetSubJsonPath()
- subJsonEnable, _ := t.settingService.GetSubJsonEnable()
- subKeyFile, _ := t.settingService.GetSubKeyFile()
- subCertFile, _ := t.settingService.GetSubCertFile()
- tls := (subKeyFile != "" && subCertFile != "")
- scheme := "http"
- if tls {
- scheme = "https"
- }
- // Fallbacks
- if subDomain == "" {
- // try panel domain, otherwise OS hostname
- if d, err := t.settingService.GetWebDomain(); err == nil && d != "" {
- subDomain = d
- } else if hostname != "" {
- subDomain = hostname
- } else {
- subDomain = "localhost"
- }
- }
- host := subDomain
- if (subPort == 443 && tls) || (subPort == 80 && !tls) {
- // standard ports: no port in host
- } else {
- host = fmt.Sprintf("%s:%d", subDomain, subPort)
- }
- // Ensure paths
- if !strings.HasPrefix(subPath, "/") {
- subPath = "/" + subPath
- }
- if !strings.HasSuffix(subPath, "/") {
- subPath = subPath + "/"
- }
- if !strings.HasPrefix(subJsonPath, "/") {
- subJsonPath = "/" + subJsonPath
- }
- if !strings.HasSuffix(subJsonPath, "/") {
- subJsonPath = subJsonPath + "/"
- }
- var subURL string
- var subJsonURL string
- // If pre-configured URIs are available, use them directly
- if subURI != "" {
- if !strings.HasSuffix(subURI, "/") {
- subURI = subURI + "/"
- }
- subURL = fmt.Sprintf("%s%s", subURI, client.SubID)
- } else {
- subURL = fmt.Sprintf("%s://%s%s%s", scheme, host, subPath, client.SubID)
- }
- if subJsonURI != "" {
- if !strings.HasSuffix(subJsonURI, "/") {
- subJsonURI = subJsonURI + "/"
- }
- subJsonURL = fmt.Sprintf("%s%s", subJsonURI, client.SubID)
- } else {
- subJsonURL = fmt.Sprintf("%s://%s%s%s", scheme, host, subJsonPath, client.SubID)
- }
- if !subJsonEnable {
- subJsonURL = ""
- }
- 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 {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
- return
- }
- msg := "Subscription URL:\r\n<code>" + subURL + "</code>"
- if subJsonURL != "" {
- msg += "\r\n\r\nJSON URL:\r\n<code>" + subJsonURL + "</code>"
- }
- inlineKeyboard := tu.InlineKeyboard(
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("client_individual_links "+email)),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("qrCode")).WithCallbackData(t.encodeQuery("client_qr_links "+email)),
- ),
- )
- t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
- }
- // sendClientIndividualLinks fetches the subscription content (individual links) and sends it to the user
- func (t *Tgbot) sendClientIndividualLinks(chatId int64, email string) {
- // Build the HTML sub page URL; we'll call it with header Accept to get raw content
- subURL, _, err := t.buildSubscriptionURLs(email)
- if err != nil {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
- return
- }
- // Try to fetch raw subscription links. Prefer plain text response.
- req, err := http.NewRequest("GET", subURL, nil)
- if err != nil {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
- return
- }
- // Force plain text to avoid HTML page; controller respects Accept header
- req.Header.Set("Accept", "text/plain, */*;q=0.1")
- // Use optimized client with connection pooling
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
- req = req.WithContext(ctx)
- resp, err := optimizedHTTPClient.Do(req)
- if err != nil {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
- return
- }
- defer resp.Body.Close()
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
- return
- }
- // If service is configured to encode (Base64), decode it
- encoded, _ := t.settingService.GetSubEncrypt()
- var content string
- if encoded {
- decoded, err := base64.StdEncoding.DecodeString(string(bodyBytes))
- if err != nil {
- // fallback to raw text
- content = string(bodyBytes)
- } else {
- content = string(decoded)
- }
- } else {
- content = string(bodyBytes)
- }
- // Normalize line endings and trim
- lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n")
- var cleaned []string
- for _, l := range lines {
- l = strings.TrimSpace(l)
- if l != "" {
- cleaned = append(cleaned, l)
- }
- }
- if len(cleaned) == 0 {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noResult"))
- return
- }
- // Send in chunks to respect message length; use monospace formatting
- const maxPerMessage = 50
- for i := 0; i < len(cleaned); i += maxPerMessage {
- j := min(i+maxPerMessage, len(cleaned))
- chunk := cleaned[i:j]
- var msg strings.Builder
- msg.WriteString(t.I18nBot("subscription.individualLinks"))
- msg.WriteString(":\r\n")
- for _, link := range chunk {
- // wrap each link in <code>
- msg.WriteString("<code>")
- msg.WriteString(link)
- msg.WriteString("</code>\r\n")
- }
- t.SendMsgToTgbot(chatId, msg.String())
- }
- }
- // sendClientQRLinks generates QR images for subscription URL, JSON URL, and a few individual links, then sends them
- func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
- subURL, subJsonURL, err := t.buildSubscriptionURLs(email)
- if err != nil {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
- return
- }
- // Helper to create QR PNG bytes from content
- createQR := func(content string, size int) ([]byte, error) {
- if size <= 0 {
- size = 256
- }
- return qrcode.Encode(content, qrcode.Medium, size)
- }
- // Inform user
- t.SendMsgToTgbot(chatId, "QRCode for client "+email+":")
- // Send sub URL QR (filename: sub.png)
- if png, err := createQR(subURL, 320); err == nil {
- document := tu.Document(
- tu.ID(chatId),
- tu.FileFromBytes(png, "sub.png"),
- )
- _, _ = bot.SendDocument(context.Background(), document)
- } else {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
- }
- // Send JSON URL QR (filename: subjson.png) when available
- if subJsonURL != "" {
- if png, err := createQR(subJsonURL, 320); err == nil {
- document := tu.Document(
- tu.ID(chatId),
- tu.FileFromBytes(png, "subjson.png"),
- )
- _, _ = bot.SendDocument(context.Background(), document)
- } else {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
- }
- }
- // Also generate a few individual links' QRs (first up to 5)
- subPageURL := subURL
- req, err := http.NewRequest("GET", subPageURL, nil)
- if err == nil {
- req.Header.Set("Accept", "text/plain, */*;q=0.1")
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
- req = req.WithContext(ctx)
- if resp, err := optimizedHTTPClient.Do(req); err == nil {
- body, _ := io.ReadAll(resp.Body)
- _ = resp.Body.Close()
- encoded, _ := t.settingService.GetSubEncrypt()
- var content string
- if encoded {
- if dec, err := base64.StdEncoding.DecodeString(string(body)); err == nil {
- content = string(dec)
- } else {
- content = string(body)
- }
- } else {
- content = string(body)
- }
- lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n")
- var cleaned []string
- for _, l := range lines {
- l = strings.TrimSpace(l)
- if l != "" {
- cleaned = append(cleaned, l)
- }
- }
- if len(cleaned) > 0 {
- max := min(len(cleaned), 5)
- for i := range max {
- if png, err := createQR(cleaned[i], 320); err == nil {
- // Use the email as filename for individual link QR
- filename := email + ".png"
- document := tu.Document(
- tu.ID(chatId),
- tu.FileFromBytes(png, filename),
- )
- _, _ = bot.SendDocument(context.Background(), document)
- // Reduced delay for better performance
- if i < max-1 { // Only delay between documents, not after the last one
- time.Sleep(50 * time.Millisecond)
- }
- }
- }
- }
- }
- }
- }
- // clientInfoMsg formats client information message based on traffic and flags.
- func (t *Tgbot) clientInfoMsg(
- traffic *xray.ClientTraffic,
- printEnabled bool,
- printOnline bool,
- printActive bool,
- printDate bool,
- printTraffic bool,
- printRefreshed bool,
- ) string {
- now := time.Now().Unix()
- expiryTime := ""
- flag := false
- diff := traffic.ExpiryTime/1000 - now
- if traffic.ExpiryTime == 0 {
- expiryTime = t.I18nBot("tgbot.unlimited")
- } else if diff > 172800 || !traffic.Enable {
- expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
- if diff > 0 {
- days := diff / 86400
- hours := (diff % 86400) / 3600
- minutes := (diff % 3600) / 60
- remainingTime := ""
- if days > 0 {
- remainingTime += fmt.Sprintf("%d %s ", days, t.I18nBot("tgbot.days"))
- }
- if hours > 0 {
- remainingTime += fmt.Sprintf("%d %s ", hours, t.I18nBot("tgbot.hours"))
- }
- if minutes > 0 {
- remainingTime += fmt.Sprintf("%d %s", minutes, t.I18nBot("tgbot.minutes"))
- }
- expiryTime += fmt.Sprintf(" (%s)", remainingTime)
- }
- } else if traffic.ExpiryTime < 0 {
- expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
- flag = true
- } else {
- expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours"))
- flag = true
- }
- total := ""
- if traffic.Total == 0 {
- total = t.I18nBot("tgbot.unlimited")
- } else {
- total = common.FormatTraffic((traffic.Total))
- }
- enabled := ""
- isEnabled, err := t.clientService.CheckIsEnabledByEmail(&t.inboundService, traffic.Email)
- if err != nil {
- logger.Warning(err)
- enabled = t.I18nBot("tgbot.wentWrong")
- } else if isEnabled {
- enabled = t.I18nBot("tgbot.messages.yes")
- } else {
- enabled = t.I18nBot("tgbot.messages.no")
- }
- active := ""
- if traffic.Enable {
- active = t.I18nBot("tgbot.messages.yes")
- } else {
- active = t.I18nBot("tgbot.messages.no")
- }
- status := t.I18nBot("tgbot.offline")
- isOnline := false
- if service.XrayProcess().IsRunning() {
- if slices.Contains(service.XrayProcess().GetOnlineClients(), traffic.Email) {
- status = t.I18nBot("tgbot.online")
- isOnline = true
- }
- }
- output := ""
- output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
- if attachIds, err := t.clientService.GetInboundIdsForEmail(nil, traffic.Email); err == nil && len(attachIds) > 0 {
- output += fmt.Sprintf("🔗 Inbounds: %s\r\n", t.describeAttachedInbounds(attachIds))
- }
- if printEnabled {
- output += t.I18nBot("tgbot.messages.enabled", "Enable=="+enabled)
- }
- if printOnline {
- output += t.I18nBot("tgbot.messages.online", "Status=="+status)
- if !isOnline && traffic.LastOnline > 0 {
- output += t.I18nBot("tgbot.messages.lastOnline", "Time=="+time.UnixMilli(traffic.LastOnline).Format("2006-01-02 15:04:05"))
- }
- }
- if printActive {
- output += t.I18nBot("tgbot.messages.active", "Enable=="+active)
- }
- if printDate {
- if flag {
- output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
- } else {
- output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
- }
- }
- if printTraffic {
- output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
- output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
- output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
- }
- if printRefreshed {
- output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
- }
- 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 {
- logger.Warning(err)
- msg := t.I18nBot("tgbot.wentWrong")
- t.SendMsgToTgbot(chatId, msg)
- return
- }
- if len(traffics) == 0 {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10)))
- return
- }
- output := ""
- if len(traffics) > 0 {
- if len(email) > 0 {
- for _, traffic := range traffics {
- if traffic.Email == email[0] {
- output := t.clientInfoMsg(traffic, true, true, true, true, true, true)
- t.SendMsgToTgbot(chatId, output)
- return
- }
- }
- msg := t.I18nBot("tgbot.noResult")
- t.SendMsgToTgbot(chatId, msg)
- return
- } else {
- for _, traffic := range traffics {
- output += t.clientInfoMsg(traffic, true, true, true, true, true, false)
- output += "\r\n"
- }
- }
- }
- output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
- t.SendMsgToTgbot(chatId, output)
- output = t.I18nBot("tgbot.commands.pleaseChoose")
- 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 {
- ips = t.I18nBot("tgbot.noIpRecord")
- }
- formattedIps := ips
- if err == nil && len(ips) > 0 {
- type ipWithTimestamp struct {
- IP string `json:"ip"`
- Timestamp int64 `json:"timestamp"`
- }
- var ipsWithTime []ipWithTimestamp
- if json.Unmarshal([]byte(ips), &ipsWithTime) == nil && len(ipsWithTime) > 0 {
- lines := make([]string, 0, len(ipsWithTime))
- for _, item := range ipsWithTime {
- if item.IP == "" {
- continue
- }
- if item.Timestamp > 0 {
- ts := time.Unix(item.Timestamp, 0).Format("2006-01-02 15:04:05")
- lines = append(lines, fmt.Sprintf("%s (%s)", item.IP, ts))
- continue
- }
- lines = append(lines, item.IP)
- }
- if len(lines) > 0 {
- formattedIps = strings.Join(lines, "\n")
- }
- } else {
- var oldIps []string
- if json.Unmarshal([]byte(ips), &oldIps) == nil && len(oldIps) > 0 {
- formattedIps = strings.Join(oldIps, "\n")
- }
- }
- }
- output := ""
- output += t.I18nBot("tgbot.messages.email", "Email=="+email)
- output += t.I18nBot("tgbot.messages.ips", "IPs=="+formattedIps)
- output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
- inlineKeyboard := tu.InlineKeyboard(
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("ips_refresh "+email)),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clearIPs")).WithCallbackData(t.encodeQuery("clear_ips "+email)),
- ),
- )
- if len(messageID) > 0 {
- t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
- } else {
- t.SendMsgToTgbot(chatId, output, inlineKeyboard)
- }
- }
- // 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 {
- logger.Warning(err)
- msg := t.I18nBot("tgbot.wentWrong")
- t.SendMsgToTgbot(chatId, msg)
- return
- }
- if client == nil {
- msg := t.I18nBot("tgbot.noResult")
- t.SendMsgToTgbot(chatId, msg)
- return
- }
- tgId := "None"
- if client.TgID != 0 {
- tgId = strconv.FormatInt(client.TgID, 10)
- }
- output := ""
- output += t.I18nBot("tgbot.messages.email", "Email=="+email)
- output += t.I18nBot("tgbot.messages.TGUser", "TelegramID=="+tgId)
- output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
- inlineKeyboard := tu.InlineKeyboard(
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("tgid_refresh "+email)),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.removeTGUser")).WithCallbackData(t.encodeQuery("tgid_remove "+email)),
- ),
- )
- if len(messageID) > 0 {
- t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
- } else {
- t.SendMsgToTgbot(chatId, output, inlineKeyboard)
- requestUser := telego.KeyboardButtonRequestUsers{
- RequestID: int32(traffic.Id),
- UserIsBot: new(bool),
- }
- keyboard := tu.Keyboard(
- tu.KeyboardRow(
- tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUsers(&requestUser),
- ),
- tu.KeyboardRow(
- tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")),
- ),
- ).WithIsPersistent().WithResizeKeyboard()
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.buttons.selectOneTGUser"), keyboard)
- }
- }
- // 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 {
- logger.Warning(err)
- msg := t.I18nBot("tgbot.wentWrong")
- t.SendMsgToTgbot(chatId, msg)
- return
- }
- if traffic == nil {
- msg := t.I18nBot("tgbot.noResult")
- t.SendMsgToTgbot(chatId, msg)
- return
- }
- output := t.clientInfoMsg(traffic, true, true, true, true, true, true)
- inlineKeyboard := tu.InlineKeyboard(
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("client_refresh "+email)),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic "+email)),
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData(t.encodeQuery("limit_traffic "+email)),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData(t.encodeQuery("reset_exp "+email)),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLog")).WithCallbackData(t.encodeQuery("ip_log "+email)),
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLimit")).WithCallbackData(t.encodeQuery("ip_limit "+email)),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.setTGUser")).WithCallbackData(t.encodeQuery("tg_user "+email)),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.toggle")).WithCallbackData(t.encodeQuery("toggle_enable "+email)),
- ),
- )
- if len(messageID) > 0 {
- t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
- } else {
- t.SendMsgToTgbot(chatId, output, inlineKeyboard)
- }
- }
- // getCommonClientButtons returns the shared inline keyboard rows for the
- // client-first multi-inbound add flow. Per-protocol secrets (UUID, password,
- // flow, method) are generated by fillProtocolDefaults on submit, so the bot
- // only exposes the universal client fields here.
- func (t *Tgbot) getCommonClientButtons() [][]telego.InlineKeyboardButton {
- attachLabel := fmt.Sprintf("➕ Attach inbound (%d)", len(receiver_inbound_IDs))
- return [][]telego.InlineKeyboardButton{
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"),
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_comment")).WithCallbackData("add_client_ch_default_comment"),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData("add_client_ch_default_traffic"),
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData("add_client_ch_default_exp"),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLimit")).WithCallbackData("add_client_ch_default_ip_limit"),
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.setTGUser")).WithCallbackData("add_client_ch_default_tg_id"),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(attachLabel).WithCallbackData("add_client_attach_more"),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitEnable")).WithCallbackData("add_client_submit_enable"),
- ),
- tu.InlineKeyboardRow(
- tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
- ),
- }
- }
- // addClient renders the draft message + shared client-first keyboard.
- func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
- inlineKeyboard := tu.InlineKeyboard(t.getCommonClientButtons()...)
- if len(messageID) > 0 {
- t.editMessageTgBot(chatId, messageID[0], msg, inlineKeyboard)
- } else {
- t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
- }
- }
|