From e8151ed4309c68bf8da44f2ab89e48c23ba2c057 Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Thu, 14 Aug 2025 12:44:25 +0300 Subject: [PATCH] Add pino and pino-pretty for logging; enhance error handling and URL validation in Telegram bot --- package.json | 2 + pnpm-lock.yaml | 146 ++++++++++++++++++++++++++++++++++++++ src/constants/messages.ts | 5 ++ src/constants/regex.ts | 2 + src/index.ts | 18 ++++- src/utils/logger.ts | 10 +++ src/utils/urls.ts | 5 ++ 7 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 src/constants/messages.ts create mode 100644 src/constants/regex.ts create mode 100644 src/utils/logger.ts create mode 100644 src/utils/urls.ts diff --git a/package.json b/package.json index a1ff6af..f0b249d 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "dependencies": { "@tobyg74/tiktok-api-dl": "^1.3.4", "@types/node": "^24.2.1", + "pino": "^9.9.0", + "pino-pretty": "^13.1.1", "telegraf": "^4.16.3", "tsup": "^8.5.0", "zod": "^4.0.17" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff591a8..a4f832e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: '@types/node': specifier: ^24.2.1 version: 24.2.1 + pino: + specifier: ^9.9.0 + version: 9.9.0 + pino-pretty: + specifier: ^13.1.1 + version: 13.1.1 telegraf: specifier: ^4.16.3 version: 4.16.3 @@ -1040,6 +1046,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -1172,6 +1182,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1271,6 +1284,9 @@ packages: dataloader@2.2.3: resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -1803,6 +1819,9 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + fast-copy@3.0.2: + resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1827,6 +1846,13 @@ packages: resolution: {integrity: sha512-GwTgG9O4FVIdShhbVF3JxOgSBY2+ePGsu2V/UONgoCPzF9VY6ZdBMKsHKCYQHZwNk3qNouUolRDsgVxcVA5G1w==} engines: {node: '>=10.0'} + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -2055,6 +2081,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -2576,6 +2605,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -2662,6 +2695,20 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-pretty@13.1.1: + resolution: {integrity: sha512-TNNEOg0eA0u+/WuqH0MH0Xui7uqVk9D74ESOpjtebSQYbNWJk/dIxCXIxFsNfeN53JmtWqYHP2OrIZjT/CBEnA==} + hasBin: true + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.9.0: + resolution: {integrity: sha512-zxsRIQG9HzG+jEljmvmZupOMDUQ0Jpj0yAgE28jQvvrdYTlEaiGwelJpdndMl/MBuRr70heIj83QyqJUWaU8mQ==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -2717,6 +2764,9 @@ packages: engines: {node: '>=14'} hasBin: true + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2741,6 +2791,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + rambda@7.5.0: resolution: {integrity: sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==} @@ -2770,6 +2823,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + recast@0.23.11: resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} engines: {node: '>= 4'} @@ -2884,6 +2941,9 @@ packages: resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} engines: {node: ^14.0.0 || >=16.0.0} + secure-json-parse@4.0.0: + resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==} + semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} @@ -2958,6 +3018,9 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -2982,6 +3045,10 @@ packages: spdx-license-ids@3.0.22: resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} @@ -3054,6 +3121,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -3101,6 +3172,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + timeout-signal@2.0.0: resolution: {integrity: sha512-YBGpG4bWsHoPvofT6y/5iqulfXIiIErl5B0LdtHT1mGXDFTAhhRrbUpTvBgYbovr+3cKblya2WAOcpoy90XguA==} engines: {node: '>=16'} @@ -4494,6 +4568,8 @@ snapshots: asynckit@0.4.0: {} + atomic-sleep@1.0.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -4641,6 +4717,8 @@ snapshots: color-name@1.1.4: {} + colorette@2.0.20: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -4740,6 +4818,8 @@ snapshots: dataloader@2.2.3: {} + dateformat@4.6.3: {} + debug@3.2.7: dependencies: ms: 2.1.3 @@ -5519,6 +5599,8 @@ snapshots: expand-template@2.0.3: {} + fast-copy@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -5545,6 +5627,10 @@ snapshots: fast-printf@1.6.10: {} + fast-redact@3.5.0: {} + + fast-safe-stringify@2.1.1: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -5771,6 +5857,8 @@ snapshots: dependencies: function-bind: 1.1.2 + help-me@5.0.0: {} + hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -6281,6 +6369,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + on-exit-leak-free@2.1.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -6369,6 +6459,42 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.1: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 3.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pump: 3.0.3 + secure-json-parse: 4.0.0 + sonic-boom: 4.2.0 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.0.0: {} + + pino@9.9.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + pirates@4.0.7: {} pkg-dir@5.0.0: @@ -6416,6 +6542,8 @@ snapshots: prettier@3.6.2: {} + process-warning@5.0.0: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -6439,6 +6567,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + rambda@7.5.0: {} ramda@0.30.1: {} @@ -6474,6 +6604,8 @@ snapshots: readdirp@4.1.2: {} + real-require@0.2.0: {} + recast@0.23.11: dependencies: ast-types: 0.16.1 @@ -6618,6 +6750,8 @@ snapshots: refa: 0.12.1 regexp-ast-analysis: 0.7.1 + secure-json-parse@4.0.0: {} + semver-compare@1.0.0: {} semver@6.3.1: {} @@ -6707,6 +6841,10 @@ snapshots: ip-address: 10.0.1 smart-buffer: 4.2.0 + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + source-map@0.6.1: {} source-map@0.8.0-beta.0: @@ -6732,6 +6870,8 @@ snapshots: spdx-license-ids@3.0.22: {} + split2@4.2.0: {} + stable-hash-x@0.2.0: {} stable-hash@0.0.5: {} @@ -6827,6 +6967,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -6894,6 +7036,10 @@ snapshots: dependencies: any-promise: 1.3.0 + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + timeout-signal@2.0.0: {} tiny-invariant@1.3.3: {} diff --git a/src/constants/messages.ts b/src/constants/messages.ts new file mode 100644 index 0000000..47fff1c --- /dev/null +++ b/src/constants/messages.ts @@ -0,0 +1,5 @@ +export const ERROR_MESSAGES = { + INVALID_URL: 'Invalid url', + INVALID_DOWNLOAD_URLS: 'Invalid download urls found', + GENERIC: 'Something went wrong', +}; diff --git a/src/constants/regex.ts b/src/constants/regex.ts new file mode 100644 index 0000000..ba1766b --- /dev/null +++ b/src/constants/regex.ts @@ -0,0 +1,2 @@ +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+)/; diff --git a/src/index.ts b/src/index.ts index bece6ab..1509ac1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,9 @@ import { env } from './config/env'; import { Downloader } from '@tobyg74/tiktok-api-dl'; import { Telegraf as Bot } from 'telegraf'; import { message } from 'telegraf/filters'; +import { logger } from './utils/logger'; +import { ERROR_MESSAGES } from './constants/messages'; +import { validateTikTokUrl } from './utils/urls'; const bot = new Bot(env.BOT_TOKEN, { telegram: { @@ -13,6 +16,8 @@ bot.on(message('text'), async (ctx) => { try { const url = ctx.message.text; + if (!validateTikTokUrl(url)) return ctx.reply(ERROR_MESSAGES.INVALID_URL); + const { result, message } = await Downloader(url, { version: 'v3', }); @@ -22,6 +27,10 @@ bot.on(message('text'), async (ctx) => { const videoUrl = result?.videoHD || result?.videoSD || result?.videoWatermark; const imagesUrls = result?.images; + if (!videoUrl && !imagesUrls?.length) { + return ctx.reply(ERROR_MESSAGES.INVALID_DOWNLOAD_URLS); + } + if (result?.type === 'video' && videoUrl) { return ctx.replyWithVideo({ url: videoUrl, @@ -32,10 +41,13 @@ bot.on(message('text'), async (ctx) => { return ctx.replyWithMediaGroup(imagesUrls.map((image) => ({ media: image, type: 'photo' }))); } } catch (error) { - const err_ = error as Error; + logger.error(error); - return ctx.reply(err_.message); + return ctx.reply(ERROR_MESSAGES.GENERIC); } }); -bot.launch(); +bot.launch(() => logger.info('Bot started')); + +process.once('SIGINT', () => bot.stop('SIGINT')); +process.once('SIGTERM', () => bot.stop('SIGTERM')); diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..e6c6ebb --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,10 @@ +import pino from 'pino'; + +export const logger = pino({ + transport: { + target: 'pino-pretty', + options: { + colorize: true, + }, + }, +}); diff --git a/src/utils/urls.ts b/src/utils/urls.ts new file mode 100644 index 0000000..20834af --- /dev/null +++ b/src/utils/urls.ts @@ -0,0 +1,5 @@ +import { TIKTOK_URL_REGEX } from "@/constants/regex"; + +export function validateTikTokUrl(url: string) { + return TIKTOK_URL_REGEX.test(url); +}