docker-compose: add Redis service and update bot configuration for caching; integrate Redis in download feature for improved performance

This commit is contained in:
vchikalkin 2025-08-15 17:23:55 +03:00
parent 0b6c4427c9
commit 463bdfd089
7 changed files with 134 additions and 5 deletions

View File

@ -27,8 +27,9 @@
"@tobyg74/tiktok-api-dl": "^1.3.4",
"@types/node": "catalog:",
"grammy": "^1.37.0",
"pino-pretty": "^13.1.1",
"ioredis": "^5.7.0",
"pino": "^9.9.0",
"pino-pretty": "^13.1.1",
"tsup": "^8.5.0",
"typescript": "catalog:",
"zod": "catalog:"

View File

@ -1,6 +1,8 @@
/* eslint-disable consistent-return */
import { type Context } from '../context';
import { logHandle } from '../helpers/logging';
import { TTL } from '@/config/redis';
import { createRedisInstance } from '@/utils/redis';
import { validateTikTokUrl } from '@/utils/urls';
import { Downloader } from '@tobyg74/tiktok-api-dl';
import { Composer, InputFile } from 'grammy';
@ -9,12 +11,18 @@ const composer = new Composer<Context>();
const feature = composer.chatType('private');
const redis = createRedisInstance();
feature.on('message:text', logHandle('download-message'), async (context) => {
try {
const url = context.message.text;
const url = context.message.text.trim();
if (!validateTikTokUrl(url)) return context.reply(context.t('invalid_url'));
const cachedFileId = await redis.get(url);
if (cachedFileId) return context.replyWithVideo(cachedFileId);
const { message, result } = await Downloader(url, { version: 'v3' });
if (message) throw new Error(message);
@ -27,7 +35,9 @@ feature.on('message:text', logHandle('download-message'), async (context) => {
}
if (result?.type === 'video' && videoUrl) {
return context.replyWithVideo(new InputFile({ url: videoUrl }));
const { video } = await context.replyWithVideo(new InputFile({ url: videoUrl }));
await redis.set(url, video.file_id, 'EX', TTL);
}
if (result?.type === 'image' && imagesUrls) {

View File

@ -3,6 +3,12 @@ import { z } from 'zod';
export const envSchema = z.object({
BOT_TOKEN: z.string(),
REDIS_HOST: z.string(),
REDIS_PASSWORD: z.string(),
REDIS_PORT: z
.string()
.transform((value) => Number.parseInt(value, 10))
.default('6379'),
TELEGRAM_API_ROOT: z.string(),
});

View File

@ -0,0 +1 @@
export const TTL = 12 * 60 * 60; // 12 hours in seconds

View File

@ -0,0 +1,15 @@
import { env } from '@/config/env';
import { logger } from '@/utils/logger';
import Redis from 'ioredis';
export function createRedisInstance() {
const redis = new Redis({
host: env.REDIS_HOST,
password: env.REDIS_PASSWORD,
port: env.REDIS_PORT,
});
redis.on('error', logger.error);
return redis;
}

View File

@ -1,4 +1,29 @@
services:
redis:
env_file:
- .env
image: redis:8-alpine
restart: always
ports:
- "127.0.0.1:6379:6379"
volumes:
- redis-data:/data
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
next-downloader-bot:
env_file:
- .env
@ -6,6 +31,9 @@ services:
context: .
dockerfile: ./apps/bot/Dockerfile
restart: always
depends_on:
- redis
telegram-bot-api:
env_file:
- .env
@ -15,8 +43,9 @@ services:
# TELEGRAM_API_HASH: "<api-hash>"
volumes:
- telegram-bot-api-data:/var/lib/telegram-bot-api
# ports:
# - "8081:8081"
ports:
- "127.0.0.1:8081:8081"
volumes:
telegram-bot-api-data:
redis-data:

67
pnpm-lock.yaml generated
View File

@ -81,6 +81,9 @@ importers:
grammy:
specifier: ^1.37.0
version: 1.37.1
ioredis:
specifier: ^5.7.0
version: 5.7.0
pino:
specifier: ^9.9.0
version: 9.9.0
@ -673,6 +676,9 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@ioredis/commands@1.3.0':
resolution: {integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==}
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@ -1331,6 +1337,10 @@ packages:
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
engines: {node: '>=18'}
cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@ -1499,6 +1509,10 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
@ -2367,6 +2381,10 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
ioredis@5.7.0:
resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==}
engines: {node: '>=12.22.0'}
ip-address@10.0.1:
resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==}
engines: {node: '>= 12'}
@ -2657,10 +2675,16 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
lodash.lowercase@4.3.0:
resolution: {integrity: sha512-UcvP1IZYyDKyEL64mmrwoA1AbFu5ahojhTtkOUr1K9dbuxzS9ev8i4TxMMGCqRC9TE8uDaSoufNAXxRPNTseVA==}
@ -3180,6 +3204,14 @@ packages:
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
engines: {node: '>= 4'}
redis-errors@1.2.0:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
redis-parser@3.0.0:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'}
refa@0.12.1:
resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@ -3422,6 +3454,9 @@ packages:
stable-hash@0.0.5:
resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
standard-as-callback@2.1.0:
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
@ -4501,6 +4536,8 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@ioredis/commands@1.3.0': {}
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@ -5201,6 +5238,8 @@ snapshots:
slice-ansi: 5.0.0
string-width: 7.2.0
cluster-key-slot@1.1.2: {}
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@ -5348,6 +5387,8 @@ snapshots:
delayed-stream@1.0.0: {}
denque@2.1.0: {}
detect-libc@2.0.4: {}
didyoumean@1.2.2: {}
@ -6465,6 +6506,20 @@ snapshots:
hasown: 2.0.2
side-channel: 1.1.0
ioredis@5.7.0:
dependencies:
'@ioredis/commands': 1.3.0
cluster-key-slot: 1.1.2
debug: 4.4.1
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
redis-errors: 1.2.0
redis-parser: 3.0.0
standard-as-callback: 2.1.0
transitivePeerDependencies:
- supports-color
ip-address@10.0.1: {}
is-array-buffer@3.0.5:
@ -6778,8 +6833,12 @@ snapshots:
dependencies:
p-locate: 5.0.0
lodash.defaults@4.2.0: {}
lodash.get@4.4.2: {}
lodash.isarguments@3.1.0: {}
lodash.lowercase@4.3.0: {}
lodash.merge@4.6.2: {}
@ -7281,6 +7340,12 @@ snapshots:
tiny-invariant: 1.3.3
tslib: 2.8.1
redis-errors@1.2.0: {}
redis-parser@3.0.0:
dependencies:
redis-errors: 1.2.0
refa@0.12.1:
dependencies:
'@eslint-community/regexpp': 4.12.1
@ -7562,6 +7627,8 @@ snapshots:
stable-hash@0.0.5: {}
standard-as-callback@2.1.0: {}
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0