├── utils └── sleep.js ├── .github ├── dependabot.yml └── workflows │ ├── njsscan.yml │ └── codeql.yml ├── package.json ├── index.js ├── plugins ├── tiktok_video.js └── tiktok_photo.js ├── handler.js ├── config.js ├── README.md └── main.js /utils/sleep.js: -------------------------------------------------------------------------------- 1 | function sleep(ms) { 2 | return new Promise(resolve => setTimeout(resolve, ms)); 3 | } 4 | 5 | module.exports = sleep; -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiktok-tele-bot", 3 | "version": "0.0.1", 4 | "description": "Simple Telegram Bot TikTok Downloader.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "start:prod": "node index.js", 9 | "test": "nodemon index.js" 10 | }, 11 | "author": "Tio", 12 | "license": "ISC", 13 | "engines": { 14 | "node": ">=20.18.1" 15 | }, 16 | "dependencies": { 17 | "axios": "^1.12.2", 18 | "btch-downloader": "^6.0.17", 19 | "chalk": "^4.1.1", 20 | "express": "^4.18.2", 21 | "node-telegram-bot-api": "^0.66.0" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^3.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { PORT } = require('./config'); 3 | require('./main'); 4 | 5 | const app = express(); 6 | 7 | app.get('/', (req, res) => { 8 | res.setHeader('Content-Type', 'application/json'); 9 | const data = { 10 | status: 'true', 11 | message: 'Bot Successfully Activated!', 12 | author: 'BOTCAHX' 13 | }; 14 | const result = { 15 | response: data 16 | }; 17 | res.send(JSON.stringify(result, null, 2)); 18 | }); 19 | 20 | function listenOnPort(port) { 21 | app.listen(port, () => { 22 | console.log(`Server is running on port ${port}`); 23 | }); 24 | app.on('error', (err) => { 25 | if (err.code === 'EADDRINUSE') { 26 | console.log(`Port ${port} is already in use. Trying another port...`); 27 | listenOnPort(port + 1); 28 | } else { 29 | console.error(err); 30 | } 31 | }); 32 | } 33 | 34 | listenOnPort(PORT); -------------------------------------------------------------------------------- /plugins/tiktok_video.js: -------------------------------------------------------------------------------- 1 | const sleep = require('../utils/sleep'); 2 | 3 | async function tiktok_video(bot, msg, data) { 4 | const From = msg.chat.id; 5 | const { title, title_audio, video, audio } = data; 6 | 7 | const caption = `Title: ${title}\nAudio: ${title_audio}`; 8 | 9 | try { 10 | await bot.sendVideo(From, video[0], { 11 | caption, 12 | reply_markup: { 13 | inline_keyboard: [[{ text: '🎥 URL Video', url: video[0] }]] 14 | } 15 | }); 16 | await sleep(3000); 17 | 18 | await bot.sendAudio(From, audio[0], { 19 | caption: `Audio: ${title_audio}`, 20 | reply_markup: { 21 | inline_keyboard: [[{ text: '🎵 URL Audio', url: audio[0] }]] 22 | } 23 | }); 24 | await sleep(3000); 25 | 26 | await bot.sendMessage(From, 'Powered by @wtffry', { 27 | reply_markup: { 28 | inline_keyboard: [[{ text: '👨‍💻 Support', url: 'https://t.me/wtffry' }]] 29 | } 30 | }); 31 | } catch (error) { 32 | await bot.sendMessage(From, 'Sorry, an error occurred while sending the TikTok video.'); 33 | console.error(`[ ERROR ] ${From}: ${error.message}`); 34 | } 35 | } 36 | 37 | module.exports = tiktok_video; -------------------------------------------------------------------------------- /plugins/tiktok_photo.js: -------------------------------------------------------------------------------- 1 | const sleep = require('../utils/sleep'); 2 | 3 | async function tiktok_photo(bot, msg, data) { 4 | const From = msg.chat.id; 5 | const { title = '', title_audio = '', video = [], audio = [], images = [] } = data; 6 | 7 | const caption = `Title: ${title}\nAudio: ${title_audio}`; 8 | const photoUrls = images.length > 0 ? images : video; 9 | 10 | try { 11 | const media = photoUrls.map((url, i) => ({ 12 | type: 'photo', 13 | media: url, 14 | caption: i === 0 ? caption : undefined 15 | })); 16 | 17 | await bot.sendMediaGroup(From, media); 18 | await sleep(3000); 19 | 20 | if (audio[0]) { 21 | await bot.sendAudio(From, audio[0], { 22 | caption: `Audio: ${title_audio}`, 23 | reply_markup: { 24 | inline_keyboard: [[{ text: 'URL Audio', url: audio[0] }]] 25 | } 26 | }); 27 | await sleep(3000); 28 | } 29 | 30 | await bot.sendMessage(From, 'Powered by @wtffry', { 31 | reply_markup: { 32 | inline_keyboard: [[{ text: 'Support', url: 'https://t.me/wtffry' }]] 33 | } 34 | }); 35 | 36 | } catch (error) { 37 | await bot.sendMessage(From, { text: 'Sorry, an error occurred while sending the TikTok photo.' }); 38 | console.error(`[PHOTO ERROR] ${From}:`, error.message); 39 | } 40 | } 41 | 42 | module.exports = tiktok_photo; 43 | -------------------------------------------------------------------------------- /handler.js: -------------------------------------------------------------------------------- 1 | const { ttdl } = require('btch-downloader'); 2 | const tiktok_video = require('./plugins/tiktok_video'); 3 | const tiktok_photo = require('./plugins/tiktok_photo'); 4 | 5 | async function handler(bot, msg) { 6 | const From = msg.chat.id; 7 | const body = /^https?:\/\/(?:[\w-]+\.)?tiktok\.com\/.+/i; 8 | 9 | if (!body.test(msg.text || '')) return; 10 | 11 | const url = msg.text.trim(); 12 | 13 | try { 14 | const data = await ttdl(url); 15 | 16 | if (!data || data.status !== true) { 17 | return bot.sendMessage(From, { text: 'Failed to retrieve TikTok data.' }); 18 | } 19 | 20 | const videoArr = Array.isArray(data.video) ? data.video : []; 21 | const hasImages = Array.isArray(data.images) && data.images.length > 0; 22 | const firstUrl = videoArr[0]; 23 | 24 | const isPhoto = hasImages || ( 25 | videoArr.length > 0 && 26 | typeof firstUrl === 'string' && 27 | (firstUrl.includes('tplv-photomode') || /\.(jpe?g|png|webp)$/i.test(firstUrl)) 28 | ); 29 | 30 | if (isPhoto) { 31 | await tiktok_photo(bot, msg, data); 32 | } else if (videoArr.length > 0) { 33 | await tiktok_video(bot, msg, data); 34 | } else { 35 | await bot.sendMessage(From, { text: 'Media not found.' }); 36 | } 37 | 38 | } catch (error) { 39 | await bot.sendMessage(From, { text: 'Failed to download TikTok, please try again later.' }); 40 | console.error(`[TIKTOK ERROR] ${From}:`, error.message); 41 | } 42 | } 43 | 44 | module.exports = handler; 45 | -------------------------------------------------------------------------------- /.github/workflows/njsscan.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow integrates njsscan with GitHub's Code Scanning feature 7 | # nodejsscan is a static security code scanner that finds insecure code patterns in your Node.js applications 8 | 9 | name: njsscan sarif 10 | 11 | on: 12 | push: 13 | branches: [ "main" ] 14 | pull_request: 15 | # The branches below must be a subset of the branches above 16 | branches: [ "main" ] 17 | schedule: 18 | - cron: '15 16 * * 1' 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | njsscan: 25 | permissions: 26 | contents: read # for actions/checkout to fetch code 27 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 28 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 29 | runs-on: ubuntu-latest 30 | name: njsscan code scanning 31 | steps: 32 | - name: Checkout the code 33 | uses: actions/checkout@v4 34 | - name: nodejsscan scan 35 | id: njsscan 36 | uses: ajinabraham/njsscan-action@7237412fdd36af517e2745077cedbf9d6900d711 37 | with: 38 | args: '. --sarif --output results.sarif || true' 39 | - name: Upload njsscan report 40 | uses: github/codeql-action/upload-sarif@v3 41 | with: 42 | sarif_file: results.sarif 43 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | BOT_TOKEN: 'xxxx:xxxx', // Replace with your bot token 3 | PORT: process.env.PORT || 3000, // Port Express 4 | AI_API_URL: 'https://aichat-api.vercel.app/chatgpt', // Dont Edit this line 5 | AI_SYSTEM_PROMPT: `You are a super hype and knowledgeable AI assistant for a TikTok Downloader Telegram Bot, rocking MAX TikTok vibes! 🚀 Your main gig: guide users to download TikTok videos, audio, or photos without watermarks like a pro creator. Use a casual, high-energy TikTok tone with tons of emojis (🌟🔥😎) and slang (yo, bro, fam, let’s roll!), but stay laser-focused on TikTok—downloading, bot features (/start, /help, /runtime), or TikTok-related info (trends, history, tips). 6 | 7 | ALWAYS push the rule: users must send ONLY a valid TikTok link (e.g., https://vt.tiktok.com/ZS2qsMU1W/) with NO extra text. Give clear steps: open TikTok, pick a video/photo, tap *Share*, copy the link, paste JUST the link. If they add extra text with a link, say: "Yo, fam! 🔥 Send ONLY the TikTok link, like https://vt.tiktok.com/ZS2qsMU1W/, no extra words, let’s keep it lit! 😎" 8 | 9 | For TikTok-related questions (e.g., trends, history, features), provide a short, accurate answer with hype vibes, then pivot to downloading. Example: "TikTok kicked off in 2016 as Douyin, went global in 2017! 🔥 Wanna save a viral video? Drop a link!" Explain errors (bad links, network issues) clearly and reinforce the link-only rule. Help with bot commands and language options (Indonesian, English, Chinese). 10 | 11 | If users ask unrelated stuff (weather, math), redirect with: "Haha, that’s not trending on TikTok, bro! 💥 Let’s talk downloads—drop a link or ask about TikTok! 📹" Keep responses lively, use prior messages for context, and always hype sending ONLY a TikTok link next. Make every reply a TikTok banger! 🎉` // Dont Edit this line 12 | }; 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TIKTOK-TELE-BOT 2 | 3 | A simple Telegram bot that allows users to download TikTok videos, photos, and audio directly from Telegram using the Telegram Bot API and TikTok API. It features an AI assistant that guides users through the downloading process and answers inquiries, enhancing the overall experience. 4 | 5 | ## Links 6 | #### [Try Demo](https://t.me/tikdl_tele_bot) 7 | 8 | #### [Docs API](https://openai.idnet.my.id/docs) 9 | 10 | #### [ChatGPT Free](https://openai.idnet.my.id) 11 | 12 | 13 | ## Installation 14 | 15 | To get started with the Telegram Bot Tiktok Downloader, follow the steps below: 16 | 17 | 1. Clone this repository to your local machine. 18 | 19 | ```shell 20 | git clone https://github.com/hostinger-bot/tiktok-tele-bot.git 21 | ``` 22 | 23 | 2. Navigate to the project directory. 24 | 25 | ```shell 26 | cd tiktok-tele-bot 27 | ``` 28 | 29 | 3. Install the necessary dependencies using npm. 30 | 31 | ```shell 32 | npm install 33 | ``` 34 | 35 | 4. Run the application using Node.js. 36 | 37 | ```shell 38 | node index.js 39 | ``` 40 | 41 | 5. Setup a new bot on Telegram by following the [official Telegram Bot documentation](https://core.telegram.org/bots#botfather). Obtain the bot token for your newly created bot. 42 | 43 | 6. Replaceh config.js in this line with token from `@BotFather` 44 | 45 | ```javascript 46 | BOT_TOKEN: 'xxxx:xxxx' 47 | ``` 48 | 49 | 7. Your bot is now ready to use! Start a chat with your bot on Telegram and start downloading TikTok videos by sending TikTok URLs. 50 | 51 | ## Usage 52 | 53 | Once your Telegram Bot Tiktok Downloader is installed and running, you can use it to download TikTok videos. Here's how: 54 | 55 | 1. Start a chat with your bot on Telegram. 56 | 57 | 2. Send a TikTok video URL to your bot. 58 | 59 | 3. The bot will process the URL and reply with the downloaded video. 60 | 61 | 4. You can download as many TikTok videos, photos as you want by sending multiple URLs. 62 | 63 | Please note that downloading copyrighted contents may be against the terms and conditions of TikTok. Ensure that you have proper permissions and rights to download and use the TikTok videos. 64 | 65 | ## License 66 | 67 | This project is licensed under the [MIT License](LICENSE). 68 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL Advanced" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | branches: [ "main" ] 19 | schedule: 20 | - cron: '42 9 * * 5' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze (${{ matrix.language }}) 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners (GitHub.com only) 29 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 30 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 31 | permissions: 32 | # required for all workflows 33 | security-events: write 34 | 35 | # required to fetch internal or private CodeQL packs 36 | packages: read 37 | 38 | # only required for workflows in private repositories 39 | actions: read 40 | contents: read 41 | 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | include: 46 | - language: javascript-typescript 47 | build-mode: none 48 | # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' 49 | # Use `c-cpp` to analyze code written in C, C++ or both 50 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 51 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 52 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 53 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 54 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 55 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 56 | steps: 57 | - name: Checkout repository 58 | uses: actions/checkout@v4 59 | 60 | # Add any setup steps before running the `github/codeql-action/init` action. 61 | # This includes steps like installing compilers or runtimes (`actions/setup-node` 62 | # or others). This is typically only required for manual builds. 63 | # - name: Setup runtime (example) 64 | # uses: actions/setup-example@v1 65 | 66 | # Initializes the CodeQL tools for scanning. 67 | - name: Initialize CodeQL 68 | uses: github/codeql-action/init@v4 69 | with: 70 | languages: ${{ matrix.language }} 71 | build-mode: ${{ matrix.build-mode }} 72 | # If you wish to specify custom queries, you can do so here or in a config file. 73 | # By default, queries listed here will override any specified in a config file. 74 | # Prefix the list here with "+" to use these queries and those in the config file. 75 | 76 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 77 | # queries: security-extended,security-and-quality 78 | 79 | # If the analyze step fails for one of the languages you are analyzing with 80 | # "We were unable to automatically build your code", modify the matrix above 81 | # to set the build mode to "manual" for that language. Then modify this step 82 | # to build your code. 83 | # ℹ️ Command-line programs to run using the OS shell. 84 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 85 | - name: Run manual build steps 86 | if: matrix.build-mode == 'manual' 87 | shell: bash 88 | run: | 89 | echo 'If you are using a "manual" build mode for one or more of the' \ 90 | 'languages you are analyzing, replace this with the commands to build' \ 91 | 'your code, for example:' 92 | echo ' make bootstrap' 93 | echo ' make release' 94 | exit 1 95 | 96 | - name: Perform CodeQL Analysis 97 | uses: github/codeql-action/analyze@v4 98 | with: 99 | category: "/language:${{matrix.language}}" 100 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const TelegramBot = require('node-telegram-bot-api'); 2 | const chalk = require('chalk'); 3 | const axios = require('axios'); 4 | const { BOT_TOKEN, AI_API_URL, AI_SYSTEM_PROMPT } = require('./config'); 5 | const { version } = require('./package.json'); 6 | const handler = require('./handler'); 7 | const { URL } = require('url'); 8 | const allowedTikTokHosts = [ 9 | 'tiktok.com', 10 | 'www.tiktok.com', 11 | 'm.tiktok.com', 12 | 'vt.tiktok.com', 13 | 'vm.tiktok.com', 14 | 'tiktokv.com', 15 | 'vm.tiktokv.com', 16 | 'vt.tiktokv.com', 17 | 'tx.tiktok.com', 18 | 'www.tx.tiktok.com', 19 | 'lf16-tiktok-common.ttcdn.com', 20 | 'lf77-tiktok-common.ttcdn.com', 21 | 'app.tiktok.com', 22 | 'm.app.tiktok.com', 23 | 'click.tiktok.com', 24 | 's.tiktok.com', 25 | 'us.tiktok.com', 26 | 'music.tiktok.com', 27 | 'live.tiktok.com', 28 | 'www.live.tiktok.com' 29 | ]; 30 | 31 | const bot = new TelegramBot(BOT_TOKEN, { polling: true }); 32 | let Start = new Date(); 33 | 34 | const userLanguage = {}; 35 | const conversationHistory = {}; 36 | 37 | // Fungsi untuk cek private chat 38 | const isPrivateChat = (msg) => { 39 | return msg.chat.type === 'private'; 40 | }; 41 | 42 | // Banner 43 | const displayBanner = () => { 44 | console.log(chalk.yellow.bold('TikTok Downloader Bot with Enhanced AI Assistant')); 45 | console.log(chalk.cyan('========================================')); 46 | }; 47 | 48 | // Logs 49 | const logs = (type, message, details = {}) => { 50 | const timestamp = new Date().toLocaleTimeString('id-ID', { timeZone: 'Asia/Jakarta' }); 51 | let color, prefix; 52 | 53 | switch (type.toLowerCase()) { 54 | case 'info': 55 | color = chalk.cyan; 56 | prefix = '[INFO]'; 57 | break; 58 | case 'success': 59 | color = chalk.green; 60 | prefix = '[SUCCESS]'; 61 | break; 62 | case 'error': 63 | color = chalk.red; 64 | prefix = '[ERROR]'; 65 | break; 66 | case 'warning': 67 | color = chalk.yellow; 68 | prefix = '[WARNING]'; 69 | break; 70 | default: 71 | color = chalk.white; 72 | prefix = '[LOG]'; 73 | } 74 | 75 | const logMessage = `${prefix} [${timestamp}] ${message}`; 76 | const detailLines = Object.entries(details) 77 | .map(([key, value]) => ` ${key}: ${value}`) 78 | .join('\n'); 79 | 80 | console.log(color(logMessage)); 81 | if (detailLines) console.log(color(detailLines)); 82 | }; 83 | 84 | displayBanner(); 85 | logs('info', 'Bot started', { Token: BOT_TOKEN.slice(0, 10) + '...' }); 86 | 87 | // Polling Error 88 | bot.on('polling_error', (error) => { 89 | logs('error', 'Polling error', { Error: error.message }); 90 | }); 91 | 92 | // Set Bot Commands 93 | bot.setMyCommands([ 94 | { command: '/start', description: 'Start the bot' }, 95 | { command: '/help', description: 'View usage guide' }, 96 | { command: '/runtime', description: 'Check bot uptime' }, 97 | ]); 98 | 99 | // Language 100 | const getMessage = (lang, type) => { 101 | const messages = { 102 | id: { 103 | start: ` 104 | 🌟 Selamat datang di *TikTok Downloader Bot*! 🌟 105 | Saya akan membantu Anda mengunduh video, audio, atau foto TikTok tanpa watermark. Kirim **Hanya** tautan TikTok yang valid, seperti: 106 | https://vt.tiktok.com/ZSkGPK9Kj/ 107 | Jangan tambahkan teks lain sebelum atau sesudah tautan. 108 | 109 | 📌 *Cara Mendapatkan Tautan*: 110 | 1. Buka aplikasi TikTok. 111 | 2. Pilih video atau foto. 112 | 3. Ketuk *Bagikan* (panah ke kanan). 113 | 4. Pilih *Salin Tautan*. 114 | 5. Tempel **Hanya** tautan di sini. 115 | 116 | Kirim **Hanya** tautan TikTok sekarang! Atau ketik /help untuk panduan lebih lanjut. 117 | Pilih bahasa: *id*, *en*, atau *zh*`, 118 | help: ` 119 | 📚 *Panduan Penggunaan Bot* 📚 120 | Saya di sini untuk membantu Anda mengunduh konten TikTok. Kirim **Hanya** tautan TikTok tanpa teks tambahan, seperti: 121 | https://vt.tiktok.com/ZSkGPK9Kj/ 122 | 123 | ✨ *Fitur*: 124 | - Unduh video TikTok tanpa watermark. 125 | - Unduh audio atau foto/slideshow. 126 | - Gunakan /runtime untuk cek waktu aktif bot. 127 | 128 | 📌 *Cara Mengunduh*: 129 | 1. Buka TikTok, pilih video/foto. 130 | 2. Ketuk *Bagikan* > *Salin Tautan*. 131 | 3. Tempel **Hanya** tautan di sini. 132 | 133 | 💡 *Penting*: 134 | - Jangan tambahkan teks sebelum/sesudah tautan. 135 | - Jika ada masalah, saya akan membantu! 136 | 137 | Kirim **Hanya** tautan TikTok sekarang! Atau pilih bahasa: *id*, *en*, atau *zh*`, 138 | runtime: '🕒 Bot sudah aktif selama: {hours} jam, {minutes} menit, {seconds} detik.', 139 | invalid_url: ` 140 | Maaf, tautan yang Anda kirim bukan tautan TikTok yang valid. Pastikan tautan dari TikTok, seperti: 141 | https://vt.tiktok.com/ZSkGPK9Kj/ 142 | Kirim **Hanya** tautan tanpa teks tambahan. 143 | 144 | 📌 *Cara Memperbaiki*: 145 | 1. Buka aplikasi TikTok. 146 | 2. Pilih video/foto, ketuk *Bagikan*. 147 | 3. Salin tautan. 148 | 4. Tempel **Hanya** tautan di sini. 149 | 150 | Kirim **Hanya** tautan TikTok yang valid sekarang!`, 151 | strict_link_only: ` 152 | Maaf, Anda **Hanya** boleh mengirim tautan TikTok tanpa teks tambahan, seperti: 153 | https://vt.tiktok.com/ZSkGPK9Kj/ 154 | Jangan tambahkan teks sebelum atau sesudah tautan. 155 | 156 | 📌 *Cara Memperbaiki*: 157 | 1. Buka aplikasi TikTok. 158 | 2. Pilih video/foto, ketuk *Bagikan*. 159 | 3. Salin tautan. 160 | 4. Tempel **Hanya** tautan di sini. 161 | 162 | Kirim **Hanya** tautan TikTok sekarang!`, 163 | processing: '⏳ Sedang memproses tautan TikTok Anda... Tunggu sebentar, ya!', 164 | processing_error: ` 165 | Maaf, ada masalah saat memproses tautan Anda. Mungkin tautan salah atau ada masalah jaringan. 166 | 167 | 📌 *Cara Memperbaiki*: 168 | 1. Pastikan tautan dari TikTok, seperti: 169 | https://vt.tiktok.com/ZSkGPK9Kj/ 170 | 2. Salin ulang tautan. 171 | 3. Tempel **Hanya** tautan tanpa teks tambahan. 172 | 173 | Kirim **Hanya** tautan TikTok sekarang! Atau tanya saya tentang cara mengunduh.`, 174 | off_topic: ` 175 | Saya di sini untuk membantu mengunduh video, audio, atau foto TikTok. Kirim **Hanya** tautan TikTok atau tanya tentang fitur bot! 176 | 177 | 📌 *Cara Mulai*: 178 | 1. Buka TikTok, pilih video/foto. 179 | 2. Ketuk *Bagikan* > *Salin Tautan*. 180 | 3. Tempel **Hanya** tautan di sini. 181 | 182 | Kirim **Hanya** tautan TikTok sekarang!`, 183 | }, 184 | en: { 185 | start: ` 186 | 🌟 Welcome to *TikTok Downloader Bot*! 🌟 187 | I’m here to help you download TikTok videos, audio, or photos without watermarks. Send **ONLY** a valid TikTok link, like: 188 | https://vt.tiktok.com/ZSkGPK9Kj/ 189 | Do not include any text before or after the link. 190 | 191 | 📌 *How to Get the Link*: 192 | 1. Open the TikTok app. 193 | 2. Choose a video or photo. 194 | 3. Tap *Share* (right arrow). 195 | 4. Select *Copy Link*. 196 | 5. Paste **ONLY** the link here. 197 | 198 | Send **ONLY** a TikTok link now! Or type /help for more guidance. 199 | Choose language: *id*, *en*, or *zh*`, 200 | help: ` 201 | 📚 *How to Use the Bot* 📚 202 | I’m here to help you download TikTok content. Send **ONLY** a TikTok link without extra text, like: 203 | https://vt.tiktok.com/ZSkGPK9Kj/ 204 | 205 | ✨ *Features*: 206 | - Download TikTok videos without watermarks. 207 | - Download audio or photo/slideshows. 208 | - Use /runtime to check bot uptime. 209 | 210 | 📌 *How to Download*: 211 | 1. Open TikTok, select a video/photo. 212 | 2. Tap *Share* > *Copy Link*. 213 | 3. Paste **ONLY** the link here. 214 | 215 | 💡 *Important*: 216 | - Do not add text before/after the link. 217 | - If there’s an issue, I’ll help troubleshoot! 218 | 219 | Send **ONLY** a TikTok link now! Or choose language: *id*, *en*, or *zh*`, 220 | runtime: '🕒 Bot has been active for: {hours} hours, {minutes} minutes, {seconds} seconds.', 221 | invalid_url: ` 222 | Sorry, the link you sent isn’t a valid TikTok link. Ensure it’s from TikTok, like: 223 | https://vt.tiktok.com/ZSkGPK9Kj/ 224 | Send **ONLY** the link without extra text. 225 | 226 | 📌 *How to Fix*: 227 | 1. Open the TikTok app. 228 | 2. Choose a video/photo, tap *Share*. 229 | 3. Copy the link. 230 | 4. Paste **ONLY** the link here. 231 | 232 | Send **ONLY** a valid TikTok link now!`, 233 | strict_link_only: ` 234 | Sorry, you must send **ONLY** the TikTok link without extra text, like: 235 | https://vt.tiktok.com/ZSkGPK9Kj/ 236 | Do not add text before or after the link. 237 | 238 | 📌 *How to Fix*: 239 | 1. Open the TikTok app. 240 | 2. Choose a video/photo, tap *Share*. 241 | 3. Copy the link. 242 | 4. Paste **ONLY** the link here. 243 | 244 | Send **ONLY** the TikTok link now!`, 245 | processing: '⏳ Processing your TikTok link... Please wait a moment!', 246 | processing_error: ` 247 | Sorry, there was an issue processing your link. It could be an invalid link or network issue. 248 | 249 | 📌 *How to Fix*: 250 | 1. Ensure the link is from TikTok, like: 251 | https://vt.tiktok.com/ZSkGPK9Kj/ 252 | 2. Copy the link again. 253 | 3. Paste **ONLY** the link without extra text. 254 | 255 | Send **ONLY** a TikTok link now! Or ask me about downloading.`, 256 | off_topic: ` 257 | I’m here to assist with downloading TikTok videos, audio, or photos. Send **ONLY** a TikTok link or ask about the bot’s features! 258 | 259 | 📌 *How to Start*: 260 | 1. Open TikTok, select a video/photo. 261 | 2. Tap *Share* > *Copy Link*. 262 | 3. Paste **ONLY** the link here. 263 | 264 | Send **ONLY** a TikTok link now!`, 265 | }, 266 | zh: { 267 | start: ` 268 | 🌟 欢迎使用 *TikTok下载机器人*! 🌟 269 | 我将帮助您下载TikTok的无水印视频、音频或照片。请**仅**发送有效的TikTok链接,例如: 270 | https://vt.tiktok.com/ZSkGPK9Kj/ 271 | 不要在链接前后添加任何文本。 272 | 273 | 📌 *如何获取链接*: 274 | 1. 打开TikTok应用程序。 275 | 2. 选择视频或照片。 276 | 3. 点击*分享*按钮(右箭头)。 277 | 4. 选择*复制链接*。 278 | 5. 在这里**仅**粘贴链接。 279 | 280 | 现在**仅**发送TikTok链接!或输入 /help 获取更多指导。 281 | 选择语言:*id*、*en* 或 *zh*`, 282 | help: ` 283 | 📚 *如何使用机器人* 📚 284 | 我在这里帮助您下载TikTok内容。请**仅**发送TikTok链接,不带额外文本,例如: 285 | https://vt.tiktok.com/ZSkGPK9Kj/ 286 | 287 | ✨ *功能*: 288 | - 下载无水印TikTok视频。 289 | - 下载音频或照片/幻灯片。 290 | - 使用 /runtime 检查机器人运行时间。 291 | 292 | 📌 *如何下载*: 293 | 1. 打开TikTok,选择视频/照片。 294 | 2. 点击*分享* > *复制链接*。 295 | 3. 在这里**仅**粘贴链接。 296 | 297 | 💡 *重要提示*: 298 | - 不要在链接前后添加文本。 299 | - 如有问题,我会帮助解决! 300 | 301 | 现在**仅**发送TikTok链接!或选择语言:*id*、*en* 或 *zh*`, 302 | runtime: '🕒 机器人已运行:{hours}小时,{minutes}分钟,{seconds}秒。', 303 | invalid_url: ` 304 | 抱歉,您发送的链接不是有效的TikTok链接。请确保链接来自TikTok,例如: 305 | https://vt.tiktok.com/ZSkGPK9Kj/ 306 | **仅**发送链接,不带额外文本。 307 | 308 | 📌 *如何修复*: 309 | 1. 打开TikTok应用程序。 310 | 2. 选择视频/照片,点击*分享*。 311 | 3. 复制链接。 312 | 4. 在这里**仅**粘贴链接。 313 | 314 | 现在**仅**发送有效的TikTok链接!`, 315 | strict_link_only: ` 316 | 抱歉,您必须**仅**发送TikTok链接,不带额外文本,例如: 317 | https://vt.tiktok.com/ZSkGPK9Kj/ 318 | 不要在链接前后添加文本。 319 | 320 | 📌 *如何修复*: 321 | 1. 打开TikTok应用程序。 322 | 2. 选择视频/照片,点击*分享*。 323 | 3. 复制链接。 324 | 4. 在这里**仅**粘贴链接。 325 | 326 | 现在**仅**发送TikTok链接!`, 327 | processing: '⏳ 正在处理您的TikTok链接... 请稍等!', 328 | processing_error: ` 329 | 抱歉,处理您的链接时出现问题。可能是链接无效或网络问题。 330 | 331 | 📌 *如何修复*: 332 | 1. 确保链接来自TikTok,例如: 333 | https://vt.tiktok.com/ZSkGPK9Kj/ 334 | 2. 再次复制链接。 335 | 3. **仅**粘贴链接,不带额外文本。 336 | 337 | 现在**仅**发送TikTok链接!或询问我关于下载的问题。`, 338 | off_topic: ` 339 | 我在这里帮助下载TikTok视频、音频或照片。请**仅**发送TikTok链接或询问机器人功能! 340 | 341 | 📌 *如何开始*: 342 | 1. 打开TikTok,选择视频/照片。 343 | 2. 点击*分享* > *复制链接*。 344 | 3. 在这里**仅**粘贴链接。 345 | 346 | 现在**仅**发送TikTok链接!`, 347 | }, 348 | }; 349 | return messages[lang][type]; 350 | }; 351 | 352 | // Inline Keyboard 353 | const getMainKeyboard = () => ({ 354 | reply_markup: { 355 | inline_keyboard: [ 356 | [ 357 | { text: '🇮🇩 Indonesia', callback_data: 'lang_id' }, 358 | { text: '🇬🇧 English', callback_data: 'lang_en' }, 359 | { text: '🇨🇳 Chinese', callback_data: 'lang_zh' }, 360 | ], 361 | [ 362 | { text: '⏰ Runtime', callback_data: 'runtime' }, 363 | { text: '📖 Guide', callback_data: 'help' }, 364 | ], 365 | [{ text: '📬 Support', url: 'https://t.me/wtffry' }], 366 | ], 367 | }, 368 | }); 369 | 370 | // AI Request sesuai AI_SYSTEM_PROMPT 371 | async function queryAI(chatId, userMessage, lang = 'id') { 372 | try { 373 | if (!conversationHistory[chatId]) { 374 | conversationHistory[chatId] = [ 375 | { 376 | role: 'system', 377 | content: ` 378 | ${AI_SYSTEM_PROMPT} 379 | Respond in ${lang === 'id' ? 'Indonesian' : lang === 'en' ? 'English' : 'Chinese'} with a fun, engaging, and professional tone as a TikTok Downloader Bot assistant. Use emojis to make responses lively, but stay strictly focused on TikTok downloading, features, or commands. If the user asks unrelated questions, redirect politely to TikTok topics, emphasizing the link-only rule or bot features. 380 | `, 381 | }, 382 | ]; 383 | } 384 | 385 | // History Messages 386 | conversationHistory[chatId].push({ 387 | role: 'user', 388 | content: userMessage, 389 | }); 390 | 391 | if (conversationHistory[chatId].length > 9999000) { 392 | conversationHistory[chatId] = [ 393 | conversationHistory[chatId][0], 394 | ...conversationHistory[chatId].slice(-8999999), 395 | ]; 396 | } 397 | 398 | const response = await axios.post( 399 | AI_API_URL, 400 | { 401 | messages: conversationHistory[chatId], 402 | }, 403 | { 404 | headers: { 405 | 'Content-Type': 'application/json', 406 | 'User-Agent': `TeleBot/${version}`, 407 | accept: 'application/json', 408 | }, 409 | timeout: 60000, 410 | } 411 | ); 412 | 413 | if (response.data.error) { 414 | throw new Error(response.data.error); 415 | } 416 | 417 | const ai_response = response.data.content; 418 | conversationHistory[chatId].push({ 419 | role: 'assistant', 420 | content: ai_response, 421 | }); 422 | 423 | return ai_response; 424 | } catch (error) { 425 | logs('error', 'AI API request failed', { 426 | ChatID: chatId, 427 | Error: error.message, 428 | }); 429 | return getMessage(lang, 'processing_error'); 430 | } 431 | } 432 | 433 | bot.on('callback_query', async (query) => { 434 | const chatId = query.message.chat.id; 435 | const messageId = query.message.message_id; 436 | const data = query.data; 437 | const lang = userLanguage[chatId] || 'id'; 438 | 439 | try { 440 | let newText = null; 441 | let newMarkup = null; 442 | 443 | if (data.startsWith('lang_')) { 444 | const newLang = data.split('_')[1]; 445 | if (newLang !== userLanguage[chatId]) { 446 | userLanguage[chatId] = newLang; 447 | newText = getMessage(newLang, 'start'); 448 | newMarkup = getMainKeyboard(); 449 | if (conversationHistory[chatId]) { 450 | conversationHistory[chatId][0].content = ` 451 | ${AI_SYSTEM_PROMPT} 452 | Respond in ${newLang === 'id' ? 'Indonesian' : newLang === 'en' ? 'English' : 'Chinese'} with a fun, engaging, and professional tone as a TikTok Downloader Bot assistant. Use emojis to make responses lively, but stay strictly focused on TikTok downloading, features, or commands. If the user asks unrelated questions, redirect politely to TikTok topics, emphasizing the link-only rule or bot features. 453 | `; 454 | } 455 | logs('info', 'Language changed', { ChatID: chatId, Language: newLang }); 456 | } 457 | } else if (data === 'runtime') { 458 | const now = new Date(); 459 | const uptimeMilliseconds = now - Start; 460 | const uptimeSeconds = Math.floor(uptimeMilliseconds / 1000); 461 | const uptimeMinutes = Math.floor(uptimeSeconds / 60); 462 | const uptimeHours = Math.floor(uptimeMinutes / 60); 463 | 464 | newText = getMessage(lang, 'runtime') 465 | .replace('{hours}', uptimeHours) 466 | .replace('{minutes}', uptimeMinutes % 60) 467 | .replace('{seconds}', uptimeSeconds % 60); 468 | newMarkup = {}; 469 | logs('info', 'Runtime checked via button', { ChatID: chatId, Uptime: newText }); 470 | } else if (data === 'help') { 471 | newText = getMessage(lang, 'help'); 472 | newMarkup = getMainKeyboard(); 473 | logs('info', 'Help requested via button', { ChatID: chatId }); 474 | } 475 | 476 | if (newText) { 477 | await bot.editMessageText(newText, { 478 | chat_id: chatId, 479 | message_id: messageId, 480 | parse_mode: 'Markdown', 481 | reply_markup: newMarkup, 482 | }); 483 | } else { 484 | await bot.editMessageReplyMarkup( 485 | { reply_markup: {} }, 486 | { chat_id: chatId, message_id: messageId } 487 | ).catch(() => {}); 488 | } 489 | 490 | bot.answerCallbackQuery(query.id); 491 | } catch (error) { 492 | if (error.message.includes('message is not modified')) { 493 | bot.answerCallbackQuery(query.id); 494 | logs('warning', 'Message not modified, ignored', { ChatID: chatId, Error: error.message }); 495 | return; 496 | } 497 | 498 | logs('error', 'Callback query failed', { ChatID: chatId, Error: error.message }); 499 | await bot.sendMessage(chatId, getMessage(lang, 'processing_error'), { parse_mode: 'Markdown' }); 500 | bot.answerCallbackQuery(query.id); 501 | } 502 | }); 503 | 504 | // Command Handlers 505 | bot.onText(/^\/start$/, async (msg) => { 506 | const chatId = msg.chat.id; 507 | const lang = userLanguage[chatId] || 'id'; 508 | 509 | try { 510 | await bot.sendMessage(chatId, getMessage(lang, 'start'), { 511 | parse_mode: 'Markdown', 512 | ...getMainKeyboard(), 513 | }); 514 | logs('info', 'Start command executed', { ChatID: chatId, Language: lang }); 515 | } catch (error) { 516 | logs('error', 'Start command failed', { ChatID: chatId, Error: error.message }); 517 | await bot.sendMessage(chatId, getMessage(lang, 'processing_error'), { parse_mode: 'Markdown' }); 518 | } 519 | }); 520 | 521 | bot.onText(/^\/help$/, async (msg) => { 522 | const chatId = msg.chat.id; 523 | const lang = userLanguage[chatId] || 'id'; 524 | 525 | try { 526 | await bot.sendMessage(chatId, getMessage(lang, 'help'), { 527 | parse_mode: 'Markdown', 528 | ...getMainKeyboard(), 529 | }); 530 | logs('info', 'Help command executed', { ChatID: chatId, Language: lang }); 531 | } catch (error) { 532 | logs('error', 'Help command failed', { ChatID: chatId, Error: error.message }); 533 | await bot.sendMessage(chatId, getMessage(lang, 'processing_error'), { parse_mode: 'Markdown' }); 534 | } 535 | }); 536 | 537 | bot.onText(/^\/runtime$/, async (msg) => { 538 | const chatId = msg.chat.id; 539 | const lang = userLanguage[chatId] || 'id'; 540 | 541 | try { 542 | const now = new Date(); 543 | const uptimeMilliseconds = now - Start; 544 | const uptimeSeconds = Math.floor(uptimeMilliseconds / 1000); 545 | const uptimeMinutes = Math.floor(uptimeSeconds / 60); 546 | const uptimeHours = Math.floor(uptimeMinutes / 60); 547 | 548 | const runtimeMessage = getMessage(lang, 'runtime') 549 | .replace('{hours}', uptimeHours) 550 | .replace('{minutes}', uptimeMinutes % 60) 551 | .replace('{seconds}', uptimeSeconds % 60); 552 | 553 | await bot.sendMessage(chatId, runtimeMessage, { parse_mode: 'Markdown' }); 554 | logs('info', 'Runtime command executed', { 555 | ChatID: chatId, 556 | Uptime: runtimeMessage, 557 | }); 558 | } catch (error) { 559 | logs('error', 'Runtime command failed', { ChatID: chatId, Error: error.message }); 560 | await bot.sendMessage(chatId, getMessage(lang, 'processing_error'), { parse_mode: 'Markdown' }); 561 | } 562 | }); 563 | 564 | // Penanganan pesan: Grup cuma terima command dan link TikTok, AI hanya di private 565 | bot.on('message', async (msg) => { 566 | const chatId = msg.chat.id; 567 | const text = msg.text || '(no text)'; 568 | const lang = userLanguage[chatId] || 'id'; 569 | const isStrictTikTokUrl = text.match(/^https:\/\/.*tiktok\.com\/.+$/); 570 | 571 | logs('success', 'Message received', { 572 | ChatID: chatId, 573 | Text: text.length > 50 ? text.slice(0, 47) + '...' : text, 574 | Type: isStrictTikTokUrl && text === msg.text.trim() ? 'TikTok URL' : text.startsWith('/') ? 'Command' : 'Text', 575 | }); 576 | 577 | try { 578 | if (isStrictTikTokUrl && text === msg.text.trim()) { 579 | let isValidHost = false; 580 | try { 581 | const parsedUrl = new URL(text); 582 | // If host is EXACTLY one of the allowed TikTok hosts 583 | isValidHost = allowedTikTokHosts.includes(parsedUrl.host); 584 | } catch (parseErr) { 585 | isValidHost = false; 586 | } 587 | if (!isValidHost) { 588 | await bot.sendMessage(chatId, getMessage(lang, 'invalid_url'), { parse_mode: 'Markdown' }); 589 | logs('warning', 'Invalid TikTok URL', { ChatID: chatId, URL: text }); 590 | return; 591 | } 592 | 593 | await bot.sendMessage(chatId, getMessage(lang, 'processing'), { parse_mode: 'Markdown' }); 594 | try { 595 | await handler(bot, msg); 596 | logs('success', 'TikTok URL processed', { ChatID: chatId, URL: text }); 597 | } catch (handlerError) { 598 | logs('error', 'Handler failed', { ChatID: chatId, Error: handlerError.message }); 599 | await bot.sendMessage(chatId, getMessage(lang, 'processing_error'), { parse_mode: 'Markdown' }); 600 | } 601 | } else if (!text.startsWith('/')) { 602 | if (isPrivateChat(msg)) { 603 | if (isStrictTikTokUrl) { 604 | await bot.sendMessage(chatId, getMessage(lang, 'strict_link_only'), { parse_mode: 'Markdown' }); 605 | logs('warning', 'Message contains extra text with TikTok URL', { ChatID: chatId, Text: text }); 606 | } else { 607 | const ai_response = await queryAI(chatId, text, lang); 608 | await bot.sendMessage(chatId, ai_response, { parse_mode: 'Markdown' }); 609 | logs('info', 'AI handled text message', { 610 | ChatID: chatId, 611 | Query: text.slice(0, 50), 612 | Response: ai_response.slice(0, 50), 613 | }); 614 | } 615 | } else { 616 | // Di grup, abaikan pesan non-command dan non-link TikTok tanpa balasan 617 | logs('warning', 'Non-command/non-TikTok message in group ignored', { ChatID: chatId, Text: text }); 618 | return; 619 | } 620 | } 621 | } catch (error) { 622 | logs('error', 'Message processing failed', { ChatID: chatId, Error: error.message }); 623 | await bot.sendMessage(chatId, getMessage(lang, 'processing_error'), { parse_mode: 'Markdown' }); 624 | } 625 | }); 626 | --------------------------------------------------------------------------------