diff --git a/apps/bot/eslint.config.js b/apps/bot/eslint.config.js index 7f22eb8..ac19d8d 100644 --- a/apps/bot/eslint.config.js +++ b/apps/bot/eslint.config.js @@ -11,6 +11,7 @@ export default [ '@typescript-eslint/naming-convention': 'off', 'unicorn/prevent-abbreviations': 'off', 'canonical/id-match': 'off', + 'id-length': 'off', }, }, ]; diff --git a/apps/bot/src/bot/features/download.ts b/apps/bot/src/bot/features/download.ts index fb1be43..b1a1923 100644 --- a/apps/bot/src/bot/features/download.ts +++ b/apps/bot/src/bot/features/download.ts @@ -2,13 +2,13 @@ import { type Context } from '../context'; import { logHandle } from '../helpers/logging'; import { TTL_URLS } from '@/config/redis'; +import { getInstagramDownloadUrl } from '@/utils/instagram'; import { getRedisInstance } from '@/utils/redis'; import { getTiktokDownloadUrl } from '@/utils/tiktok'; -import { getInstagramDownloadUrl } from '@/utils/instagram'; -import { validateTikTokUrl, validateInstagramUrl, validateYoutubeUrl } from '@/utils/urls'; +import { validateInstagramUrl, validateTikTokUrl, validateYoutubeUrl } from '@/utils/urls'; +import { getYoutubeDownloadUrl } from '@/utils/youtube'; import { Composer, InputFile } from 'grammy'; import { cluster } from 'radashi'; -import { getYoutubeDownloadUrl } from '@/utils/youtube'; const composer = new Composer(); const feature = composer.chatType('private'); @@ -22,7 +22,9 @@ feature.on('message:text', logHandle('download-message'), async (context) => { const isInstagram = validateInstagramUrl(url); const isYoutube = validateYoutubeUrl(url); - if (!isTikTok && !isInstagram && !isYoutube) { + const isSupportedService = isTikTok || isInstagram || isYoutube; + + if (!isSupportedService) { return context.reply(context.t('err-invalid-url')); } @@ -47,8 +49,9 @@ feature.on('message:text', logHandle('download-message'), async (context) => { const result = await getYoutubeDownloadUrl(url); videoUrl = result.play; } - } catch (err: any) { - const message = err?.message ?? String(err); + } catch (error_: unknown) { + const error = error_ as Error; + const message = error?.message ?? String(error); if (typeof message === 'string' && message.startsWith('err-')) { return context.reply(context.t(message)); } diff --git a/apps/bot/src/constants/limits.ts b/apps/bot/src/constants/limits.ts index 85c6701..9fa716d 100644 --- a/apps/bot/src/constants/limits.ts +++ b/apps/bot/src/constants/limits.ts @@ -1 +1 @@ -export const MAX_VIDEO_DURATION_SECONDS = 180; // 3 minutes \ No newline at end of file +export const MAX_VIDEO_DURATION_SECONDS = 180; // 3 minutes diff --git a/apps/bot/src/constants/regex.ts b/apps/bot/src/constants/regex.ts index 63d7f42..a803d7e 100644 --- a/apps/bot/src/constants/regex.ts +++ b/apps/bot/src/constants/regex.ts @@ -3,6 +3,7 @@ export const TIKTOK_URL_REGEX = /https:\/\/(?:m|t|www|vm|vt|lite)?\.?tiktok\.com\/(.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|&item_id=)(\d+)|\w+)/u; export const INSTAGRAM_URL_REGEX = - /https?:\/\/(www\.)?instagram\.com\/(p|reel|tv|stories)\/([a-zA-Z0-9_-]+)(\/)?(\?utm_source=ig_web_copy_link&igshid=[a-z0-9]+)?/u; + /https?:\/\/(www\.)?instagram\.com\/(p|reel|tv|stories)\/([\w-]+)(\/)?(\?utm_source=ig_web_copy_link&igshid=[a-z0-9]+)?/u; -export const YOUTUBE_URL_REGEX = /(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))/u; +export const YOUTUBE_URL_REGEX = + /(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/))([\w-]{11})(?:[?&]\S*)?/u; diff --git a/apps/bot/src/utils/instagram.ts b/apps/bot/src/utils/instagram.ts index ba33454..f264e81 100644 --- a/apps/bot/src/utils/instagram.ts +++ b/apps/bot/src/utils/instagram.ts @@ -1,26 +1,26 @@ import axios from 'axios'; -export interface Root { - type: string; - id: number; - url: string; - username: string; - mediaUrls: string[]; - carouselItems: CarouselItem[]; - authorInfo: AuthorInfo; -} - -export interface CarouselItem { - url: string; - type: string; -} - -export interface AuthorInfo { - id: number; - username: string; - nickname: string; +export type AuthorInfo = { avatar: string; -} + id: number; + nickname: string; + username: string; +}; + +export type CarouselItem = { + type: string; + url: string; +}; + +export type Root = { + authorInfo: AuthorInfo; + carouselItems: CarouselItem[]; + id: number; + mediaUrls: string[]; + type: string; + url: string; + username: string; +}; export async function getInstagramDownloadUrl(url: string) { const { data } = await axios.post('https://thesocialcat.com/api/instagram-download', { @@ -33,8 +33,8 @@ export async function getInstagramDownloadUrl(url: string) { if (isVideo) { return { - play: data.mediaUrls.at(0), images: [], + play: data.mediaUrls.at(0), }; } diff --git a/apps/bot/src/utils/urls.ts b/apps/bot/src/utils/urls.ts index c9457f0..0e3790f 100644 --- a/apps/bot/src/utils/urls.ts +++ b/apps/bot/src/utils/urls.ts @@ -1,13 +1,13 @@ import { INSTAGRAM_URL_REGEX, TIKTOK_URL_REGEX, YOUTUBE_URL_REGEX } from '@/constants/regex'; -export function validateTikTokUrl(url: string) { - return TIKTOK_URL_REGEX.test(url); -} - export function validateInstagramUrl(url: string) { return INSTAGRAM_URL_REGEX.test(url); } +export function validateTikTokUrl(url: string) { + return TIKTOK_URL_REGEX.test(url); +} + export function validateYoutubeUrl(url: string) { return YOUTUBE_URL_REGEX.test(url); } diff --git a/apps/bot/src/utils/youtube.ts b/apps/bot/src/utils/youtube.ts index c905320..6a78fde 100644 --- a/apps/bot/src/utils/youtube.ts +++ b/apps/bot/src/utils/youtube.ts @@ -24,32 +24,32 @@ const headers = { const client = wrapper( axios.create({ + headers, jar, withCredentials: true, - headers, }), ); -export interface Media { - type: string; - quality: string; - extension: string; - fileSize: number; -} - -export interface InfoRoot { - title: string; - thumbnail: string; +export type DownloadRoot = { duration: number; - medias: Media[]; -} - -export interface DownloadRoot { + filename: string; status: string; url: string; - filename: string; +}; + +export type InfoRoot = { duration: number; -} + medias: Media[]; + thumbnail: string; + title: string; +}; + +export type Media = { + extension: string; + fileSize: number; + quality: string; + type: string; +}; const qualityOrder = ['144p', '240p', '360p', '480p', '1080p', '720p'].reverse(); @@ -66,7 +66,8 @@ export async function getYoutubeDownloadUrl(url: string) { ); if (!infoData?.medias.length) throw new Error('err-invalid-youtube-response'); - if (infoData.duration > MAX_VIDEO_DURATION_SECONDS) throw new Error('err-youtube-duration-exceeded'); + if (infoData.duration > MAX_VIDEO_DURATION_SECONDS) + throw new Error('err-youtube-duration-exceeded'); let quality: string | undefined;