tgbot_router.go 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381
  1. package tgbot
  2. import (
  3. "context"
  4. "fmt"
  5. "html"
  6. "slices"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  11. "github.com/mymmrac/telego"
  12. th "github.com/mymmrac/telego/telegohandler"
  13. tu "github.com/mymmrac/telego/telegoutil"
  14. )
  15. // recoverBotPanic must be deferred by every bot handler entry point: telego's
  16. // dispatch has no recovery of its own, so one bad update would kill the panel.
  17. func recoverBotPanic() {
  18. if r := recover(); r != nil {
  19. logger.Error("Recovered panic in Telegram bot handler:", r)
  20. }
  21. }
  22. // runBotHandler runs a bot handler on a worker slot and recovers panics: a bad
  23. // callback must not take down the whole panel, the way a cron panic would not.
  24. func runBotHandler(fn func()) {
  25. messageWorkerPool <- struct{}{}
  26. defer func() { <-messageWorkerPool }()
  27. defer recoverBotPanic()
  28. fn()
  29. }
  30. // chooseInboundClient fetches the inbound once and reuses the row: the inline
  31. // keyboard outlives the inbound, so a stale tap must answer an error, not panic.
  32. func (t *Tgbot) chooseInboundClient(callbackQuery *telego.CallbackQuery, chatId int64, inboundID int, action string) {
  33. inbound, err := t.inboundService.GetInbound(inboundID)
  34. if err != nil {
  35. logger.Warning("chooseInboundClient GetInbound failed:", err)
  36. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getInboundsFailed"))
  37. return
  38. }
  39. clientsKB, err := t.getInboundClientsFor(inbound, action)
  40. if err != nil {
  41. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  42. return
  43. }
  44. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clientsKB)
  45. }
  46. // OnReceive starts the message receiving loop for the Telegram bot.
  47. func (t *Tgbot) OnReceive() {
  48. params := telego.GetUpdatesParams{
  49. Timeout: 20, // Reduced timeout to detect connection issues faster
  50. }
  51. // Strict singleton: never start a second long-polling loop.
  52. tgBotMutex.Lock()
  53. if botCancel != nil || isRunning {
  54. tgBotMutex.Unlock()
  55. logger.Warning("TgBot OnReceive called while already running; ignoring.")
  56. return
  57. }
  58. ctx, cancel := context.WithCancel(context.Background())
  59. botCancel = cancel
  60. isRunning = true
  61. // Add to WaitGroup before releasing the lock so StopBot() can't return
  62. // before this receiver goroutine is accounted for.
  63. botWG.Add(1)
  64. tgBotMutex.Unlock()
  65. // Get updates channel using the context with shorter timeout for better error recovery
  66. updates, _ := bot.UpdatesViaLongPolling(ctx, &params)
  67. go func() {
  68. defer botWG.Done()
  69. h, _ := th.NewBotHandler(bot, updates)
  70. tgBotMutex.Lock()
  71. botHandler = h
  72. tgBotMutex.Unlock()
  73. h.HandleMessage(func(ctx *th.Context, message telego.Message) error {
  74. defer recoverBotPanic()
  75. userStateMgr.clear(messageActor(message))
  76. t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove())
  77. return nil
  78. }, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard")))
  79. h.HandleMessage(func(ctx *th.Context, message telego.Message) error {
  80. defer recoverBotPanic()
  81. if !t.isCommandForCurrentBot(&message) {
  82. return nil
  83. }
  84. // Use goroutine with worker pool for concurrent command processing
  85. go runBotHandler(func() {
  86. userStateMgr.clear(messageActor(message))
  87. t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID))
  88. })
  89. return nil
  90. }, th.AnyCommand())
  91. h.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error {
  92. // Use goroutine with worker pool for concurrent callback processing
  93. go runBotHandler(func() {
  94. userStateMgr.clear(callbackActor(&query))
  95. t.answerCallback(&query, checkAdmin(query.From.ID))
  96. })
  97. return nil
  98. }, th.AnyCallbackQueryWithMessage())
  99. h.HandleMessage(func(ctx *th.Context, message telego.Message) error {
  100. defer recoverBotPanic()
  101. userStateMgr.maybePrune(time.Hour)
  102. actor := messageActor(message)
  103. if userState, exists := userStateMgr.get(actor); exists {
  104. // Only a wizard step touches the draft, so only it takes the lock.
  105. draft := addClientDrafts.forActor(actor)
  106. draft.Lock()
  107. defer draft.Unlock()
  108. switch userState {
  109. case "awaiting_email":
  110. if draft.email == strings.TrimSpace(message.Text) {
  111. t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
  112. userStateMgr.clear(actor)
  113. return nil
  114. }
  115. draft.email = strings.TrimSpace(message.Text)
  116. if t.isSingleWord(draft.email) {
  117. userStateMgr.set(actor, "awaiting_email")
  118. cancel_btn_markup := tu.InlineKeyboard(
  119. tu.InlineKeyboardRow(
  120. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
  121. ),
  122. )
  123. t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup)
  124. } else {
  125. t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_email"), 3, tu.ReplyKeyboardRemove())
  126. userStateMgr.clear(actor)
  127. t.addClient(message.Chat.ID, draft, t.BuildClientDraftMessage(draft))
  128. }
  129. case "awaiting_comment":
  130. if draft.comment == strings.TrimSpace(message.Text) {
  131. t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
  132. userStateMgr.clear(actor)
  133. return nil
  134. }
  135. draft.comment = strings.TrimSpace(message.Text)
  136. t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_comment"), 3, tu.ReplyKeyboardRemove())
  137. userStateMgr.clear(actor)
  138. t.addClient(message.Chat.ID, draft, t.BuildClientDraftMessage(draft))
  139. case "awaiting_tg_id":
  140. input := strings.TrimSpace(message.Text)
  141. if input == "" || input == "-" || strings.EqualFold(input, "none") {
  142. draft.tgID = ""
  143. t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
  144. userStateMgr.clear(actor)
  145. t.addClient(message.Chat.ID, draft, t.BuildClientDraftMessage(draft))
  146. return nil
  147. }
  148. if _, err := strconv.ParseInt(input, 10, 64); err != nil {
  149. cancel_btn_markup := tu.InlineKeyboard(
  150. tu.InlineKeyboardRow(
  151. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
  152. ),
  153. )
  154. t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup)
  155. return nil
  156. }
  157. draft.tgID = input
  158. t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.userSaved"), 3, tu.ReplyKeyboardRemove())
  159. userStateMgr.clear(actor)
  160. t.addClient(message.Chat.ID, draft, t.BuildClientDraftMessage(draft))
  161. }
  162. } else {
  163. if message.UsersShared != nil {
  164. if checkAdmin(message.From.ID) {
  165. for _, sharedUser := range message.UsersShared.Users {
  166. userID := sharedUser.UserID
  167. needRestart, err := t.clientService.SetClientTelegramUserID(&t.inboundService, message.UsersShared.RequestID, userID)
  168. if needRestart {
  169. t.xrayService.SetToNeedRestart()
  170. }
  171. output := ""
  172. if err != nil {
  173. output += t.I18nBot("tgbot.messages.selectUserFailed")
  174. } else {
  175. output += t.I18nBot("tgbot.messages.userSaved")
  176. }
  177. t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove())
  178. }
  179. } else {
  180. t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove())
  181. }
  182. }
  183. }
  184. return nil
  185. }, th.AnyMessage())
  186. _ = h.Start()
  187. }()
  188. }
  189. // answerCommand processes incoming command messages from Telegram users.
  190. func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) {
  191. msg, onlyMessage := "", false
  192. command, _, commandArgs := tu.ParseCommand(message.Text)
  193. // Helper function to handle unknown commands.
  194. handleUnknownCommand := func() {
  195. msg += t.I18nBot("tgbot.commands.unknown")
  196. }
  197. // Handle the command.
  198. switch command {
  199. case "help":
  200. msg += t.I18nBot("tgbot.commands.help")
  201. msg += t.I18nBot("tgbot.commands.pleaseChoose")
  202. case "start":
  203. msg += t.I18nBot("tgbot.commands.start", "Firstname=="+html.EscapeString(message.From.FirstName))
  204. if isAdmin {
  205. msg += t.I18nBot("tgbot.commands.welcome", "Hostname=="+hostname)
  206. }
  207. msg += "\n\n" + t.I18nBot("tgbot.commands.pleaseChoose")
  208. case "status":
  209. onlyMessage = true
  210. msg += t.I18nBot("tgbot.commands.status")
  211. case "id":
  212. onlyMessage = true
  213. msg += t.I18nBot("tgbot.commands.getID", "ID=="+strconv.FormatInt(message.From.ID, 10))
  214. case "usage":
  215. onlyMessage = true
  216. if len(commandArgs) > 0 {
  217. if isAdmin {
  218. t.searchClient(chatId, commandArgs[0])
  219. } else {
  220. t.getClientUsage(chatId, message.From.ID, commandArgs[0])
  221. }
  222. } else {
  223. msg += t.I18nBot("tgbot.commands.usage")
  224. }
  225. case "inbound":
  226. onlyMessage = true
  227. if isAdmin && len(commandArgs) > 0 {
  228. t.searchInbound(chatId, commandArgs[0])
  229. } else {
  230. handleUnknownCommand()
  231. }
  232. case "restart":
  233. onlyMessage = true
  234. if isAdmin {
  235. if len(commandArgs) == 0 {
  236. if t.xrayService.IsXrayRunning() {
  237. err := t.xrayService.RestartXray(true)
  238. if err != nil {
  239. msg += t.I18nBot("tgbot.commands.restartFailed", "Error=="+err.Error())
  240. } else {
  241. msg += t.I18nBot("tgbot.commands.restartSuccess")
  242. }
  243. } else {
  244. msg += t.I18nBot("tgbot.commands.xrayNotRunning")
  245. }
  246. } else {
  247. handleUnknownCommand()
  248. msg += t.I18nBot("tgbot.commands.restartUsage")
  249. }
  250. } else {
  251. handleUnknownCommand()
  252. }
  253. case "clearall":
  254. onlyMessage = true
  255. if isAdmin {
  256. inlineKeyboard := tu.InlineKeyboard(
  257. tu.InlineKeyboardRow(
  258. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("reset_all_traffics_cancel")),
  259. ),
  260. tu.InlineKeyboardRow(
  261. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_all_traffics_c")),
  262. ),
  263. )
  264. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.AreYouSure"), inlineKeyboard)
  265. } else {
  266. handleUnknownCommand()
  267. }
  268. default:
  269. handleUnknownCommand()
  270. }
  271. if msg != "" {
  272. t.sendResponse(chatId, msg, onlyMessage, isAdmin)
  273. }
  274. }
  275. func (t *Tgbot) isCommandForCurrentBot(message *telego.Message) bool {
  276. return isCommandForBot(message.Text, botUsername())
  277. }
  278. func botUsername() string {
  279. if bot == nil {
  280. return ""
  281. }
  282. return bot.Username()
  283. }
  284. func isCommandForBot(text string, username string) bool {
  285. _, commandUsername, _ := tu.ParseCommand(text)
  286. return commandUsername == "" || username == "" || strings.EqualFold(commandUsername, username)
  287. }
  288. // answerCallback processes callback queries from inline keyboards.
  289. func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
  290. chatId := callbackQuery.Message.GetChat().ID
  291. actor := callbackActor(callbackQuery)
  292. // Only an admin's wizard callbacks touch a draft, so only they take its lock:
  293. // a report tap must not wait on a slot, a rejected chat must not be stored.
  294. var draft *clientDraft
  295. if isAdmin && isAddClientStep(callbackQuery.Data) {
  296. draft = addClientDrafts.forActor(actor)
  297. draft.Lock()
  298. defer draft.Unlock()
  299. }
  300. if isAdmin {
  301. // get query from hash storage
  302. decodedQuery, err := t.decodeQuery(callbackQuery.Data)
  303. if err != nil {
  304. // A button older than the 20-minute hash window is the common case
  305. // here; the answer clears it, the message outlives a failed send.
  306. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.noQuery"))
  307. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noQuery"))
  308. return
  309. }
  310. dataArray := strings.Split(decodedQuery, " ")
  311. if len(dataArray) >= 2 && len(dataArray[1]) > 0 {
  312. email := dataArray[1]
  313. switch dataArray[0] {
  314. case "get_clients_for_sub":
  315. inboundIdInt, err := strconv.Atoi(dataArray[1])
  316. if err != nil {
  317. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  318. return
  319. }
  320. t.chooseInboundClient(callbackQuery, chatId, inboundIdInt, "client_sub_links")
  321. case "get_clients_for_individual":
  322. inboundIdInt, err := strconv.Atoi(dataArray[1])
  323. if err != nil {
  324. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  325. return
  326. }
  327. t.chooseInboundClient(callbackQuery, chatId, inboundIdInt, "client_individual_links")
  328. case "get_clients_for_qr":
  329. inboundIdInt, err := strconv.Atoi(dataArray[1])
  330. if err != nil {
  331. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  332. return
  333. }
  334. t.chooseInboundClient(callbackQuery, chatId, inboundIdInt, "client_qr_links")
  335. case "client_sub_links":
  336. t.sendClientSubLinks(chatId, email)
  337. return
  338. case "client_individual_links":
  339. t.sendClientIndividualLinks(chatId, email)
  340. return
  341. case "client_qr_links":
  342. t.sendClientQRLinks(chatId, email)
  343. return
  344. case "client_get_usage":
  345. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.messages.email", "Email=="+email))
  346. t.searchClient(chatId, email)
  347. case "client_refresh":
  348. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email))
  349. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  350. case "client_cancel":
  351. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
  352. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  353. case "ips_refresh":
  354. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.IpRefreshSuccess", "Email=="+email))
  355. t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
  356. case "ips_cancel":
  357. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
  358. t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
  359. case "tgid_refresh":
  360. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.TGIdRefreshSuccess", "Email=="+email))
  361. t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
  362. case "tgid_cancel":
  363. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
  364. t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
  365. case "reset_traffic":
  366. inlineKeyboard := tu.InlineKeyboard(
  367. tu.InlineKeyboardRow(
  368. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
  369. ),
  370. tu.InlineKeyboardRow(
  371. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic_c "+email)),
  372. ),
  373. )
  374. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  375. case "reset_traffic_c":
  376. err := t.inboundService.ResetClientTrafficByEmail(email)
  377. if err == nil {
  378. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetTrafficSuccess", "Email=="+email))
  379. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  380. } else {
  381. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  382. }
  383. case "limit_traffic":
  384. inlineKeyboard := tu.InlineKeyboard(
  385. tu.InlineKeyboardRow(
  386. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
  387. ),
  388. tu.InlineKeyboardRow(
  389. tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 0")),
  390. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" 0")),
  391. ),
  392. tu.InlineKeyboardRow(
  393. tu.InlineKeyboardButton("1 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 1")),
  394. tu.InlineKeyboardButton("5 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 5")),
  395. tu.InlineKeyboardButton("10 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 10")),
  396. ),
  397. tu.InlineKeyboardRow(
  398. tu.InlineKeyboardButton("20 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 20")),
  399. tu.InlineKeyboardButton("30 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 30")),
  400. tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")),
  401. ),
  402. tu.InlineKeyboardRow(
  403. tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 50")),
  404. tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
  405. tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")),
  406. ),
  407. tu.InlineKeyboardRow(
  408. tu.InlineKeyboardButton("100 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 100")),
  409. tu.InlineKeyboardButton("150 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 150")),
  410. tu.InlineKeyboardButton("200 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 200")),
  411. ),
  412. )
  413. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  414. case "limit_traffic_c":
  415. if len(dataArray) == 3 {
  416. limitTraffic, err := strconv.Atoi(dataArray[2])
  417. if err == nil {
  418. needRestart, err := t.clientService.ResetClientTrafficLimitByEmail(&t.inboundService, email, limitTraffic)
  419. if needRestart {
  420. t.xrayService.SetToNeedRestart()
  421. }
  422. if err == nil {
  423. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.setTrafficLimitSuccess", "Email=="+email))
  424. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  425. return
  426. }
  427. }
  428. }
  429. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  430. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  431. case "limit_traffic_in":
  432. if len(dataArray) >= 3 {
  433. oldInputNumber, err := strconv.Atoi(dataArray[2])
  434. inputNumber := oldInputNumber
  435. if err == nil {
  436. if len(dataArray) == 4 {
  437. num, err := strconv.Atoi(dataArray[3])
  438. if err == nil {
  439. inputNumber = updateNumericInput(inputNumber, num)
  440. }
  441. if inputNumber == oldInputNumber {
  442. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  443. return
  444. }
  445. if inputNumber >= 999999 {
  446. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  447. return
  448. }
  449. }
  450. inlineKeyboard := t.numericKeypad(numericKeypadSpec{dataBase: "limit_traffic", dataArgs: email + " ", cancelData: "client_cancel " + email, confirmLabelKey: "tgbot.buttons.confirmNumberAdd"}, inputNumber)
  451. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  452. return
  453. }
  454. }
  455. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  456. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  457. case "add_client_limit_traffic_c":
  458. limitTraffic, _ := strconv.ParseInt(dataArray[1], 10, 64)
  459. draft.totalGB = limitTraffic * 1024 * 1024 * 1024
  460. messageId := callbackQuery.Message.GetMessageID()
  461. message_text := t.BuildClientDraftMessage(draft)
  462. t.addClient(callbackQuery.Message.GetChat().ID, draft, message_text, messageId)
  463. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  464. case "add_client_limit_traffic_in":
  465. if len(dataArray) >= 2 {
  466. oldInputNumber, err := strconv.Atoi(dataArray[1])
  467. inputNumber := oldInputNumber
  468. if err == nil {
  469. if len(dataArray) == 3 {
  470. num, err := strconv.Atoi(dataArray[2])
  471. if err == nil {
  472. inputNumber = updateNumericInput(inputNumber, num)
  473. }
  474. if inputNumber == oldInputNumber {
  475. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  476. return
  477. }
  478. if inputNumber >= 999999 {
  479. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  480. return
  481. }
  482. }
  483. inlineKeyboard := t.numericKeypad(numericKeypadSpec{dataBase: "add_client_limit_traffic", cancelData: "add_client_default_traffic_exp", confirmLabelKey: "tgbot.buttons.confirmNumberAdd"}, inputNumber)
  484. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  485. return
  486. }
  487. }
  488. case "reset_exp":
  489. inlineKeyboard := tu.InlineKeyboard(
  490. tu.InlineKeyboardRow(
  491. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
  492. ),
  493. tu.InlineKeyboardRow(
  494. tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 0")),
  495. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("reset_exp_in "+email+" 0")),
  496. ),
  497. tu.InlineKeyboardRow(
  498. tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 7 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 7")),
  499. tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 10 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 10")),
  500. ),
  501. tu.InlineKeyboardRow(
  502. tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 14 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 14")),
  503. tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 20 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 20")),
  504. ),
  505. tu.InlineKeyboardRow(
  506. tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 1 "+t.I18nBot("tgbot.month")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 30")),
  507. tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 3 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 90")),
  508. ),
  509. tu.InlineKeyboardRow(
  510. tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 6 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 180")),
  511. tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 365")),
  512. ),
  513. )
  514. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  515. case "reset_exp_c":
  516. if len(dataArray) == 3 {
  517. days, err := strconv.ParseInt(dataArray[2], 10, 64)
  518. if err == nil {
  519. var date int64
  520. if days > 0 {
  521. traffic, err := t.inboundService.GetClientTrafficByEmail(email)
  522. if err != nil {
  523. logger.Warning(err)
  524. msg := t.I18nBot("tgbot.wentWrong")
  525. t.SendMsgToTgbot(chatId, msg)
  526. return
  527. }
  528. if traffic == nil {
  529. msg := t.I18nBot("tgbot.noResult")
  530. t.SendMsgToTgbot(chatId, msg)
  531. return
  532. }
  533. if traffic.ExpiryTime > 0 {
  534. if traffic.ExpiryTime-time.Now().Unix()*1000 < 0 {
  535. date = -(days * 24 * 60 * 60000)
  536. } else {
  537. date = traffic.ExpiryTime + days*24*60*60000
  538. }
  539. } else {
  540. date = traffic.ExpiryTime - days*24*60*60000
  541. }
  542. }
  543. needRestart, err := t.clientService.ResetClientExpiryTimeByEmail(&t.inboundService, email, date)
  544. if needRestart {
  545. t.xrayService.SetToNeedRestart()
  546. }
  547. if err == nil {
  548. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.expireResetSuccess", "Email=="+email))
  549. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  550. return
  551. }
  552. }
  553. }
  554. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  555. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  556. case "reset_exp_in":
  557. if len(dataArray) >= 3 {
  558. oldInputNumber, err := strconv.Atoi(dataArray[2])
  559. inputNumber := oldInputNumber
  560. if err == nil {
  561. if len(dataArray) == 4 {
  562. num, err := strconv.Atoi(dataArray[3])
  563. if err == nil {
  564. inputNumber = updateNumericInput(inputNumber, num)
  565. }
  566. if inputNumber == oldInputNumber {
  567. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  568. return
  569. }
  570. if inputNumber >= 999999 {
  571. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  572. return
  573. }
  574. }
  575. inlineKeyboard := t.numericKeypad(numericKeypadSpec{dataBase: "reset_exp", dataArgs: email + " ", cancelData: "client_cancel " + email, confirmLabelKey: "tgbot.buttons.confirmNumber"}, inputNumber)
  576. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  577. return
  578. }
  579. }
  580. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  581. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  582. case "add_client_reset_exp_c":
  583. // The wizard's presets and its custom keypad land in this one case, so a
  584. // second tap replaces the term it set; 0 is the Unlimited button.
  585. days, _ := strconv.ParseInt(dataArray[1], 10, 64)
  586. draft.expiryTime = -days * 24 * 60 * 60000
  587. messageId := callbackQuery.Message.GetMessageID()
  588. message_text := t.BuildClientDraftMessage(draft)
  589. t.addClient(callbackQuery.Message.GetChat().ID, draft, message_text, messageId)
  590. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  591. case "add_client_reset_exp_in":
  592. if len(dataArray) >= 2 {
  593. oldInputNumber, err := strconv.Atoi(dataArray[1])
  594. inputNumber := oldInputNumber
  595. if err == nil {
  596. if len(dataArray) == 3 {
  597. num, err := strconv.Atoi(dataArray[2])
  598. if err == nil {
  599. inputNumber = updateNumericInput(inputNumber, num)
  600. }
  601. if inputNumber == oldInputNumber {
  602. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  603. return
  604. }
  605. if inputNumber >= 999999 {
  606. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  607. return
  608. }
  609. }
  610. inlineKeyboard := t.numericKeypad(numericKeypadSpec{dataBase: "add_client_reset_exp", cancelData: "add_client_default_traffic_exp", confirmLabelKey: "tgbot.buttons.confirmNumberAdd"}, inputNumber)
  611. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  612. return
  613. }
  614. }
  615. case "ip_limit":
  616. inlineKeyboard := tu.InlineKeyboard(
  617. tu.InlineKeyboardRow(
  618. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelIpLimit")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
  619. ),
  620. tu.InlineKeyboardRow(
  621. tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 0")),
  622. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("ip_limit_in "+email+" 0")),
  623. ),
  624. tu.InlineKeyboardRow(
  625. tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 1")),
  626. tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 2")),
  627. ),
  628. tu.InlineKeyboardRow(
  629. tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 3")),
  630. tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 4")),
  631. ),
  632. tu.InlineKeyboardRow(
  633. tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 5")),
  634. tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 6")),
  635. tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 7")),
  636. ),
  637. tu.InlineKeyboardRow(
  638. tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 8")),
  639. tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 9")),
  640. tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 10")),
  641. ),
  642. )
  643. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  644. case "ip_limit_c":
  645. if len(dataArray) == 3 {
  646. count, err := strconv.Atoi(dataArray[2])
  647. if err == nil {
  648. needRestart, err := t.clientService.ResetClientIpLimitByEmail(&t.inboundService, email, count)
  649. if needRestart {
  650. t.xrayService.SetToNeedRestart()
  651. }
  652. if err == nil {
  653. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetIpSuccess", "Email=="+email, "Count=="+strconv.Itoa(count)))
  654. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  655. return
  656. }
  657. }
  658. }
  659. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  660. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  661. case "ip_limit_in":
  662. if len(dataArray) >= 3 {
  663. oldInputNumber, err := strconv.Atoi(dataArray[2])
  664. inputNumber := oldInputNumber
  665. if err == nil {
  666. if len(dataArray) == 4 {
  667. num, err := strconv.Atoi(dataArray[3])
  668. if err == nil {
  669. inputNumber = updateNumericInput(inputNumber, num)
  670. }
  671. if inputNumber == oldInputNumber {
  672. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  673. return
  674. }
  675. if inputNumber >= 999999 {
  676. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  677. return
  678. }
  679. }
  680. inlineKeyboard := t.numericKeypad(numericKeypadSpec{dataBase: "ip_limit", dataArgs: email + " ", cancelData: "client_cancel " + email, confirmLabelKey: "tgbot.buttons.confirmNumber"}, inputNumber)
  681. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  682. return
  683. }
  684. }
  685. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  686. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  687. case "add_client_ip_limit_c":
  688. if len(dataArray) == 2 {
  689. count, _ := strconv.Atoi(dataArray[1])
  690. draft.limitIP = count
  691. }
  692. messageId := callbackQuery.Message.GetMessageID()
  693. message_text := t.BuildClientDraftMessage(draft)
  694. t.addClient(callbackQuery.Message.GetChat().ID, draft, message_text, messageId)
  695. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  696. case "add_client_ip_limit_in":
  697. if len(dataArray) >= 2 {
  698. oldInputNumber, err := strconv.Atoi(dataArray[1])
  699. inputNumber := oldInputNumber
  700. if err == nil {
  701. if len(dataArray) == 3 {
  702. num, err := strconv.Atoi(dataArray[2])
  703. if err == nil {
  704. inputNumber = updateNumericInput(inputNumber, num)
  705. }
  706. if inputNumber == oldInputNumber {
  707. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  708. return
  709. }
  710. if inputNumber >= 999999 {
  711. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  712. return
  713. }
  714. }
  715. inlineKeyboard := t.numericKeypad(numericKeypadSpec{dataBase: "add_client_ip_limit", cancelData: "add_client_default_ip_limit", confirmLabelKey: "tgbot.buttons.confirmNumber"}, inputNumber)
  716. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  717. return
  718. }
  719. }
  720. case "clear_ips":
  721. inlineKeyboard := tu.InlineKeyboard(
  722. tu.InlineKeyboardRow(
  723. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("ips_cancel "+email)),
  724. ),
  725. tu.InlineKeyboardRow(
  726. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmClearIps")).WithCallbackData(t.encodeQuery("clear_ips_c "+email)),
  727. ),
  728. )
  729. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  730. case "clear_ips_c":
  731. err := t.inboundService.ClearClientIps(email)
  732. if err == nil {
  733. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clearIpSuccess", "Email=="+email))
  734. t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
  735. } else {
  736. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  737. }
  738. case "ip_log":
  739. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getIpLog", "Email=="+email))
  740. t.searchClientIps(chatId, email)
  741. case "tg_user":
  742. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getUserInfo", "Email=="+email))
  743. t.clientTelegramUserInfo(chatId, email)
  744. case "tgid_remove":
  745. inlineKeyboard := tu.InlineKeyboard(
  746. tu.InlineKeyboardRow(
  747. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("tgid_cancel "+email)),
  748. ),
  749. tu.InlineKeyboardRow(
  750. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmRemoveTGUser")).WithCallbackData(t.encodeQuery("tgid_remove_c "+email)),
  751. ),
  752. )
  753. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  754. case "tgid_remove_c":
  755. traffic, err := t.inboundService.GetClientTrafficByEmail(email)
  756. if err != nil || traffic == nil {
  757. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  758. return
  759. }
  760. needRestart, err := t.clientService.SetClientTelegramUserID(&t.inboundService, traffic.Id, EmptyTelegramUserID)
  761. if needRestart {
  762. t.xrayService.SetToNeedRestart()
  763. }
  764. if err == nil {
  765. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.removedTGUserSuccess", "Email=="+email))
  766. t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
  767. } else {
  768. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  769. }
  770. case "toggle_enable":
  771. inlineKeyboard := tu.InlineKeyboard(
  772. tu.InlineKeyboardRow(
  773. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)),
  774. ),
  775. tu.InlineKeyboardRow(
  776. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmToggle")).WithCallbackData(t.encodeQuery("toggle_enable_c "+email)),
  777. ),
  778. )
  779. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  780. case "toggle_enable_c":
  781. enabled, needRestart, err := t.clientService.ToggleClientEnableByEmail(&t.inboundService, email)
  782. if needRestart {
  783. t.xrayService.SetToNeedRestart()
  784. }
  785. if err == nil {
  786. if enabled {
  787. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.enableSuccess", "Email=="+email))
  788. } else {
  789. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.disableSuccess", "Email=="+email))
  790. }
  791. t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
  792. } else {
  793. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  794. }
  795. case "get_clients":
  796. inboundId := dataArray[1]
  797. inboundIdInt, err := strconv.Atoi(inboundId)
  798. if err != nil {
  799. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  800. return
  801. }
  802. inbound, err := t.inboundService.GetInbound(inboundIdInt)
  803. if err != nil {
  804. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  805. return
  806. }
  807. clients, err := t.getInboundClients(inboundIdInt)
  808. if err != nil {
  809. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  810. return
  811. }
  812. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clients)
  813. case "add_client_to":
  814. draft.email = t.randomLowerAndNum(8)
  815. draft.limitIP = 0
  816. draft.totalGB = 0
  817. draft.expiryTime = 0
  818. draft.enable = true
  819. draft.tgID = ""
  820. draft.subID = t.randomLowerAndNum(16)
  821. draft.comment = ""
  822. draft.reset = 0
  823. inboundId := dataArray[1]
  824. inboundIdInt, err := strconv.Atoi(inboundId)
  825. if err != nil {
  826. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  827. return
  828. }
  829. draft.receiverInboundID = inboundIdInt
  830. draft.receiverInboundIDs = []int{inboundIdInt}
  831. t.addClient(callbackQuery.Message.GetChat().ID, draft, t.BuildClientDraftMessage(draft))
  832. case "add_client_toggle_attach":
  833. inboundIdStr := dataArray[1]
  834. inboundIdInt, err := strconv.Atoi(inboundIdStr)
  835. if err != nil {
  836. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  837. return
  838. }
  839. found := -1
  840. for i, id := range draft.receiverInboundIDs {
  841. if id == inboundIdInt {
  842. found = i
  843. break
  844. }
  845. }
  846. if found >= 0 {
  847. draft.receiverInboundIDs = append(draft.receiverInboundIDs[:found], draft.receiverInboundIDs[found+1:]...)
  848. } else {
  849. draft.receiverInboundIDs = append(draft.receiverInboundIDs, inboundIdInt)
  850. }
  851. picker, err := t.getInboundsAttachPicker(draft)
  852. if err != nil {
  853. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  854. return
  855. }
  856. t.editMessageCallbackTgBot(callbackQuery.Message.GetChat().ID, callbackQuery.Message.GetMessageID(), picker)
  857. default:
  858. // An unknown action with arguments is still a tap, and an
  859. // unanswered tap spins until Telegram times it out.
  860. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  861. }
  862. return
  863. } else {
  864. switch callbackQuery.Data {
  865. case "get_inbounds":
  866. inbounds, err := t.getInbounds()
  867. if err != nil {
  868. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  869. return
  870. }
  871. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.allClients"))
  872. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds)
  873. case "admin_client_sub_links":
  874. inbounds, err := t.getInboundsFor("get_clients_for_sub")
  875. if err != nil {
  876. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  877. return
  878. }
  879. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds)
  880. case "admin_client_individual_links":
  881. inbounds, err := t.getInboundsFor("get_clients_for_individual")
  882. if err != nil {
  883. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  884. return
  885. }
  886. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds)
  887. case "admin_client_qr_links":
  888. inbounds, err := t.getInboundsFor("get_clients_for_qr")
  889. if err != nil {
  890. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  891. return
  892. }
  893. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds)
  894. }
  895. }
  896. }
  897. if !isAdmin {
  898. // encodeQuery hashes any payload past 64 chars, so a long email's button
  899. // must be decoded before the gate can see which client it names.
  900. if decoded, err := t.decodeQuery(callbackQuery.Data); err == nil {
  901. callbackQuery.Data = decoded
  902. }
  903. if !isClientSelfCallback(callbackQuery.Data) {
  904. return
  905. }
  906. }
  907. switch callbackQuery.Data {
  908. case "get_usage":
  909. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.serverUsage"))
  910. t.getServerUsage(chatId)
  911. case "usage_refresh":
  912. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  913. t.getServerUsage(chatId, callbackQuery.Message.GetMessageID())
  914. case "inbounds":
  915. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getInbounds"))
  916. t.SendMsgToTgbot(chatId, t.getInboundUsages())
  917. case "deplete_soon":
  918. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.depleteSoon"))
  919. t.getExhausted(chatId)
  920. case "get_backup":
  921. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.dbBackup"))
  922. t.sendBackup(chatId)
  923. case "get_banlogs":
  924. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getBanLogs"))
  925. t.sendBanLogs(chatId, true)
  926. case "client_traffic":
  927. tgUserID := callbackQuery.From.ID
  928. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.clientUsage"))
  929. t.getClientUsage(chatId, tgUserID)
  930. case "client_commands":
  931. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
  932. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpClientCommands"))
  933. case "client_sub_links":
  934. // show user's own clients to choose one for sub links
  935. tgUserID := callbackQuery.From.ID
  936. traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
  937. if err != nil {
  938. // fallback to message
  939. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  940. return
  941. }
  942. if len(traffics) == 0 {
  943. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10)))
  944. return
  945. }
  946. var buttons []telego.InlineKeyboardButton
  947. for _, tr := range traffics {
  948. buttons = append(buttons, tu.InlineKeyboardButton(tr.Email).WithCallbackData(t.encodeQuery("client_sub_links "+tr.Email)))
  949. }
  950. cols := 1
  951. if len(buttons) >= 6 {
  952. cols = 2
  953. }
  954. keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
  955. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), keyboard)
  956. case "client_individual_links":
  957. // show user's clients to choose for individual links
  958. tgUserID := callbackQuery.From.ID
  959. traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
  960. if err != nil {
  961. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
  962. return
  963. }
  964. if len(traffics) == 0 {
  965. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10)))
  966. return
  967. }
  968. var buttons2 []telego.InlineKeyboardButton
  969. for _, tr := range traffics {
  970. buttons2 = append(buttons2, tu.InlineKeyboardButton(tr.Email).WithCallbackData(t.encodeQuery("client_individual_links "+tr.Email)))
  971. }
  972. cols2 := 1
  973. if len(buttons2) >= 6 {
  974. cols2 = 2
  975. }
  976. keyboard2 := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols2, buttons2...))
  977. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), keyboard2)
  978. case "client_qr_links":
  979. // show user's clients to choose for QR codes
  980. tgUserID := callbackQuery.From.ID
  981. traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
  982. if err != nil {
  983. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOccurred")+"\r\n"+err.Error())
  984. return
  985. }
  986. if len(traffics) == 0 {
  987. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10)))
  988. return
  989. }
  990. var buttons3 []telego.InlineKeyboardButton
  991. for _, tr := range traffics {
  992. buttons3 = append(buttons3, tu.InlineKeyboardButton(tr.Email).WithCallbackData(t.encodeQuery("client_qr_links "+tr.Email)))
  993. }
  994. cols3 := 1
  995. if len(buttons3) >= 6 {
  996. cols3 = 2
  997. }
  998. keyboard3 := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols3, buttons3...))
  999. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), keyboard3)
  1000. case "onlines":
  1001. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.onlines"))
  1002. t.onlineClients(chatId)
  1003. case "onlines_refresh":
  1004. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
  1005. t.onlineClients(chatId, callbackQuery.Message.GetMessageID())
  1006. case "commands":
  1007. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
  1008. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
  1009. case "add_client":
  1010. draft.email = t.randomLowerAndNum(8)
  1011. draft.limitIP = 0
  1012. draft.totalGB = 0
  1013. draft.expiryTime = 0
  1014. draft.enable = true
  1015. draft.tgID = ""
  1016. draft.subID = t.randomLowerAndNum(16)
  1017. draft.comment = ""
  1018. draft.reset = 0
  1019. inbounds, err := t.getInboundsAddClient()
  1020. if err != nil {
  1021. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  1022. return
  1023. }
  1024. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.addClient"))
  1025. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds)
  1026. case "add_client_ch_default_email":
  1027. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1028. userStateMgr.set(actor, "awaiting_email")
  1029. cancel_btn_markup := tu.InlineKeyboard(
  1030. tu.InlineKeyboardRow(
  1031. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
  1032. ),
  1033. )
  1034. prompt_message := t.I18nBot("tgbot.messages.email_prompt", "ClientEmail=="+html.EscapeString(draft.email))
  1035. t.SendMsgToTgbot(chatId, prompt_message, cancel_btn_markup)
  1036. case "add_client_ch_default_comment":
  1037. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1038. userStateMgr.set(actor, "awaiting_comment")
  1039. cancel_btn_markup := tu.InlineKeyboard(
  1040. tu.InlineKeyboardRow(
  1041. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
  1042. ),
  1043. )
  1044. prompt_message := t.I18nBot("tgbot.messages.comment_prompt", "ClientComment=="+html.EscapeString(draft.comment))
  1045. t.SendMsgToTgbot(chatId, prompt_message, cancel_btn_markup)
  1046. case "add_client_ch_default_tg_id":
  1047. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1048. userStateMgr.set(actor, "awaiting_tg_id")
  1049. cancel_btn_markup := tu.InlineKeyboard(
  1050. tu.InlineKeyboardRow(
  1051. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"),
  1052. ),
  1053. )
  1054. current := draft.tgID
  1055. if current == "" {
  1056. current = "—"
  1057. }
  1058. t.SendMsgToTgbot(chatId, fmt.Sprintf("Send the Telegram user id (numeric) to attach to this client, or send <code>-</code> / <code>none</code> to clear.\nCurrent: <code>%s</code>", html.EscapeString(current)), cancel_btn_markup)
  1059. case "add_client_ch_default_traffic":
  1060. inlineKeyboard := tu.InlineKeyboard(
  1061. tu.InlineKeyboardRow(
  1062. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_traffic_exp")),
  1063. ),
  1064. tu.InlineKeyboardRow(
  1065. tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 0")),
  1066. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("add_client_limit_traffic_in 0")),
  1067. ),
  1068. tu.InlineKeyboardRow(
  1069. tu.InlineKeyboardButton("1 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 1")),
  1070. tu.InlineKeyboardButton("5 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 5")),
  1071. tu.InlineKeyboardButton("10 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 10")),
  1072. ),
  1073. tu.InlineKeyboardRow(
  1074. tu.InlineKeyboardButton("20 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 20")),
  1075. tu.InlineKeyboardButton("30 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 30")),
  1076. tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 40")),
  1077. ),
  1078. tu.InlineKeyboardRow(
  1079. tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 50")),
  1080. tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 60")),
  1081. tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 80")),
  1082. ),
  1083. tu.InlineKeyboardRow(
  1084. tu.InlineKeyboardButton("100 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 100")),
  1085. tu.InlineKeyboardButton("150 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 150")),
  1086. tu.InlineKeyboardButton("200 GB").WithCallbackData(t.encodeQuery("add_client_limit_traffic_c 200")),
  1087. ),
  1088. )
  1089. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  1090. case "add_client_ch_default_exp":
  1091. inlineKeyboard := tu.InlineKeyboard(
  1092. tu.InlineKeyboardRow(
  1093. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_traffic_exp")),
  1094. ),
  1095. tu.InlineKeyboardRow(
  1096. tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 0")),
  1097. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("add_client_reset_exp_in 0")),
  1098. ),
  1099. // No "Add" verb: these replace the term the draft carries, unlike the
  1100. // renewal keyboard, whose reset_exp_c handler really does add to it.
  1101. tu.InlineKeyboardRow(
  1102. tu.InlineKeyboardButton("7 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 7")),
  1103. tu.InlineKeyboardButton("10 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 10")),
  1104. ),
  1105. tu.InlineKeyboardRow(
  1106. tu.InlineKeyboardButton("14 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 14")),
  1107. tu.InlineKeyboardButton("20 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 20")),
  1108. ),
  1109. tu.InlineKeyboardRow(
  1110. tu.InlineKeyboardButton("1 "+t.I18nBot("tgbot.month")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 30")),
  1111. tu.InlineKeyboardButton("3 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 90")),
  1112. ),
  1113. tu.InlineKeyboardRow(
  1114. tu.InlineKeyboardButton("6 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 180")),
  1115. tu.InlineKeyboardButton("12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("add_client_reset_exp_c 365")),
  1116. ),
  1117. )
  1118. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  1119. case "add_client_ch_default_ip_limit":
  1120. inlineKeyboard := tu.InlineKeyboard(
  1121. tu.InlineKeyboardRow(
  1122. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_ip_limit")),
  1123. ),
  1124. tu.InlineKeyboardRow(
  1125. tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("add_client_ip_limit_c 0")),
  1126. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("add_client_ip_limit_in 0")),
  1127. ),
  1128. tu.InlineKeyboardRow(
  1129. tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 1")),
  1130. tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 2")),
  1131. ),
  1132. tu.InlineKeyboardRow(
  1133. tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 3")),
  1134. tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 4")),
  1135. ),
  1136. tu.InlineKeyboardRow(
  1137. tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 5")),
  1138. tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 6")),
  1139. tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 7")),
  1140. ),
  1141. tu.InlineKeyboardRow(
  1142. tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 8")),
  1143. tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 9")),
  1144. tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 10")),
  1145. ),
  1146. )
  1147. t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
  1148. case "add_client_default_info":
  1149. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1150. t.SendMsgToTgbotDeleteAfter(chatId, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
  1151. userStateMgr.clear(actor)
  1152. t.addClient(chatId, draft, t.BuildClientDraftMessage(draft))
  1153. case "add_client_cancel":
  1154. userStateMgr.clear(actor)
  1155. addClientDrafts.reset(actor)
  1156. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1157. t.SendMsgToTgbotDeleteAfter(chatId, t.I18nBot("tgbot.messages.cancel"), 3, tu.ReplyKeyboardRemove())
  1158. case "add_client_default_traffic_exp":
  1159. messageId := callbackQuery.Message.GetMessageID()
  1160. message_text := t.BuildClientDraftMessage(draft)
  1161. t.addClient(chatId, draft, message_text, messageId)
  1162. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+draft.email))
  1163. case "add_client_default_ip_limit":
  1164. messageId := callbackQuery.Message.GetMessageID()
  1165. message_text := t.BuildClientDraftMessage(draft)
  1166. t.addClient(chatId, draft, message_text, messageId)
  1167. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+draft.email))
  1168. case "add_client_attach_more":
  1169. picker, err := t.getInboundsAttachPicker(draft)
  1170. if err != nil {
  1171. t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
  1172. return
  1173. }
  1174. t.SendMsgToTgbot(chatId, "Pick inbound(s) to attach:", picker)
  1175. case "add_client_attach_done":
  1176. if draft.receiverInboundID == 0 && len(draft.receiverInboundIDs) > 0 {
  1177. draft.receiverInboundID = draft.receiverInboundIDs[0]
  1178. }
  1179. if draft.receiverInboundID == 0 {
  1180. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getInboundsFailed"))
  1181. return
  1182. }
  1183. message_text := t.BuildClientDraftMessage(draft)
  1184. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1185. t.addClient(chatId, draft, message_text)
  1186. case "add_client_submit_disable":
  1187. draft.enable = false
  1188. _, err := t.SubmitAddClient(draft)
  1189. if err != nil {
  1190. errorMessage := fmt.Sprintf("%v", err)
  1191. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.error_add_client", "error=="+errorMessage), tu.ReplyKeyboardRemove())
  1192. } else {
  1193. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1194. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.successfulOperation"), tu.ReplyKeyboardRemove())
  1195. t.sendClientIndividualLinks(chatId, draft.email)
  1196. t.sendClientQRLinks(chatId, draft.email)
  1197. addClientDrafts.reset(actor)
  1198. }
  1199. case "add_client_submit_enable":
  1200. draft.enable = true
  1201. _, err := t.SubmitAddClient(draft)
  1202. if err != nil {
  1203. errorMessage := fmt.Sprintf("%v", err)
  1204. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.error_add_client", "error=="+errorMessage), tu.ReplyKeyboardRemove())
  1205. } else {
  1206. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1207. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.successfulOperation"), tu.ReplyKeyboardRemove())
  1208. t.sendClientIndividualLinks(chatId, draft.email)
  1209. t.sendClientQRLinks(chatId, draft.email)
  1210. addClientDrafts.reset(actor)
  1211. }
  1212. case "reset_all_traffics_cancel":
  1213. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1214. t.SendMsgToTgbotDeleteAfter(chatId, t.I18nBot("tgbot.messages.cancel"), 1, tu.ReplyKeyboardRemove())
  1215. case "reset_all_traffics":
  1216. inlineKeyboard := tu.InlineKeyboard(
  1217. tu.InlineKeyboardRow(
  1218. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("reset_all_traffics_cancel")),
  1219. ),
  1220. tu.InlineKeyboardRow(
  1221. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_all_traffics_c")),
  1222. ),
  1223. )
  1224. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.AreYouSure"), inlineKeyboard)
  1225. case "reset_all_traffics_c":
  1226. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1227. emails, err := t.inboundService.GetAllEmails()
  1228. if err != nil {
  1229. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
  1230. return
  1231. }
  1232. // One report per tap, not one message per client: a large panel would
  1233. // otherwise burst past Telegram's rate limit. SendMsgToTgbot pages it.
  1234. var report strings.Builder
  1235. for _, email := range emails {
  1236. if err := t.inboundService.ResetClientTrafficByEmail(email); err == nil {
  1237. report.WriteString(t.I18nBot("tgbot.messages.SuccessResetTraffic", "ClientEmail=="+email))
  1238. } else {
  1239. report.WriteString(t.I18nBot("tgbot.messages.FailedResetTraffic", "ClientEmail=="+email, "ErrorMessage=="+err.Error()))
  1240. }
  1241. report.WriteString("\r\n\r\n")
  1242. }
  1243. report.WriteString(t.I18nBot("tgbot.messages.FinishProcess"))
  1244. // Escaped whole: one stray "<" in a remark or email otherwise makes
  1245. // Telegram reject the page it landed on, losing ~15 clients at once.
  1246. t.SendMsgToTgbot(chatId, html.EscapeString(report.String()), tu.ReplyKeyboardRemove())
  1247. case "get_sorted_traffic_usage_report":
  1248. t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
  1249. emails, err := t.inboundService.GetAllEmails()
  1250. if err != nil {
  1251. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
  1252. return
  1253. }
  1254. validEmails, missingEmails, err := t.inboundService.FilterAndSortClientEmails(emails)
  1255. if err != nil {
  1256. t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
  1257. return
  1258. }
  1259. // Batched for the same reason as the reset report above: one message
  1260. // per client hits Telegram's rate limit on a large panel.
  1261. var report strings.Builder
  1262. for _, email := range validEmails {
  1263. traffic, err := t.inboundService.GetClientTrafficByEmail(email)
  1264. if err != nil {
  1265. logger.Warning(err)
  1266. report.WriteString(t.I18nBot("tgbot.wentWrong"))
  1267. report.WriteString("\r\n\r\n")
  1268. continue
  1269. }
  1270. if traffic == nil {
  1271. report.WriteString(t.I18nBot("tgbot.noResult"))
  1272. report.WriteString("\r\n\r\n")
  1273. continue
  1274. }
  1275. report.WriteString(t.clientInfoMsg(traffic, false, false, false, false, true, false))
  1276. report.WriteString("\r\n\r\n")
  1277. }
  1278. for _, email := range missingEmails {
  1279. fmt.Fprintf(&report, "📧 %s\r\n%s\r\n\r\n", email, t.I18nBot("tgbot.noResult"))
  1280. }
  1281. if report.Len() > 0 {
  1282. t.SendMsgToTgbot(chatId, html.EscapeString(report.String()), tu.ReplyKeyboardRemove())
  1283. }
  1284. default:
  1285. action, email, ok := splitClientLinkCallback(callbackQuery.Data)
  1286. if !ok {
  1287. // Nothing matched: an unknown button still has to be answered, or it
  1288. // keeps spinning until Telegram times the callback out.
  1289. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  1290. return
  1291. }
  1292. // The keyboard outlives the chat it was sent to, so the email in it
  1293. // cannot authorise itself: a non-admin only reaches their own clients.
  1294. if !isAdmin && !t.clientOwnedByTgUser(callbackQuery.From.ID, email) {
  1295. t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
  1296. return
  1297. }
  1298. switch action {
  1299. case "client_sub_links":
  1300. t.sendClientSubLinks(chatId, email)
  1301. case "client_individual_links":
  1302. t.sendClientIndividualLinks(chatId, email)
  1303. case "client_qr_links":
  1304. t.sendClientQRLinks(chatId, email)
  1305. }
  1306. }
  1307. }
  1308. // checkAdmin checks if the given Telegram ID is an admin.
  1309. func checkAdmin(tgId int64) bool {
  1310. return slices.Contains(adminSnapshot(), tgId)
  1311. }
  1312. // isClientSelfCallback reports whether a callback is per-user rather than
  1313. // admin-only; the caller still has to prove the client is its own.
  1314. func isClientSelfCallback(data string) bool {
  1315. switch data {
  1316. case "client_traffic", "client_commands", "client_sub_links",
  1317. "client_individual_links", "client_qr_links":
  1318. return true
  1319. }
  1320. _, _, ok := splitClientLinkCallback(data)
  1321. return ok
  1322. }
  1323. // splitClientLinkCallback splits "<action> <email>" for the per-client link
  1324. // callbacks; ok is false for every other data.
  1325. func splitClientLinkCallback(data string) (action, email string, ok bool) {
  1326. for _, candidate := range []string{"client_sub_links", "client_individual_links", "client_qr_links"} {
  1327. if rest, found := strings.CutPrefix(data, candidate+" "); found && rest != "" {
  1328. return candidate, rest, true
  1329. }
  1330. }
  1331. return "", "", false
  1332. }