add rate limiter to telegram
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-06-24 12:40:42 +03:00
parent afacaf4f14
commit 1852c5025e
3 changed files with 57 additions and 17 deletions

View File

@@ -6,24 +6,35 @@ import (
"log/slog"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"golang.org/x/time/rate"
)
// TelegramClientAdapter адаптирует библиотеку tgbotapi под наш интерфейс interfaces.TelegramClient.
type TelegramClientAdapter struct {
api *tgbotapi.BotAPI
cacheChatID int64
api *tgbotapi.BotAPI
cacheChatID int64
fastLimiter *rate.Limiter // Для общих быстрых запросов
cacheLimiter *rate.Limiter // Для медленных запросов в кэш-канал
}
// NewTelegramClientAdapter создает новый адаптер.
func NewTelegramClientAdapter(api *tgbotapi.BotAPI, cacheChatID int64) *TelegramClientAdapter {
func NewTelegramClientAdapter(api *tgbotapi.BotAPI, cacheChatID int64, fastLimiter, cacheLimiter *rate.Limiter) *TelegramClientAdapter {
return &TelegramClientAdapter{
api: api,
cacheChatID: cacheChatID,
api: api,
cacheChatID: cacheChatID,
fastLimiter: fastLimiter,
cacheLimiter: cacheLimiter,
}
}
// SendAudioToCacheChannel загружает аудиофайл в кэш-канал и возвращает его FileID.
// ИСПОЛЬЗУЕТ МЕДЛЕННЫЙ ЛИМИТЕР.
func (t *TelegramClientAdapter) SendAudioToCacheChannel(ctx context.Context, audioPath, title, performer string) (string, error) {
// Ждем, пока МЕДЛЕННЫЙ лимитер разрешит выполнить запрос
if err := t.cacheLimiter.Wait(ctx); err != nil {
return "", err
}
audio := tgbotapi.NewAudio(t.cacheChatID, tgbotapi.FilePath(audioPath))
audio.Title = title
audio.Performer = performer
@@ -41,11 +52,17 @@ func (t *TelegramClientAdapter) SendAudioToCacheChannel(ctx context.Context, aud
}
// AnswerInlineQuery отвечает на inline-запрос.
// ИСПОЛЬЗУЕТ БЫСТРЫЙ ЛИМИТЕР.
func (t *TelegramClientAdapter) AnswerInlineQuery(ctx context.Context, queryID string, results []interface{}) error {
// Ждем, пока БЫСТРЫЙ лимитер разрешит выполнить запрос
if err := t.fastLimiter.Wait(ctx); err != nil {
return err
}
inlineConfig := tgbotapi.InlineConfig{
InlineQueryID: queryID,
Results: results,
CacheTime: 1, // Кэшируем результат на стороне Telegram на 1 секунду
CacheTime: 1,
}
if _, err := t.api.Request(inlineConfig); err != nil {
@@ -55,7 +72,13 @@ func (t *TelegramClientAdapter) AnswerInlineQuery(ctx context.Context, queryID s
}
// SendMessage отправляет текстовое сообщение.
// ИСПОЛЬЗУЕТ БЫСТРЫЙ ЛИМИТЕР.
func (t *TelegramClientAdapter) SendMessage(ctx context.Context, chatID int64, text string) error {
// Ждем, пока БЫСТРЫЙ лимитер разрешит выполнить запрос
if err := t.fastLimiter.Wait(ctx); err != nil {
return err
}
msg := tgbotapi.NewMessage(chatID, text)
if _, err := t.api.Send(msg); err != nil {
return fmt.Errorf("failed to send message: %w", err)

View File

@@ -10,15 +10,17 @@ import (
// Config содержит всю конфигурацию приложения, получаемую из переменных окружения.
type Config struct {
TelegramBotToken string `env:"TELEGRAM_BOT_TOKEN,required"`
TelegramAdminIDsRaw string `env:"TELEGRAM_ADMIN_IDS,required"`
TelegramCacheChatID int64 `env:"TELEGRAM_CACHE_CHAT_ID,required"`
YandexMusicToken string `env:"YANDEX_MUSIC_TOKEN"`
DatabasePath string `env:"DATABASE_PATH" envDefault:"/data/bot.db"`
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
ProcessorWorkers int `env:"PROCESSOR_WORKERS" envDefault:"4"`
YandexAPIRateLimit int `env:"YANDEX_API_RATE_LIMIT" envDefault:"5"`
TelegramAdminIDs []int64 `env:"-"` // Это поле будет заполнено после парсинга
TelegramBotToken string `env:"TELEGRAM_BOT_TOKEN,required"`
TelegramAdminIDsRaw string `env:"TELEGRAM_ADMIN_IDS,required"`
TelegramCacheChatID int64 `env:"TELEGRAM_CACHE_CHAT_ID,required"`
YandexMusicToken string `env:"YANDEX_MUSIC_TOKEN"`
DatabasePath string `env:"DATABASE_PATH" envDefault:"/data/bot.db"`
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
ProcessorWorkers int `env:"PROCESSOR_WORKERS" envDefault:"4"`
YandexAPIRateLimit int `env:"YANDEX_API_RATE_LIMIT" envDefault:"5"`
TelegramAPIRateLimit int `env:"TELEGRAM_API_RATE_LIMIT" envDefault:"25"` // Общий лимит в секунду
TelegramCacheRateLimitPerMinute int `env:"TELEGRAM_CACHE_RATE_LIMIT_PER_MINUTE" envDefault:"20"` // Лимит для кэш-канала в минуту
TelegramAdminIDs []int64 `env:"-"`
}
// New загружает конфигурацию из переменных окружения и парсит необходимые поля.