feat: update bot dependencies to include @grammyjs/runner and @grammyjs/storage-redis; enhance session management and rate limiting configuration
This commit is contained in:
parent
e308ba74ea
commit
e4233c506b
@ -5,7 +5,7 @@ start =
|
|||||||
err-invalid-url = ❌ Invalid URL! Please send a valid TikTok link (e.g., https://vt.tiktok.com/...)
|
err-invalid-url = ❌ Invalid URL! Please send a valid TikTok link (e.g., https://vt.tiktok.com/...)
|
||||||
err-invalid-download-urls = 🔍 Download links not found. The video might be deleted or unavailable
|
err-invalid-download-urls = 🔍 Download links not found. The video might be deleted or unavailable
|
||||||
err-generic = ⚠️ Something went wrong. Please try again in a few seconds
|
err-generic = ⚠️ Something went wrong. Please try again in a few seconds
|
||||||
err-limit-exceeded = 🚫 Too many requests! Please wait before sending the next link
|
err-limit-exceeded = 🚫 Too many requests! Please wait
|
||||||
|
|
||||||
|
|
||||||
msg-welcome = Welcome! I can download TikTok videos and images for you without watermark. Just send me the link (for example: https://vt.tiktok.com/...)
|
msg-welcome = Welcome! I can download TikTok videos and images for you without watermark. Just send me the link (for example: https://vt.tiktok.com/...)
|
||||||
@ -5,7 +5,7 @@ start =
|
|||||||
err-invalid-url = ❌ Неверная ссылка! Отправьте корректную ссылку TikTok (например: https://vt.tiktok.com/...)
|
err-invalid-url = ❌ Неверная ссылка! Отправьте корректную ссылку TikTok (например: https://vt.tiktok.com/...)
|
||||||
err-invalid-download-urls = 🔍 Не удалось найти ссылки для скачивания. Возможно, видео удалено или недоступно
|
err-invalid-download-urls = 🔍 Не удалось найти ссылки для скачивания. Возможно, видео удалено или недоступно
|
||||||
err-generic = ⚠️ Что-то пошло не так. Попробуйте еще раз через несколько секунд
|
err-generic = ⚠️ Что-то пошло не так. Попробуйте еще раз через несколько секунд
|
||||||
err-limit-exceeded = 🚫 Слишком много запросов! Подождите немного перед следующей ссылкой
|
err-limit-exceeded = 🚫 Слишком много запросов! Подождите немного
|
||||||
|
|
||||||
|
|
||||||
msg-welcome = Добро пожаловать! Я могу скачать для вас видео и изображения из TikTok без водяного знака. Для этого просто отправьте мне ссылку (например: https://vt.tiktok.com/...)
|
msg-welcome = Добро пожаловать! Я могу скачать для вас видео и изображения из TikTok без водяного знака. Для этого просто отправьте мне ссылку (например: https://vt.tiktok.com/...)
|
||||||
@ -24,6 +24,8 @@
|
|||||||
"@grammyjs/i18n": "^1.1.2",
|
"@grammyjs/i18n": "^1.1.2",
|
||||||
"@grammyjs/parse-mode": "^2.2.0",
|
"@grammyjs/parse-mode": "^2.2.0",
|
||||||
"@grammyjs/ratelimiter": "^1.2.1",
|
"@grammyjs/ratelimiter": "^1.2.1",
|
||||||
|
"@grammyjs/runner": "^2.0.3",
|
||||||
|
"@grammyjs/storage-redis": "^2.5.1",
|
||||||
"@grammyjs/types": "^3.21.0",
|
"@grammyjs/types": "^3.21.0",
|
||||||
"@repo/typescript-config": "workspace:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
"@tobyg74/tiktok-api-dl": "^1.3.4",
|
"@tobyg74/tiktok-api-dl": "^1.3.4",
|
||||||
|
|||||||
@ -3,13 +3,16 @@ import { type AutoChatActionFlavor } from '@grammyjs/auto-chat-action';
|
|||||||
import { type CommandsFlavor } from '@grammyjs/commands';
|
import { type CommandsFlavor } from '@grammyjs/commands';
|
||||||
import { type HydrateFlavor } from '@grammyjs/hydrate';
|
import { type HydrateFlavor } from '@grammyjs/hydrate';
|
||||||
import { type I18nFlavor } from '@grammyjs/i18n';
|
import { type I18nFlavor } from '@grammyjs/i18n';
|
||||||
import { type Context as DefaultContext } from 'grammy';
|
import { type Context as DefaultContext, type SessionFlavor } from 'grammy';
|
||||||
|
|
||||||
export type Context = HydrateFlavor<
|
export type Context = HydrateFlavor<
|
||||||
AutoChatActionFlavor &
|
AutoChatActionFlavor &
|
||||||
CommandsFlavor &
|
CommandsFlavor &
|
||||||
DefaultContext &
|
DefaultContext &
|
||||||
I18nFlavor & {
|
I18nFlavor &
|
||||||
|
SessionFlavor<SessionData> & {
|
||||||
logger: typeof logger;
|
logger: typeof logger;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type SessionData = {};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable consistent-return */
|
/* eslint-disable consistent-return */
|
||||||
import { type Context } from '../context';
|
import { type Context } from '../context';
|
||||||
import { logHandle } from '../helpers/logging';
|
import { logHandle } from '../helpers/logging';
|
||||||
import { TTL } from '@/config/redis';
|
import { TTL_URLS } from '@/config/redis';
|
||||||
import { getRedisInstance } from '@/utils/redis';
|
import { getRedisInstance } from '@/utils/redis';
|
||||||
import { validateTikTokUrl } from '@/utils/urls';
|
import { validateTikTokUrl } from '@/utils/urls';
|
||||||
import { Downloader } from '@tobyg74/tiktok-api-dl';
|
import { Downloader } from '@tobyg74/tiktok-api-dl';
|
||||||
@ -39,7 +39,7 @@ feature.on('message:text', logHandle('download-message'), async (context) => {
|
|||||||
|
|
||||||
if (result?.type === 'video' && videoUrl) {
|
if (result?.type === 'video' && videoUrl) {
|
||||||
const { video } = await context.replyWithVideo(new InputFile({ url: videoUrl }));
|
const { video } = await context.replyWithVideo(new InputFile({ url: videoUrl }));
|
||||||
await redis.set(url, video.file_id, 'EX', TTL);
|
await redis.set(url, video.file_id, 'EX', TTL_URLS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,12 +5,15 @@ import * as features from './features';
|
|||||||
import { errorHandler } from './handlers/errors';
|
import { errorHandler } from './handlers/errors';
|
||||||
import { i18n } from './i18n';
|
import { i18n } from './i18n';
|
||||||
import * as middlewares from './middlewares';
|
import * as middlewares from './middlewares';
|
||||||
|
import { session } from './middlewares';
|
||||||
import { env } from '@/config/env';
|
import { env } from '@/config/env';
|
||||||
import { logger } from '@/utils/logger';
|
import { logger } from '@/utils/logger';
|
||||||
import { getRedisInstance } from '@/utils/redis';
|
import { getRedisInstance } from '@/utils/redis';
|
||||||
|
import { getSessionKey } from '@/utils/session';
|
||||||
import { autoChatAction } from '@grammyjs/auto-chat-action';
|
import { autoChatAction } from '@grammyjs/auto-chat-action';
|
||||||
import { hydrate } from '@grammyjs/hydrate';
|
import { hydrate } from '@grammyjs/hydrate';
|
||||||
import { limit } from '@grammyjs/ratelimiter';
|
import { limit } from '@grammyjs/ratelimiter';
|
||||||
|
import { sequentialize } from '@grammyjs/runner';
|
||||||
import { Bot } from 'grammy';
|
import { Bot } from 'grammy';
|
||||||
|
|
||||||
type Parameters_ = {
|
type Parameters_ = {
|
||||||
@ -34,12 +37,12 @@ export function createBot({ apiRoot, token }: Parameters_) {
|
|||||||
keyGenerator: (ctx) => {
|
keyGenerator: (ctx) => {
|
||||||
return ctx.from?.id.toString();
|
return ctx.from?.id.toString();
|
||||||
},
|
},
|
||||||
limit: 1,
|
limit: env.RATE_LIMIT,
|
||||||
onLimitExceeded: async (ctx) => {
|
onLimitExceeded: async (ctx) => {
|
||||||
await ctx.reply(ctx.t('err-limit-exceeded'));
|
await ctx.reply(ctx.t('err-limit-exceeded'));
|
||||||
},
|
},
|
||||||
storageClient: redis,
|
storageClient: redis,
|
||||||
timeFrame: env.RATE_LIMIT,
|
timeFrame: env.RATE_LIMIT_TIME,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -53,6 +56,9 @@ export function createBot({ apiRoot, token }: Parameters_) {
|
|||||||
|
|
||||||
const protectedBot = bot.errorBoundary(errorHandler);
|
const protectedBot = bot.errorBoundary(errorHandler);
|
||||||
|
|
||||||
|
protectedBot.use(sequentialize(getSessionKey));
|
||||||
|
protectedBot.use(session());
|
||||||
|
|
||||||
protectedBot.use(middlewares.updateLogger());
|
protectedBot.use(middlewares.updateLogger());
|
||||||
protectedBot.use(setCommands);
|
protectedBot.use(setCommands);
|
||||||
protectedBot.use(autoChatAction(bot.api));
|
protectedBot.use(autoChatAction(bot.api));
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
|
export * from './session';
|
||||||
export * from './update-logger';
|
export * from './update-logger';
|
||||||
|
|||||||
20
apps/bot/src/bot/middlewares/session.ts
Normal file
20
apps/bot/src/bot/middlewares/session.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { type Context } from '@/bot/context';
|
||||||
|
import { TTL_SESSION } from '@/config/redis';
|
||||||
|
import { getRedisInstance } from '@/utils/redis';
|
||||||
|
import { getSessionKey } from '@/utils/session';
|
||||||
|
import { RedisAdapter } from '@grammyjs/storage-redis';
|
||||||
|
import { session as createSession, type Middleware } from 'grammy';
|
||||||
|
|
||||||
|
const storage = new RedisAdapter({
|
||||||
|
autoParseDates: true,
|
||||||
|
instance: getRedisInstance(),
|
||||||
|
ttl: TTL_SESSION,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function session(): Middleware<Context> {
|
||||||
|
return createSession({
|
||||||
|
getSessionKey,
|
||||||
|
initial: () => ({}),
|
||||||
|
storage,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -6,7 +6,11 @@ export const envSchema = z.object({
|
|||||||
RATE_LIMIT: z
|
RATE_LIMIT: z
|
||||||
.string()
|
.string()
|
||||||
.transform((value) => Number.parseInt(value, 10))
|
.transform((value) => Number.parseInt(value, 10))
|
||||||
.default('5000'),
|
.default('2'),
|
||||||
|
RATE_LIMIT_TIME: z
|
||||||
|
.string()
|
||||||
|
.transform((value) => Number.parseInt(value, 10))
|
||||||
|
.default('3000'),
|
||||||
REDIS_HOST: z.string().default('redis'),
|
REDIS_HOST: z.string().default('redis'),
|
||||||
REDIS_PASSWORD: z.string(),
|
REDIS_PASSWORD: z.string(),
|
||||||
REDIS_PORT: z
|
REDIS_PORT: z
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export const TTL = 12 * 60 * 60; // 12 hours in seconds
|
export const TTL_URLS = 12 * 60 * 60; // 12 hours in seconds
|
||||||
|
export const TTL_SESSION = 5 * 60; // 5 minutes in seconds
|
||||||
|
|||||||
@ -2,12 +2,15 @@ import { createBot } from './bot';
|
|||||||
import { env as environment } from './config/env';
|
import { env as environment } from './config/env';
|
||||||
import { logger } from './utils/logger';
|
import { logger } from './utils/logger';
|
||||||
import { getRedisInstance } from './utils/redis';
|
import { getRedisInstance } from './utils/redis';
|
||||||
|
import { run } from '@grammyjs/runner';
|
||||||
|
|
||||||
const bot = createBot({
|
const bot = createBot({
|
||||||
apiRoot: environment.TELEGRAM_API_ROOT,
|
apiRoot: environment.TELEGRAM_API_ROOT,
|
||||||
token: environment.BOT_TOKEN,
|
token: environment.BOT_TOKEN,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const runner = run(bot);
|
||||||
|
|
||||||
const redis = getRedisInstance();
|
const redis = getRedisInstance();
|
||||||
|
|
||||||
// Graceful shutdown function
|
// Graceful shutdown function
|
||||||
@ -16,7 +19,7 @@ async function gracefulShutdown(signal: string) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Stop the bot
|
// Stop the bot
|
||||||
await bot.stop();
|
await runner.stop();
|
||||||
logger.info('Bot stopped');
|
logger.info('Bot stopped');
|
||||||
|
|
||||||
// Disconnect Redis
|
// Disconnect Redis
|
||||||
@ -33,6 +36,4 @@ async function gracefulShutdown(signal: string) {
|
|||||||
process.once('SIGINT', () => gracefulShutdown('SIGINT'));
|
process.once('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||||
process.once('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
process.once('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||||
|
|
||||||
bot.start({
|
logger.info('Bot started');
|
||||||
onStart: ({ username }) => logger.info(`Bot ${username} started`),
|
|
||||||
});
|
|
||||||
|
|||||||
5
apps/bot/src/utils/session.ts
Normal file
5
apps/bot/src/utils/session.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { type Context } from '@/bot/context';
|
||||||
|
|
||||||
|
export function getSessionKey(ctx: Omit<Context, 'session'>) {
|
||||||
|
return ctx.chat?.id.toString();
|
||||||
|
}
|
||||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@ -72,6 +72,12 @@ importers:
|
|||||||
'@grammyjs/ratelimiter':
|
'@grammyjs/ratelimiter':
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
|
'@grammyjs/runner':
|
||||||
|
specifier: ^2.0.3
|
||||||
|
version: 2.0.3(grammy@1.37.1)
|
||||||
|
'@grammyjs/storage-redis':
|
||||||
|
specifier: ^2.5.1
|
||||||
|
version: 2.5.1
|
||||||
'@grammyjs/types':
|
'@grammyjs/types':
|
||||||
specifier: ^3.21.0
|
specifier: ^3.21.0
|
||||||
version: 3.21.0
|
version: 3.21.0
|
||||||
@ -530,6 +536,15 @@ packages:
|
|||||||
'@grammyjs/ratelimiter@1.2.1':
|
'@grammyjs/ratelimiter@1.2.1':
|
||||||
resolution: {integrity: sha512-4bmVUBCBnIb2epbDiBLCvvnVjaYg7kDCPR1Ptt6gqoxm5vlD8BjainYv+yjF6221hu2KUv8QAckumDI+6xyGsQ==}
|
resolution: {integrity: sha512-4bmVUBCBnIb2epbDiBLCvvnVjaYg7kDCPR1Ptt6gqoxm5vlD8BjainYv+yjF6221hu2KUv8QAckumDI+6xyGsQ==}
|
||||||
|
|
||||||
|
'@grammyjs/runner@2.0.3':
|
||||||
|
resolution: {integrity: sha512-nckmTs1dPWfVQteK9cxqxzE+0m1VRvluLWB8UgFzsjg62w3qthPJt0TYtJBEdG7OedvfQq4vnFAyE6iaMkR42A==}
|
||||||
|
engines: {node: '>=12.20.0 || >=14.13.1'}
|
||||||
|
peerDependencies:
|
||||||
|
grammy: ^1.13.1
|
||||||
|
|
||||||
|
'@grammyjs/storage-redis@2.5.1':
|
||||||
|
resolution: {integrity: sha512-Rz7bnrtDz8NOjgSRR4n/rBciHKSvmkrgIUc+8Yb96tSsecBlA91MGOmcgmQXKxb7gTmwliF37VgmMT85bsCZtA==}
|
||||||
|
|
||||||
'@grammyjs/types@3.21.0':
|
'@grammyjs/types@3.21.0':
|
||||||
resolution: {integrity: sha512-IMj0EpmglPCICuyfGRx4ENKPSuzS2xMSoPgSPzHC6FtnWKDEmJLBP/GbPv/h3TAeb27txqxm/BUld+gbJk6ccQ==}
|
resolution: {integrity: sha512-IMj0EpmglPCICuyfGRx4ENKPSuzS2xMSoPgSPzHC6FtnWKDEmJLBP/GbPv/h3TAeb27txqxm/BUld+gbJk6ccQ==}
|
||||||
|
|
||||||
@ -4310,6 +4325,13 @@ snapshots:
|
|||||||
|
|
||||||
'@grammyjs/ratelimiter@1.2.1': {}
|
'@grammyjs/ratelimiter@1.2.1': {}
|
||||||
|
|
||||||
|
'@grammyjs/runner@2.0.3(grammy@1.37.1)':
|
||||||
|
dependencies:
|
||||||
|
abort-controller: 3.0.0
|
||||||
|
grammy: 1.37.1
|
||||||
|
|
||||||
|
'@grammyjs/storage-redis@2.5.1': {}
|
||||||
|
|
||||||
'@grammyjs/types@3.21.0': {}
|
'@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)':
|
'@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)':
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user