├── .env.example ├── .gitignore ├── README.md ├── load.py ├── main.py ├── models.py ├── requirements.txt ├── tiktok-downloader.sql ├── tiktok_downloader ├── __init__.py ├── get_content.py └── musicaldown.py └── utils ├── __init__.py └── get_video_detail.py /.env.example: -------------------------------------------------------------------------------- 1 | # paste token from father bot to token_bot vairable 2 | # example 3 | # token_bot = 0292828282:AGGxIO88Gc8F8TDsn2ZWWWE3kXupmjXevck 4 | # token bot create on botFather in telegram 5 | 6 | 7 | token_bot = "" 8 | 9 | # api_id and api_hash get from my.telegram.org 10 | 11 | api_id = 12 | api_hash = "" 13 | 14 | # configure database 15 | 16 | database_url = "localhost" 17 | database_port = 3306 18 | database_user = "" 19 | database_pass = "" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | */__pycache__/ 2 | .vscode/* 3 | .env 4 | *.mp4 5 | session/* 6 | *.html 7 | .log 8 | __pycache__/* 9 | env 10 | tes.py 11 | *.session 12 | *.session-journal 13 | venv 14 | *.js 15 | *.json 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tiktok Downloader 2 | 3 | Source code for Telegram bot which functions to download TikTok videos without watermark.
4 | This source code is not downloaded directly from TikTok but uses a third party. 5 | 6 | # How to run on your local machine 7 | 8 | 1. Install python 3.8+ 9 | You can download from official python website : https://python.org.
10 | If you use Linux, you can directly install Python with the command 11 | ```bash 12 | sudo apt install python3 python3-pip -y 13 | ``` 14 | 2. Clone or download this repository 15 | ```bash 16 | git clone https://github.com/akasakaid/TiktokDownloader.git 17 | ``` 18 | 3. Goto TiktokDownloader folder
19 | ```powershell 20 | cd TiktokDownloader 21 | ``` 22 | 4. Install required libraries
23 | ```bash 24 | python -m pip install -r requirements.txt 25 | ``` 26 | or 27 | ```bash 28 | python3 -m pip install -r requirements.txt 29 | ``` 30 | 5. Copy .env.example into .env 31 | ```powershell 32 | cp .env.example .env 33 | ``` 34 | or 35 | ``` 36 | copy .env.example .env 37 | ``` 38 | 6. Open .env in your code editor or something like that 39 | 7. Create Telegram token bot in Bot Father : https://t.me/BotFather 40 | 8. Copy and paste the token from bot father into .env 41 | ```env 42 | token_bot = "token_botxxxxxxxxx" 43 | ``` 44 | 9. Create Api Hash and Api Id. You can create from https://my.telegram.org 45 | 10. Copy and paste Api Hash and Api Id into .env file 46 | ```env 47 | api_id = "123123" 48 | api_hash = "qwerertyiuo" 49 | ``` 50 | 11. Finally, type the command below to run the program 51 | ```bash 52 | python main.py 53 | ``` 54 | or 55 | ```bash 56 | python3 main.py 57 | ``` 58 | **if you want to run program 24/7, you can to run this program in linux server with using screen program** 59 | 60 | Install screen 61 | ```bash 62 | sudo apt install screen -y 63 | ``` 64 | Create session 65 | ```bash 66 | screen -R 67 | example : screen -R tiktok 68 | ``` 69 | Run the program 70 | ```bash 71 | python3 main.py 72 | ``` 73 | Save the session using keyboard shortcut **ctrl + a + d** 74 | 75 | Done 76 | # Follow me 77 | * https://facebook.com/fawwazthoerif 78 | * https://instagram.com/fawatashi
79 | *You can contact me with social media in above* 80 | # Support me 81 | 82 | * (Indonesia) https://trakteer.id/fawwazthoerif 83 | * (Global/International) https://sociabuzz.com/fawwazthoerif/tribe 84 | * Bitcoin ```1HjMdpmiM8bUs2CtfjpXcjZjBBxNgBvhp4``` 85 | * Tron / USDT (Tron20) ```TZANkbi8z22cEpkpWfJ1F6H84r9DA19Es1``` -------------------------------------------------------------------------------- /load.py: -------------------------------------------------------------------------------- 1 | import os 2 | import databases 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | 7 | token_bot = os.environ.get("token_bot") 8 | api_id = os.environ.get("api_id") 9 | api_hash = os.environ.get("api_hash") 10 | database_name = "tiktok-downloader" 11 | database_url = os.getenv("database_url") 12 | database_port = os.getenv("database_port") 13 | database_user = os.getenv("database_user") 14 | database_pass = os.getenv("database_pass") 15 | 16 | DATABASE = f"mysql+aiomysql://{database_user}:{database_pass}@{database_url}:{database_port}/{database_name}" 17 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import utils 2 | import pathlib 3 | import pyrogram 4 | import databases 5 | import tiktok_downloader 6 | from load import * 7 | from models import users, videos 8 | from datetime import datetime, timezone 9 | 10 | cwd = pathlib.Path(__file__).parent 11 | 12 | bot = pyrogram.Client( 13 | name="tiktok-bot", api_id=api_id, api_hash=api_hash, bot_token=token_bot 14 | ) 15 | 16 | 17 | async def start_handler(client: pyrogram.Client, message: pyrogram.types.Message): 18 | first_name = message.chat.first_name 19 | username = message.chat.username 20 | last_name = message.chat.last_name 21 | userid = message.chat.id 22 | text = message.text 23 | print(f"{userid} {first_name} - {text}") 24 | query = "SELECT * FROM users WHERE user_id = :userid" 25 | values = {"userid": userid} 26 | async with databases.Database(DATABASE) as database: 27 | result = await database.fetch_one(query=query, values=values) 28 | if result is None: 29 | query = users.insert() 30 | values = { 31 | "user_id": userid, 32 | "first_name": first_name, 33 | "last_name": last_name, 34 | "username": username, 35 | "created_at": datetime.now(tz=timezone.utc) 36 | .now() 37 | .isoformat() 38 | .split(".")[0], 39 | } 40 | await database.execute(query=query, values=values) 41 | msgid = message.id 42 | retext = f"""Welcome {first_name} to Tiktok Video Downloader Bot 43 | 44 | How to use : 45 | 46 | ID : Cara menggunakan bot hanya dengan mengirimkan tautan dari video tiktok yang ingin kamu unduh. 47 | 48 | EN : How to use the bot by simply sending the link of the tiktok video you want to download. 49 | """ 50 | await client.send_message(chat_id=userid, text=retext, reply_to_message_id=msgid) 51 | return 52 | 53 | 54 | async def ping_handler(client: pyrogram.Client, message: pyrogram.types.Message): 55 | userid = message.chat.id 56 | first_name = message.chat.first_name or "" 57 | msg_id = message.id 58 | print(f"{userid} {first_name} - /ping") 59 | result = await client.send_message( 60 | chat_id=userid, text="Pong!", reply_to_message_id=msg_id 61 | ) 62 | await asyncio.sleep(2) 63 | await client.delete_messages(chat_id=userid, message_ids=msg_id) 64 | await client.delete_messages(chat_id=userid, message_ids=result.id) 65 | 66 | 67 | async def tiktok_handler(client: pyrogram.Client, message: pyrogram.types.Message): 68 | userid = message.chat.id 69 | first_name = message.chat.first_name or "" 70 | last_name = message.chat.last_name 71 | username = message.chat.username 72 | text = message.text 73 | msgid = message.id 74 | print(f"{userid} {first_name} - {text}") 75 | query = "SELECT * FROM users WHERE user_id = :userid" 76 | values = {"userid": userid} 77 | async with databases.Database(DATABASE) as database: 78 | result = await database.fetch_one(query=query, values=values) 79 | if result is None: 80 | query = users.insert() 81 | values = { 82 | "user_id": userid, 83 | "first_name": first_name, 84 | "last_name": last_name, 85 | "username": username, 86 | "created_at": datetime.now(tz=timezone.utc) 87 | .now() 88 | .isoformat() 89 | .split(".")[0], 90 | } 91 | await database.execute(query=query, values=values) 92 | tiktok_url = None 93 | if len(text.split("\n")) > 1: 94 | tiktok_url = text.split("\n")[0] 95 | if "tiktok" not in tiktok_url: 96 | retext = "The video link you sent may be wrong." 97 | await client.send_message( 98 | chat_id=userid, text=retext, reply_to_message_id=msgid 99 | ) 100 | return 101 | else: 102 | tiktok_url = text 103 | video_id, author_id, author_username, video_url, images, cookies = ( 104 | await utils.get_video_detail(tiktok_url) 105 | ) 106 | print( 107 | f"video id : {video_id}, author id : {author_id}, username : {author_username}" 108 | ) 109 | print(f"video url : {video_url}") 110 | if video_id is None: 111 | retext = "The tiktok video you want to download doesn't exist, it might be deleted or a private video." 112 | await client.send_message( 113 | chat_id=userid, text=retext, reply_to_message_id=msgid 114 | ) 115 | return 116 | link_length = len(tiktok_url) 117 | source_link = f"[Video Source]({tiktok_url})" 118 | retext = f"Successfully download the video\n" 119 | if link_length > 40: 120 | retext += f"\n{source_link}\n" 121 | retext += "\nPowered by @TiktokVideoDownloaderIDBot" 122 | keylist = [ 123 | [ 124 | pyrogram.types.InlineKeyboardButton(text="Source Video", url=tiktok_url), 125 | ], 126 | [ 127 | pyrogram.types.InlineKeyboardButton( 128 | text="Follow Me", url="https://t.me/fawwazthoerif" 129 | ), 130 | pyrogram.types.InlineKeyboardButton( 131 | text="Donation", callback_data="donation" 132 | ), 133 | ], 134 | ] 135 | if link_length > 40: 136 | keylist.pop(0) 137 | rekey = pyrogram.types.InlineKeyboardMarkup(inline_keyboard=keylist) 138 | async with databases.Database(DATABASE) as database: 139 | query = ( 140 | "SELECT * FROM videos WHERE video_id = :video_id AND author_id = :author_id" 141 | ) 142 | values = { 143 | "video_id": video_id, 144 | "author_id": author_id, 145 | } 146 | result = await database.fetch_one(query=query, values=values) 147 | if result is not None: 148 | print("using cache database !") 149 | file_id = result.file_id 150 | file_unique_id = result.file_unique_id 151 | await client.delete_messages(chat_id=userid, message_ids=msgid) 152 | await client.send_cached_media( 153 | chat_id=userid, file_id=file_id, caption=retext, reply_markup=rekey 154 | ) 155 | return 156 | now = int(datetime.now(tz=timezone.utc).timestamp()) 157 | output = cwd.joinpath(f"{video_id}.mp4") 158 | if video_url is None or len(video_url) <= 0: 159 | print("try download with musicaldown !") 160 | result = await tiktok_downloader.musicaldown(url=tiktok_url, output=output) 161 | else: 162 | print("try download with main tiktok") 163 | result = await tiktok_downloader.get_content( 164 | url=video_url, output=output, cookies=cookies 165 | ) 166 | await client.delete_messages(chat_id=userid, message_ids=msgid) 167 | result = await client.send_video( 168 | chat_id=userid, video=output, caption=retext, reply_markup=rekey 169 | ) 170 | video = result.video 171 | animation = result.animation 172 | if video is not None: 173 | file_id = result.video.file_id 174 | file_unique_id = result.video.file_unique_id 175 | if animation is not None: 176 | file_id = result.animation.file_id 177 | file_unique_id = result.animation.file_unique_id 178 | async with databases.Database(DATABASE) as database: 179 | query = videos.insert() 180 | values = { 181 | "author_id": author_id, 182 | "author_username": author_username, 183 | "video_id": video_id, 184 | "file_id": file_id, 185 | "file_unique_id": file_unique_id, 186 | "created_at": datetime.now(tz=timezone.utc).now().isoformat().split(".")[0], 187 | } 188 | await database.execute(query=query, values=values) 189 | output.unlink(missing_ok=True) 190 | return 191 | 192 | 193 | async def donation_handler( 194 | client: pyrogram.Client, 195 | message: pyrogram.types.Message | pyrogram.types.CallbackQuery, 196 | ): 197 | if isinstance(message, pyrogram.types.Message): 198 | userid = message.chat.id 199 | first_name = message.chat.first_name 200 | text = message.text 201 | if isinstance(message, pyrogram.types.CallbackQuery): 202 | userid = message.from_user.id 203 | first_name = message.from_user.first_name 204 | text = message.data 205 | print(f"{userid} {first_name} - {text}") 206 | retext = """If you like my work, you can support me through the link below. 207 | 208 | International : https://sociabuzz.com/fawwazthoerif/tribe 209 | Indonesia : https://trakteer.id/fawwazthoerif/tip 210 | 211 | CRYPTO 212 | USDT (TON) : `UQDicJd7KwBcxzqbn6agUc_KVl8BklzyvuKGxEVG7xuhnTFt` 213 | """ 214 | await client.send_message( 215 | chat_id=userid, text=retext, disable_web_page_preview=True 216 | ) 217 | return 218 | 219 | 220 | async def main(): 221 | print(f"start bot !") 222 | await bot.start() 223 | me = await bot.get_me() 224 | botname = me.first_name 225 | botuname = me.username 226 | print(f"Bot name : {botname}") 227 | print(f"Bot username : {botuname}") 228 | bot.add_handler( 229 | handler=pyrogram.handlers.message_handler.MessageHandler( 230 | callback=start_handler, 231 | filters=pyrogram.filters.command(commands=["start"]), 232 | ) 233 | ) 234 | bot.add_handler( 235 | handler=pyrogram.handlers.message_handler.MessageHandler( 236 | callback=ping_handler, 237 | filters=pyrogram.filters.regex(r"ping"), 238 | ) 239 | ) 240 | bot.add_handler( 241 | handler=pyrogram.handlers.message_handler.MessageHandler( 242 | callback=tiktok_handler, 243 | filters=pyrogram.filters.regex(r"tiktok"), 244 | ) 245 | ) 246 | bot.add_handler( 247 | handler=pyrogram.handlers.message_handler.MessageHandler( 248 | callback=donation_handler, 249 | filters=pyrogram.filters.regex(r"donation"), 250 | ) 251 | ) 252 | bot.add_handler( 253 | handler=pyrogram.handlers.callback_query_handler.CallbackQueryHandler( 254 | callback=donation_handler, 255 | filters=pyrogram.filters.regex(r"donation"), 256 | ) 257 | ) 258 | await pyrogram.idle() 259 | await bot.stop() 260 | 261 | 262 | import asyncio 263 | 264 | bot.run(main()) 265 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy 2 | 3 | metadata = sqlalchemy.MetaData() 4 | 5 | users = sqlalchemy.Table( 6 | "users", 7 | metadata, 8 | sqlalchemy.Column( 9 | name="id", type_=sqlalchemy.BigInteger, primary_key=True, autoincrement=True 10 | ), 11 | sqlalchemy.Column( 12 | name="user_id", 13 | type_=sqlalchemy.BigInteger, 14 | unique=True, 15 | ), 16 | sqlalchemy.Column( 17 | name="first_name", type_=sqlalchemy.String(length=255), index=True 18 | ), 19 | sqlalchemy.Column( 20 | name="last_name", type_=sqlalchemy.String(length=255), index=True, nullable=True 21 | ), 22 | sqlalchemy.Column( 23 | name="username", type_=sqlalchemy.String(length=255), index=True, nullable=True 24 | ), 25 | sqlalchemy.Column(name="updated_at", type_=sqlalchemy.DateTime, nullable=True), 26 | sqlalchemy.Column(name="created_at", type_=sqlalchemy.DateTime), 27 | ) 28 | 29 | videos = sqlalchemy.Table( 30 | "videos", 31 | metadata, 32 | sqlalchemy.Column( 33 | name="id", type_=sqlalchemy.BigInteger, primary_key=True, autoincrement=True 34 | ), 35 | sqlalchemy.Column( 36 | name="author_id", type_=sqlalchemy.VARCHAR(length=255), index=True 37 | ), 38 | sqlalchemy.Column( 39 | name="author_username", type_=sqlalchemy.VARCHAR(length=255), index=True 40 | ), 41 | sqlalchemy.Column(name="video_id", type_=sqlalchemy.BigInteger, unique=True), 42 | sqlalchemy.Column(name="file_id", type_=sqlalchemy.String(length=255), index=True), 43 | sqlalchemy.Column( 44 | name="file_unique_id", type_=sqlalchemy.String(length=255), index=True 45 | ), 46 | sqlalchemy.Column(name="updated_at", type_=sqlalchemy.DateTime, nullable=True), 47 | sqlalchemy.Column(name="created_at", type_=sqlalchemy.DateTime), 48 | ) 49 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akasakaid/TiktokDownloader/6ae07656bbc491d705936b3c4942ef6ab5d0ac7b/requirements.txt -------------------------------------------------------------------------------- /tiktok-downloader.sql: -------------------------------------------------------------------------------- 1 | -- Adminer 4.8.1 MySQL 10.4.32-MariaDB dump 2 | 3 | SET NAMES utf8; 4 | SET time_zone = '+00:00'; 5 | SET foreign_key_checks = 0; 6 | SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; 7 | 8 | SET NAMES utf8mb4; 9 | 10 | DROP DATABASE IF EXISTS `tiktok-downloader`; 11 | CREATE DATABASE `tiktok-downloader` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */; 12 | USE `tiktok-downloader`; 13 | 14 | DROP TABLE IF EXISTS `users`; 15 | CREATE TABLE `users` ( 16 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 17 | `user_id` bigint(20) NOT NULL, 18 | `first_name` varchar(255) NOT NULL, 19 | `last_name` varchar(255) DEFAULT NULL, 20 | `username` varchar(255) DEFAULT NULL, 21 | `updated_at` datetime DEFAULT NULL, 22 | `created_at` datetime NOT NULL, 23 | PRIMARY KEY (`id`), 24 | UNIQUE KEY `user_id` (`user_id`), 25 | KEY `first_name` (`first_name`), 26 | KEY `last_name` (`last_name`), 27 | KEY `username` (`username`) 28 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 29 | 30 | 31 | DROP TABLE IF EXISTS `videos`; 32 | CREATE TABLE `videos` ( 33 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 34 | `author_id` varchar(255) DEFAULT NULL, 35 | `author_username` varchar(255) DEFAULT NULL, 36 | `video_id` bigint(20) NOT NULL, 37 | `file_id` varchar(255) NOT NULL, 38 | `file_unique_id` varchar(255) NOT NULL, 39 | `updated_at` datetime DEFAULT NULL, 40 | `created_at` datetime NOT NULL, 41 | PRIMARY KEY (`id`), 42 | UNIQUE KEY `video_id` (`video_id`), 43 | KEY `file_id` (`file_id`), 44 | KEY `file_unique_id` (`file_unique_id`), 45 | KEY `author_id` (`author_id`), 46 | KEY `author_username` (`author_username`) 47 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 48 | 49 | 50 | -- 2024-10-12 23:06:54 -------------------------------------------------------------------------------- /tiktok_downloader/__init__.py: -------------------------------------------------------------------------------- 1 | from .musicaldown import musicaldown 2 | from .get_content import get_content 3 | -------------------------------------------------------------------------------- /tiktok_downloader/get_content.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import aiofiles 3 | 4 | 5 | async def get_content( 6 | url: str, output: str = "video.mp4", cookies: httpx.Cookies = None 7 | ): 8 | client = httpx.AsyncClient() 9 | result = await client.get( 10 | url, 11 | headers={ 12 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0" 13 | }, 14 | cookies=cookies, 15 | ) 16 | async with aiofiles.open(output, "wb") as w: 17 | async for content in result.aiter_bytes(chunk_size=1024): 18 | await w.write(content) 19 | -------------------------------------------------------------------------------- /tiktok_downloader/musicaldown.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import re 3 | import random 4 | from fake_useragent import UserAgent 5 | from bs4 import BeautifulSoup as bs 6 | from .get_content import get_content 7 | 8 | 9 | async def musicaldown(url: str, output: str): 10 | """ 11 | url: tiktok video url 12 | output: output file name 13 | """ 14 | try: 15 | headers = {"User-Agent": UserAgent().random} 16 | ses = httpx.AsyncClient(headers=headers) 17 | res = await ses.get("https://musicaldown.com/en") 18 | parsing = bs(res.text, "html.parser") 19 | allInput = parsing.findAll("input") 20 | data = {} 21 | for i in allInput: 22 | if i.get("id") == "link_url": 23 | data[i.get("name")] = url 24 | continue 25 | 26 | data[i.get("name")] = i.get("value") 27 | 28 | res = await ses.post( 29 | "https://musicaldown.com/download", data=data, follow_redirects=True 30 | ) 31 | if res.text.find("Convert Video Now") >= 0: 32 | data = re.search(r"data: '(.*?)'", res.text).group(1) 33 | urlSlider = re.search(r"url: '(.*?)'", res.text).group(1) 34 | res = await ses.post(urlSlider, data={"data": data}) 35 | if res.text.find('"success":true') >= 0: 36 | urlVideo = res.json()["url"] 37 | res = await get_content(urlVideo, output) 38 | return True 39 | 40 | return False 41 | 42 | parsing = bs(res.text, "html.parser") 43 | urls = parsing.findAll( 44 | "a", attrs={"class": "btn waves-effect waves-light orange download"} 45 | ) 46 | if len(urls) <= 0: 47 | return False 48 | 49 | i = random.randint(0, 1) 50 | urlVideo = urls[i].get("href") 51 | res = await get_content(urlVideo, output) 52 | return True 53 | 54 | except Exception as e: 55 | print(f"musicaldown error : {e}") 56 | return False 57 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .get_video_detail import get_video_detail 2 | -------------------------------------------------------------------------------- /utils/get_video_detail.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import json 3 | from bs4 import BeautifulSoup as bs 4 | from pathlib import Path 5 | from tiktok_downloader.get_content import get_content 6 | 7 | 8 | async def get_video_detail(url: str): 9 | """ 10 | url: str -> tiktok video url 11 | 12 | return video_id / None 13 | """ 14 | path = Path(url) 15 | post_id = path.stem 16 | headers = { 17 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0", 18 | } 19 | ses = httpx.AsyncClient(headers=headers) 20 | if not post_id.isdigit(): 21 | result = await ses.get(url, follow_redirects=False) 22 | if result.text.startswith('