From 00dab85d2f40b2c6668df7db76955a877f91d441 Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Fri, 15 Aug 2025 18:04:18 +0300 Subject: [PATCH] feat: add rate limiting functionality to bot; update localization files for limit exceeded messages in English and Russian --- apps/bot/locales/en.ftl | 3 ++- apps/bot/locales/ru.ftl | 3 ++- apps/bot/package.json | 1 + apps/bot/src/bot/index.ts | 22 +++++++++++++++++++++- apps/bot/src/config/env.ts | 4 ++++ pnpm-lock.yaml | 8 ++++++++ 6 files changed, 38 insertions(+), 3 deletions(-) diff --git a/apps/bot/locales/en.ftl b/apps/bot/locales/en.ftl index b862980..fc38c45 100644 --- a/apps/bot/locales/en.ftl +++ b/apps/bot/locales/en.ftl @@ -1,3 +1,4 @@ invalid_url = Invalid url invalid_download_urls = download urls not found -generic = Something went wrong \ No newline at end of file +generic = Something went wrong +limit_exceeded = Please stop spaming! \ No newline at end of file diff --git a/apps/bot/locales/ru.ftl b/apps/bot/locales/ru.ftl index f429f51..ccb9b7e 100644 --- a/apps/bot/locales/ru.ftl +++ b/apps/bot/locales/ru.ftl @@ -1,3 +1,4 @@ invalid_url = Неверная ссылка invalid_download_urls = Не найдены ссылки для скачивания -generic = Что-то пошло не так \ No newline at end of file +generic = Что-то пошло не так +limit_exceeded = Пожалуйста, прекратите спамить! \ No newline at end of file diff --git a/apps/bot/package.json b/apps/bot/package.json index a5ca0fd..2cece67 100644 --- a/apps/bot/package.json +++ b/apps/bot/package.json @@ -22,6 +22,7 @@ "@grammyjs/hydrate": "^1.4.1", "@grammyjs/i18n": "^1.1.2", "@grammyjs/parse-mode": "^2.2.0", + "@grammyjs/ratelimiter": "^1.2.1", "@grammyjs/types": "^3.21.0", "@repo/typescript-config": "workspace:*", "@tobyg74/tiktok-api-dl": "^1.3.4", diff --git a/apps/bot/src/bot/index.ts b/apps/bot/src/bot/index.ts index f53d6b1..c2e3dcf 100644 --- a/apps/bot/src/bot/index.ts +++ b/apps/bot/src/bot/index.ts @@ -3,9 +3,12 @@ import { type Context } from './context'; import * as features from './features'; import { errorHandler } from './handlers/errors'; import { i18n } from './i18n'; +import { env } from '@/config/env'; import { logger } from '@/utils/logger'; +import { getRedisInstance } from '@/utils/redis'; import { autoChatAction } from '@grammyjs/auto-chat-action'; import { hydrate } from '@grammyjs/hydrate'; +import { limit } from '@grammyjs/ratelimiter'; import { Bot } from 'grammy'; type Parameters_ = { @@ -13,6 +16,8 @@ type Parameters_ = { token: string; }; +const redis = getRedisInstance(); + export function createBot({ apiRoot, token }: Parameters_) { const bot = new Bot(token, { client: { @@ -20,6 +25,22 @@ export function createBot({ apiRoot, token }: Parameters_) { }, }); + bot.use(i18n); + + bot.use( + limit({ + keyGenerator: (ctx) => { + return ctx.from?.id.toString(); + }, + limit: 1, + onLimitExceeded: async (ctx) => { + await ctx.reply(ctx.t('limit_exceeded')); + }, + storageClient: redis, + timeFrame: env.RATE_LIMIT, + }), + ); + bot.use(async (context, next) => { context.logger = logger.child({ update_id: context.update.update_id, @@ -30,7 +51,6 @@ export function createBot({ apiRoot, token }: Parameters_) { const protectedBot = bot.errorBoundary(errorHandler); - protectedBot.use(i18n); protectedBot.use(autoChatAction(bot.api)); protectedBot.use(hydrate()); protectedBot.use(features.download); diff --git a/apps/bot/src/config/env.ts b/apps/bot/src/config/env.ts index 5e6c92a..5a66275 100644 --- a/apps/bot/src/config/env.ts +++ b/apps/bot/src/config/env.ts @@ -3,6 +3,10 @@ import { z } from 'zod'; export const envSchema = z.object({ BOT_TOKEN: z.string(), + RATE_LIMIT: z + .string() + .transform((value) => Number.parseInt(value, 10)) + .default('5000'), REDIS_HOST: z.string(), REDIS_PASSWORD: z.string(), REDIS_PORT: z diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8654c46..d284320 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: '@grammyjs/parse-mode': specifier: ^2.2.0 version: 2.2.0(grammy@1.37.1) + '@grammyjs/ratelimiter': + specifier: ^1.2.1 + version: 1.2.1 '@grammyjs/types': specifier: ^3.21.0 version: 3.21.0 @@ -516,6 +519,9 @@ packages: peerDependencies: grammy: ^1.36.1 + '@grammyjs/ratelimiter@1.2.1': + resolution: {integrity: sha512-4bmVUBCBnIb2epbDiBLCvvnVjaYg7kDCPR1Ptt6gqoxm5vlD8BjainYv+yjF6221hu2KUv8QAckumDI+6xyGsQ==} + '@grammyjs/types@3.21.0': resolution: {integrity: sha512-IMj0EpmglPCICuyfGRx4ENKPSuzS2xMSoPgSPzHC6FtnWKDEmJLBP/GbPv/h3TAeb27txqxm/BUld+gbJk6ccQ==} @@ -4290,6 +4296,8 @@ snapshots: dependencies: grammy: 1.37.1 + '@grammyjs/ratelimiter@1.2.1': {} + '@grammyjs/types@3.21.0': {} '@graphql-eslint/eslint-plugin@4.4.0(@types/node@24.3.0)(eslint@9.33.0(jiti@2.5.1))(graphql@16.11.0)(typescript@5.9.2)':