diff --git a/web/controller/index.go b/web/controller/index.go index 71a1a34a..b4f981e8 100644 --- a/web/controller/index.go +++ b/web/controller/index.go @@ -4,7 +4,6 @@ import ( "net/http" "time" "x-ui/logger" - "x-ui/web/job" "x-ui/web/service" "x-ui/web/session" @@ -20,6 +19,7 @@ type IndexController struct { BaseController userService service.UserService + tgbot service.Tgbot } func NewIndexController(g *gin.RouterGroup) *IndexController { @@ -60,13 +60,13 @@ func (a *IndexController) login(c *gin.Context) { user := a.userService.CheckUser(form.Username, form.Password) timeStr := time.Now().Format("2006-01-02 15:04:05") if user == nil { - job.NewStatsNotifyJob().UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0) + a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0) logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password) pureJsonMsg(c, false, I18n(c, "pages.login.toasts.wrongUsernameOrPassword")) return } else { logger.Infof("%s login success,Ip Address:%s\n", form.Username, getRemoteIp(c)) - job.NewStatsNotifyJob().UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1) + a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1) } err = session.SetLoginUser(c, user) diff --git a/web/job/stats_notify_job.go b/web/job/stats_notify_job.go index 92963127..689ba200 100644 --- a/web/job/stats_notify_job.go +++ b/web/job/stats_notify_job.go @@ -8,8 +8,6 @@ import ( "x-ui/logger" "x-ui/util/common" "x-ui/web/service" - - tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) type LoginStatus byte @@ -24,37 +22,13 @@ type StatsNotifyJob struct { xrayService service.XrayService inboundService service.InboundService settingService service.SettingService + tgbotService service.Tgbot } func NewStatsNotifyJob() *StatsNotifyJob { return new(StatsNotifyJob) } -func (j *StatsNotifyJob) SendMsgToTgbot(msg string) { - //Telegram bot basic info - tgBottoken, err := j.settingService.GetTgBotToken() - if err != nil || tgBottoken == "" { - logger.Warning("sendMsgToTgbot failed,GetTgBotToken fail:", err) - return - } - tgBotid, err := j.settingService.GetTgBotChatId() - if err != nil { - logger.Warning("sendMsgToTgbot failed,GetTgBotChatId fail:", err) - return - } - - bot, err := tgbotapi.NewBotAPI(tgBottoken) - if err != nil { - fmt.Println("get tgbot error:", err) - return - } - bot.Debug = true - fmt.Printf("Authorized on account %s", bot.Self.UserName) - info := tgbotapi.NewMessage(int64(tgBotid), msg) - //msg.ReplyToMessageID = int(tgBotid) - bot.Send(info) -} - // Here run is a interface method of Job interface func (j *StatsNotifyJob) Run() { if !j.xrayService.IsXrayRunning() { @@ -111,138 +85,5 @@ func (j *StatsNotifyJob) Run() { info += fmt.Sprintf("Expire date:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) } } - j.SendMsgToTgbot(info) -} - -func (j *StatsNotifyJob) UserLoginNotify(username string, ip string, time string, status LoginStatus) { - if username == "" || ip == "" || time == "" { - logger.Warning("UserLoginNotify failed,invalid info") - return - } - var msg string - // Get hostname - name, err := os.Hostname() - if err != nil { - fmt.Println("get hostname error:", err) - return - } - if status == LoginSuccess { - msg = fmt.Sprintf("Successfully logged-in to the panel\r\nHostname:%s\r\n", name) - } else if status == LoginFail { - msg = fmt.Sprintf("Login to the panel was unsuccessful\r\nHostname:%s\r\n", name) - } - msg += fmt.Sprintf("Time:%s\r\n", time) - msg += fmt.Sprintf("Username:%s\r\n", username) - msg += fmt.Sprintf("IP:%s\r\n", ip) - j.SendMsgToTgbot(msg) -} - -var numericKeyboard = tgbotapi.NewInlineKeyboardMarkup( - tgbotapi.NewInlineKeyboardRow( - tgbotapi.NewInlineKeyboardButtonData("Get Usage", "get_usage"), - ), -) - -func (j *StatsNotifyJob) OnReceive() *StatsNotifyJob { - tgBottoken, err := j.settingService.GetTgBotToken() - if err != nil || tgBottoken == "" { - logger.Warning("sendMsgToTgbot failed,GetTgBotToken fail:", err) - return j - } - bot, err := tgbotapi.NewBotAPI(tgBottoken) - if err != nil { - fmt.Println("get tgbot error:", err) - return j - } - bot.Debug = false - u := tgbotapi.NewUpdate(0) - u.Timeout = 10 - - updates := bot.GetUpdatesChan(u) - - for update := range updates { - if update.Message == nil { - - if update.CallbackQuery != nil { - // Respond to the callback query, telling Telegram to show the user - // a message with the data received. - callback := tgbotapi.NewCallback(update.CallbackQuery.ID, update.CallbackQuery.Data) - if _, err := bot.Request(callback); err != nil { - logger.Warning(err) - } - - // And finally, send a message containing the data received. - msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, "") - - switch update.CallbackQuery.Data { - case "get_usage": - msg.Text = "for get your usage send command like this : \n /usage uuid | id \n example : /usage fc3239ed-8f3b-4151-ff51-b183d5182142" - msg.ParseMode = "HTML" - } - if _, err := bot.Send(msg); err != nil { - logger.Warning(err) - } - } - - continue - } - - if !update.Message.IsCommand() { // ignore any non-command Messages - continue - } - - // Create a new MessageConfig. We don't have text yet, - // so we leave it empty. - msg := tgbotapi.NewMessage(update.Message.Chat.ID, "") - - // Extract the command from the Message. - switch update.Message.Command() { - case "help": - msg.Text = "What you need?" - msg.ReplyMarkup = numericKeyboard - case "start": - msg.Text = "Hi :) \n What you need?" - msg.ReplyMarkup = numericKeyboard - - case "status": - msg.Text = "bot is ok." - - case "usage": - msg.Text = j.getClientUsage(update.Message.CommandArguments()) - default: - msg.Text = "I don't know that command, /help" - msg.ReplyMarkup = numericKeyboard - - } - - if _, err := bot.Send(msg); err != nil { - logger.Warning(err) - } - } - return j - -} -func (j *StatsNotifyJob) getClientUsage(id string) string { - traffic, err := j.inboundService.GetClientTrafficById(id) - if err != nil { - logger.Warning(err) - return "something wrong!" - } - expiryTime := "" - if traffic.ExpiryTime == 0 { - expiryTime = fmt.Sprintf("unlimited") - } else { - expiryTime = fmt.Sprintf("%s", time.Unix((traffic.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) - } - total := "" - if traffic.Total == 0 { - total = fmt.Sprintf("unlimited") - } else { - total = fmt.Sprintf("%s", common.FormatTraffic((traffic.Total))) - } - output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n", - traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)), - total, expiryTime) - - return output + j.tgbotService.SendMsgToTgbot(info) } diff --git a/web/service/tgbot.go b/web/service/tgbot.go new file mode 100644 index 00000000..b7fd288b --- /dev/null +++ b/web/service/tgbot.go @@ -0,0 +1,197 @@ +package service + +import ( + "fmt" + "os" + "time" + "x-ui/logger" + "x-ui/util/common" + + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +var bot *tgbotapi.BotAPI +var tgBotid int +var isRunning bool + +var numericKeyboard = tgbotapi.NewInlineKeyboardMarkup( + tgbotapi.NewInlineKeyboardRow( + tgbotapi.NewInlineKeyboardButtonData("Get Usage", "get_usage"), + ), +) + +type LoginStatus byte + +const ( + LoginSuccess LoginStatus = 1 + LoginFail LoginStatus = 0 +) + +type Tgbot struct { + inboundService InboundService + settingService SettingService +} + +func (t *Tgbot) NewTgbot() *Tgbot { + return new(Tgbot) +} + +func (t *Tgbot) Start() error { + tgBottoken, err := t.settingService.GetTgBotToken() + if err != nil || tgBottoken == "" { + logger.Warning("Get TgBotToken failed:", err) + return err + } + + tgBotid, err = t.settingService.GetTgBotChatId() + if err != nil { + logger.Warning("Get GetTgBotChatId failed:", err) + return err + } + + bot, err = tgbotapi.NewBotAPI(tgBottoken) + if err != nil { + fmt.Println("Get tgbot's api error:", err) + return err + } + bot.Debug = false + + // listen for TG bot income messages + if !isRunning { + logger.Info("Telegram receiver starting") + go t.OnReceive() + isRunning = true + } + + return nil +} + +func (t *Tgbot) IsRunnging() bool { + return isRunning +} + +func (t *Tgbot) Stop() { + bot.StopReceivingUpdates() + logger.Info("Send Kill to Telegram listener ...") + isRunning = false +} + +func (t *Tgbot) OnReceive() { + u := tgbotapi.NewUpdate(0) + u.Timeout = 10 + + updates := bot.GetUpdatesChan(u) + + for update := range updates { + if update.Message == nil { + + if update.CallbackQuery != nil { + // Respond to the callback query, telling Telegram to show the user + // a message with the data received. + callback := tgbotapi.NewCallback(update.CallbackQuery.ID, update.CallbackQuery.Data) + if _, err := bot.Request(callback); err != nil { + logger.Warning(err) + } + + // And finally, send a message containing the data received. + msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, "") + + switch update.CallbackQuery.Data { + case "get_usage": + msg.Text = "for get your usage send command like this : \n /usage uuid | id \n example : /usage fc3239ed-8f3b-4151-ff51-b183d5182142" + msg.ParseMode = "HTML" + } + if _, err := bot.Send(msg); err != nil { + logger.Warning(err) + } + } + + continue + } + + if !update.Message.IsCommand() { // ignore any non-command Messages + continue + } + + // Create a new MessageConfig. We don't have text yet, + // so we leave it empty. + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "") + + // Extract the command from the Message. + switch update.Message.Command() { + case "help": + msg.Text = "What you need?" + msg.ReplyMarkup = numericKeyboard + case "start": + msg.Text = "Hi :) \n What you need?" + msg.ReplyMarkup = numericKeyboard + + case "status": + msg.Text = "bot is ok." + + case "usage": + msg.Text = t.getClientUsage(update.Message.CommandArguments()) + default: + msg.Text = "I don't know that command, /help" + msg.ReplyMarkup = numericKeyboard + } + + if _, err := bot.Send(msg); err != nil { + logger.Warning(err) + } + } +} + +func (t *Tgbot) SendMsgToTgbot(msg string) { + info := tgbotapi.NewMessage(int64(tgBotid), msg) + //msg.ReplyToMessageID = int(tgBotid) + bot.Send(info) +} + +func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status LoginStatus) { + if username == "" || ip == "" || time == "" { + logger.Warning("UserLoginNotify failed,invalid info") + return + } + var msg string + // Get hostname + name, err := os.Hostname() + if err != nil { + fmt.Println("get hostname error:", err) + return + } + if status == LoginSuccess { + msg = fmt.Sprintf("Successfully logged-in to the panel\r\nHostname:%s\r\n", name) + } else if status == LoginFail { + msg = fmt.Sprintf("Login to the panel was unsuccessful\r\nHostname:%s\r\n", name) + } + msg += fmt.Sprintf("Time:%s\r\n", time) + msg += fmt.Sprintf("Username:%s\r\n", username) + msg += fmt.Sprintf("IP:%s\r\n", ip) + t.SendMsgToTgbot(msg) +} + +func (t *Tgbot) getClientUsage(id string) string { + traffic, err := t.inboundService.GetClientTrafficById(id) + if err != nil { + logger.Warning(err) + return "something wrong!" + } + expiryTime := "" + if traffic.ExpiryTime == 0 { + expiryTime = "unlimited" + } else { + expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") + } + total := "" + if traffic.Total == 0 { + total = "unlimited" + } else { + total = common.FormatTraffic((traffic.Total)) + } + output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n", + traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)), + total, expiryTime) + + return output +} diff --git a/web/web.go b/web/web.go index 3f9344d5..7a68d1ee 100644 --- a/web/web.go +++ b/web/web.go @@ -88,7 +88,7 @@ type Server struct { xrayService service.XrayService settingService service.SettingService - inboundService service.InboundService + tgbotService service.Tgbot cron *cron.Cron @@ -325,8 +325,6 @@ func (s *Server) startTask() { logger.Warning("Add NewStatsNotifyJob error", err) return } - // listen for TG bot income messages - go job.NewStatsNotifyJob().OnReceive() } else { s.cron.Remove(entry) } @@ -403,6 +401,12 @@ func (s *Server) Start() (err error) { s.httpServer.Serve(listener) }() + isTgbotenabled, err := s.settingService.GetTgbotenabled() + if (err == nil) && (isTgbotenabled) { + tgBot := s.tgbotService.NewTgbot() + tgBot.Start() + } + return nil } @@ -412,6 +416,9 @@ func (s *Server) Stop() error { if s.cron != nil { s.cron.Stop() } + if s.tgbotService.IsRunnging() { + s.tgbotService.Stop() + } var err1 error var err2 error if s.httpServer != nil {