Update dependencies for Grammy framework; add new packages for enhanced bot functionality and improve logging configuration.
This commit is contained in:
parent
8225bbe546
commit
140477c87a
@ -13,7 +13,11 @@
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531",
|
||||
"dependencies": {
|
||||
"@grammyjs/auto-chat-action": "^0.1.1",
|
||||
"@grammyjs/hydrate": "^1.4.1",
|
||||
"@grammyjs/i18n": "^1.1.2",
|
||||
"@grammyjs/parse-mode": "^2.2.0",
|
||||
"@grammyjs/types": "^3.21.0",
|
||||
"@tobyg74/tiktok-api-dl": "^1.3.4",
|
||||
"@types/node": "^24.2.1",
|
||||
"grammy": "^1.37.0",
|
||||
|
||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@ -8,9 +8,21 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@grammyjs/auto-chat-action':
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1(grammy@1.37.0)
|
||||
'@grammyjs/hydrate':
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1(grammy@1.37.0)
|
||||
'@grammyjs/i18n':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(grammy@1.37.0)
|
||||
'@grammyjs/parse-mode':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(grammy@1.37.0)
|
||||
'@grammyjs/types':
|
||||
specifier: ^3.21.0
|
||||
version: 3.21.0
|
||||
'@tobyg74/tiktok-api-dl':
|
||||
specifier: ^1.3.4
|
||||
version: 1.3.4(@types/node@24.2.1)(canvas@3.1.2)(typescript@5.9.2)
|
||||
@ -403,12 +415,29 @@ packages:
|
||||
resolution: {integrity: sha512-YF4gZ4sLYRQfctpUR2uhb5UyPUYY5n/bi3OaED/Q4awKjPjlaF8tInO3uja7pnLQcmLTURkZL7L9zxv2Z5NDwg==}
|
||||
engines: {node: '>=12.0.0', npm: '>=7.0.0'}
|
||||
|
||||
'@grammyjs/auto-chat-action@0.1.1':
|
||||
resolution: {integrity: sha512-70Jdy+keIri4452tluaZpKtHag5gD8pNczoXqajsnsx7pJC5wg3DAQ5unpt0xJb8KsVBAGWuJb298SBl3TVecg==}
|
||||
peerDependencies:
|
||||
grammy: ^1.0.0
|
||||
|
||||
'@grammyjs/hydrate@1.4.1':
|
||||
resolution: {integrity: sha512-xvd7XXoVHEhxBV2M8UHDRYkZlv32JioHfs+0l+eN34mkJJJBchuRDgRY3fkVJko8o1hGFHHLAJJTlEs2OAMolA==}
|
||||
engines: {node: ^12.20.0 || >=14.13.1}
|
||||
peerDependencies:
|
||||
grammy: ^1.20.1
|
||||
|
||||
'@grammyjs/i18n@1.1.2':
|
||||
resolution: {integrity: sha512-PcK06mxuDDZjxdZ5HywBhr+erEITsR816KP4DNIDDds1jpA45pfz/nS9FdZmzF8H6lMyPix3mV5WL1rT4q+BuA==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
grammy: ^1.10.0
|
||||
|
||||
'@grammyjs/parse-mode@2.2.0':
|
||||
resolution: {integrity: sha512-sI5xjXYn1ihEEf1bJx4ew2KPsX1O3jsd2V/MpA1CX2tCYlxquidr7agk4IOR5bGEK38pyNVxVBdyCiy/eMxEfQ==}
|
||||
engines: {node: '>=14.13.1'}
|
||||
peerDependencies:
|
||||
grammy: ^1.36.1
|
||||
|
||||
'@grammyjs/types@3.21.0':
|
||||
resolution: {integrity: sha512-IMj0EpmglPCICuyfGRx4ENKPSuzS2xMSoPgSPzHC6FtnWKDEmJLBP/GbPv/h3TAeb27txqxm/BUld+gbJk6ccQ==}
|
||||
|
||||
@ -3816,6 +3845,15 @@ snapshots:
|
||||
|
||||
'@fluent/langneg@0.6.2': {}
|
||||
|
||||
'@grammyjs/auto-chat-action@0.1.1(grammy@1.37.0)':
|
||||
dependencies:
|
||||
grammy: 1.37.0
|
||||
|
||||
'@grammyjs/hydrate@1.4.1(grammy@1.37.0)':
|
||||
dependencies:
|
||||
abort-controller: 3.0.0
|
||||
grammy: 1.37.0
|
||||
|
||||
'@grammyjs/i18n@1.1.2(grammy@1.37.0)':
|
||||
dependencies:
|
||||
'@deno/shim-deno': 0.18.2
|
||||
@ -3823,6 +3861,10 @@ snapshots:
|
||||
'@fluent/langneg': 0.6.2
|
||||
grammy: 1.37.0
|
||||
|
||||
'@grammyjs/parse-mode@2.2.0(grammy@1.37.0)':
|
||||
dependencies:
|
||||
grammy: 1.37.0
|
||||
|
||||
'@grammyjs/types@3.21.0': {}
|
||||
|
||||
'@graphql-eslint/eslint-plugin@4.4.0(@types/node@24.2.1)(eslint@9.33.0(jiti@2.5.1))(graphql@16.11.0)(typescript@5.9.2)':
|
||||
|
||||
13
src/bot/context.ts
Normal file
13
src/bot/context.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { logger } from '@/utils/logger';
|
||||
import { HydrateFlavor } from '@grammyjs/hydrate';
|
||||
import { I18nFlavor } from '@grammyjs/i18n';
|
||||
import { Context as DefaultContext } from 'grammy';
|
||||
import type { AutoChatActionFlavor } from '@grammyjs/auto-chat-action';
|
||||
|
||||
export type Context = HydrateFlavor<
|
||||
DefaultContext &
|
||||
AutoChatActionFlavor &
|
||||
I18nFlavor & {
|
||||
logger: typeof logger;
|
||||
}
|
||||
>;
|
||||
42
src/bot/features/download.ts
Normal file
42
src/bot/features/download.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { validateTikTokUrl } from '@/utils/urls';
|
||||
import type { Context } from '../context';
|
||||
import { logHandle } from '../helpers/logging';
|
||||
import { Composer, InputFile } from 'grammy';
|
||||
import { Downloader } from '@tobyg74/tiktok-api-dl';
|
||||
|
||||
const composer = new Composer<Context>();
|
||||
|
||||
const feature = composer.chatType('private');
|
||||
|
||||
feature.on('message:text', logHandle('download-message'), async (ctx) => {
|
||||
try {
|
||||
const url = ctx.message.text;
|
||||
|
||||
if (!validateTikTokUrl(url)) return ctx.reply(ctx.t('invalid_url'));
|
||||
|
||||
const { result, message } = await Downloader(url, { version: 'v3' });
|
||||
|
||||
if (message) throw new Error(message);
|
||||
|
||||
const videoUrl = result?.videoHD || result?.videoSD || result?.videoWatermark;
|
||||
const imagesUrls = result?.images;
|
||||
|
||||
if (!videoUrl && !imagesUrls?.length) {
|
||||
return ctx.reply(ctx.t('invalid_download_urls'));
|
||||
}
|
||||
|
||||
if (result?.type === 'video' && videoUrl) {
|
||||
return ctx.replyWithVideo(new InputFile({ url: videoUrl }));
|
||||
}
|
||||
|
||||
if (result?.type === 'image' && imagesUrls) {
|
||||
return ctx.replyWithMediaGroup(imagesUrls.map((image) => ({ media: image, type: 'photo' })));
|
||||
}
|
||||
} catch (error) {
|
||||
ctx.logger.error(error);
|
||||
|
||||
return ctx.reply(ctx.t('generic'));
|
||||
}
|
||||
});
|
||||
|
||||
export { composer as download };
|
||||
1
src/bot/features/index.ts
Normal file
1
src/bot/features/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './download';
|
||||
12
src/bot/handlers/errors.ts
Normal file
12
src/bot/handlers/errors.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { Context } from '../context';
|
||||
import type { ErrorHandler } from 'grammy';
|
||||
import { getUpdateInfo } from '../helpers/logging';
|
||||
|
||||
export const errorHandler: ErrorHandler<Context> = (error) => {
|
||||
const { ctx } = error;
|
||||
|
||||
ctx.logger.error({
|
||||
err: error.error,
|
||||
update: getUpdateInfo(ctx),
|
||||
});
|
||||
};
|
||||
20
src/bot/helpers/logging.ts
Normal file
20
src/bot/helpers/logging.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { Context } from '../context';
|
||||
import type { Update } from '@grammyjs/types';
|
||||
import type { Middleware } from 'grammy';
|
||||
|
||||
export function getUpdateInfo(ctx: Context): Omit<Update, 'update_id'> {
|
||||
const { update_id, ...update } = ctx.update;
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
export function logHandle(id: string): Middleware<Context> {
|
||||
return (ctx, next) => {
|
||||
ctx.logger.info({
|
||||
msg: `Handle "${id}"`,
|
||||
...(id.startsWith('unhandled') ? { update: getUpdateInfo(ctx) } : {}),
|
||||
});
|
||||
|
||||
return next();
|
||||
};
|
||||
}
|
||||
15
src/bot/i18n.ts
Normal file
15
src/bot/i18n.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { Context } from './context';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { I18n } from '@grammyjs/i18n';
|
||||
|
||||
export const i18n = new I18n<Context>({
|
||||
defaultLocale: 'en',
|
||||
directory: path.resolve(process.cwd(), 'locales'),
|
||||
useSession: true,
|
||||
fluentBundleOptions: {
|
||||
useIsolating: false,
|
||||
},
|
||||
});
|
||||
|
||||
export const isMultipleLocales = i18n.locales.length > 1;
|
||||
38
src/bot/index.ts
Normal file
38
src/bot/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Bot } from 'grammy';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { Context } from './context';
|
||||
import { i18n } from './i18n';
|
||||
import { errorHandler } from './handlers/errors';
|
||||
import * as features from './features';
|
||||
import { hydrate } from '@grammyjs/hydrate';
|
||||
import { autoChatAction } from '@grammyjs/auto-chat-action';
|
||||
|
||||
type Params = {
|
||||
token: string;
|
||||
apiRoot: string;
|
||||
};
|
||||
|
||||
export function createBot({ token, apiRoot }: Params) {
|
||||
const bot = new Bot<Context>(token, {
|
||||
client: {
|
||||
apiRoot,
|
||||
},
|
||||
});
|
||||
|
||||
bot.use(async (ctx, next) => {
|
||||
ctx.logger = logger.child({
|
||||
update_id: ctx.update.update_id,
|
||||
});
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
const protectedBot = bot.errorBoundary(errorHandler);
|
||||
|
||||
protectedBot.use(i18n);
|
||||
protectedBot.use(autoChatAction(bot.api));
|
||||
protectedBot.use(hydrate());
|
||||
protectedBot.use(features.download);
|
||||
|
||||
return bot;
|
||||
}
|
||||
54
src/index.ts
54
src/index.ts
@ -1,56 +1,10 @@
|
||||
import { createBot } from './bot';
|
||||
import { env } from './config/env';
|
||||
import { Downloader } from '@tobyg74/tiktok-api-dl';
|
||||
import { Bot, Context, InputFile } from 'grammy';
|
||||
import { logger } from './utils/logger';
|
||||
import { validateTikTokUrl } from './utils/urls';
|
||||
import { I18n, I18nFlavor } from '@grammyjs/i18n';
|
||||
|
||||
type MyContext = Context & I18nFlavor;
|
||||
|
||||
const bot = new Bot<MyContext>(env.BOT_TOKEN, {
|
||||
client: {
|
||||
apiRoot: env.TELEGRAM_API_ROOT,
|
||||
},
|
||||
});
|
||||
|
||||
const i18n = new I18n<MyContext>({
|
||||
defaultLocale: 'en', // see below for more information
|
||||
directory: 'locales', // Load all translation files from locales/.
|
||||
});
|
||||
|
||||
bot.use(i18n);
|
||||
|
||||
bot.on('message:text', async (ctx) => {
|
||||
try {
|
||||
const url = ctx.message.text;
|
||||
|
||||
if (!validateTikTokUrl(url)) return ctx.reply(ctx.t('invalid_url'));
|
||||
|
||||
const { result, message } = await Downloader(url, { version: 'v3' });
|
||||
|
||||
if (message) throw new Error(message);
|
||||
|
||||
const videoUrl = result?.videoHD || result?.videoSD || result?.videoWatermark;
|
||||
const imagesUrls = result?.images;
|
||||
|
||||
if (!videoUrl && !imagesUrls?.length) {
|
||||
return ctx.reply(ctx.t('invalid_download_urls'));
|
||||
}
|
||||
|
||||
if (result?.type === 'video' && videoUrl) {
|
||||
await ctx.replyWithChatAction('upload_video');
|
||||
return ctx.replyWithVideo(new InputFile({ url: videoUrl }));
|
||||
}
|
||||
|
||||
if (result?.type === 'image' && imagesUrls) {
|
||||
await ctx.replyWithChatAction('upload_photo');
|
||||
return ctx.replyWithMediaGroup(imagesUrls.map((image) => ({ media: image, type: 'photo' })));
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
|
||||
return ctx.reply(ctx.t('generic'));
|
||||
}
|
||||
const bot = createBot({
|
||||
token: env.BOT_TOKEN,
|
||||
apiRoot: env.TELEGRAM_API_ROOT,
|
||||
});
|
||||
|
||||
// Stopping the bot when the Node.js process
|
||||
|
||||
@ -3,8 +3,10 @@ import pino from 'pino';
|
||||
export const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user