package telegram import ( "fmt" "sync" "time" "github.com/NicoNex/echotron/v3" "github.com/labstack/gommon/log" "github.com/ngoduykhanh/wireguard-ui/store" ) type SendRequestedConfigsToTelegram func(db store.IStore, userid int64) []string type TgBotInitDependencies struct { DB store.IStore SendRequestedConfigsToTelegram SendRequestedConfigsToTelegram } var ( Token string AllowConfRequest bool FloodWait int LogLevel log.Lvl Bot *echotron.API BotMutex sync.RWMutex floodWait = make(map[int64]int64) floodMessageSent = make(map[int64]struct{}) ) func Start(initDeps TgBotInitDependencies) (err error) { ticker := time.NewTicker(time.Minute) defer func() { if err != nil { BotMutex.Lock() Bot = nil BotMutex.Unlock() ticker.Stop() } if r := recover(); r != nil { err = fmt.Errorf("[PANIC] recovered from panic: %v", r) } }() token := Token if token == "" || len(token) < 30 { return } bot := echotron.NewAPI(token) res, err := bot.GetMe() if !res.Ok || err != nil { log.Warnf("[Telegram] Unable to connect to bot.\n%v\n%v", res.Description, err) return } BotMutex.Lock() Bot = &bot BotMutex.Unlock() if LogLevel <= log.INFO { fmt.Printf("[Telegram] Authorized as %s\n", res.Result.Username) } go func() { for range ticker.C { updateFloodWait() } }() if !AllowConfRequest { return } updatesChan := echotron.PollingUpdatesOptions(token, false, echotron.UpdateOptions{AllowedUpdates: []echotron.UpdateType{echotron.MessageUpdate}}) for update := range updatesChan { if update.Message != nil { userid := update.Message.Chat.ID if _, wait := floodWait[userid]; wait { if _, notified := floodMessageSent[userid]; notified { continue } floodMessageSent[userid] = struct{}{} _, err := bot.SendMessage( fmt.Sprintf("You can only request your configs once per %d minutes", FloodWait), userid, &echotron.MessageOptions{ ReplyToMessageID: update.Message.ID, }) if err != nil { log.Errorf("Failed to send telegram message. Error %v", err) } continue } floodWait[userid] = time.Now().Unix() failed := initDeps.SendRequestedConfigsToTelegram(initDeps.DB, userid) if len(failed) > 0 { messageText := "Failed to send configs:\n" for _, f := range failed { messageText += f + "\n" } _, err := bot.SendMessage( messageText, userid, &echotron.MessageOptions{ ReplyToMessageID: update.Message.ID, }) if err != nil { log.Errorf("Failed to send telegram message. Error %v", err) } } } } return err } func SendConfig(userid int64, clientName string, confData, qrData []byte, ignoreFloodWait bool) error { BotMutex.RLock() defer BotMutex.RUnlock() if Bot == nil { return fmt.Errorf("telegram bot is not configured or not available") } if _, wait := floodWait[userid]; wait && !ignoreFloodWait { return fmt.Errorf("this client already got their config less than %d minutes ago", FloodWait) } if !ignoreFloodWait { floodWait[userid] = time.Now().Unix() } qrAttachment := echotron.NewInputFileBytes("qr.png", qrData) _, err := Bot.SendPhoto(qrAttachment, userid, &echotron.PhotoOptions{Caption: clientName}) if err != nil { log.Error(err) return fmt.Errorf("unable to send qr picture") } confAttachment := echotron.NewInputFileBytes(clientName+".conf", confData) _, err = Bot.SendDocument(confAttachment, userid, nil) if err != nil { log.Error(err) return fmt.Errorf("unable to send conf file") } return nil } func updateFloodWait() { thresholdTS := time.Now().Unix() - 60*int64(FloodWait) for userid, ts := range floodWait { if ts < thresholdTS { delete(floodWait, userid) delete(floodMessageSent, userid) } } }