├── .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('