package telegram import ( "fmt" "sync" "time" "github.com/NicoNex/echotron/v3" "github.com/labstack/gommon/log" "github.com/ngoduykhanh/wireguard-ui/store" "github.com/skip2/go-qrcode" ) 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 MTProxyLink string // ДОБАВИЛИ НАШУ ССЫЛКУ 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("Подождите %d секунд, защита от флуда", 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("Этот клиент будет готов получить конфиг через %d секунд", 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") } if MTProxyLink != "" { // Генерируем QR-код (размер 512x512, средний уровень коррекции ошибок) mtQRData, err := qrcode.Encode(MTProxyLink, qrcode.Medium, 512) if err == nil { // Превращаем байты в картинку для Телеграма mtAttachment := echotron.NewInputFileBytes("mtproxy.png", mtQRData) // Формируем красивое сообщение (можешь поменять текст) caption := "🚀 Твой MTProxy для обхода блокировок: \n\n" + MTProxyLink // Отправляем! _, err = Bot.SendPhoto(mtAttachment, userid, &echotron.PhotoOptions{Caption: caption}) if err != nil { log.Errorf("Failed to send MTProxy to user %d: %v", userid, err) } } else { log.Errorf("Failed to generate MTProxy QR: %v", err) } } // --- КОНЕЦ НАШЕГО БЛОКА --- return nil } func updateFloodWait() { thresholdTS := time.Now().Unix() - int64(FloodWait) for userid, ts := range floodWait { if ts < thresholdTS { delete(floodWait, userid) delete(floodMessageSent, userid) } } }