Skip to content

Commit af32952

Browse files
committed
Rewrite the entire codebase to work with yt-dlp
1 parent c0a6759 commit af32952

9 files changed

Lines changed: 129 additions & 245 deletions

File tree

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,22 @@ But then you won't be able to **upload files up to 2000 MB** and get these [feat
5151

5252
Read the instructions on [eternnoir/pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI/#using-local-bot-api-sever) and [tdlib/telegram-bot-api](https://github.com/tdlib/telegram-bot-api) for more information.
5353

54-
5554
#### Run your bot
5655

5756
Open a new "[screen](https://www.geeksforgeeks.org/screen-command-in-linux-with-examples/)" or tab on your terminal. Then run:
57+
5858
```bash
5959
python bot.py
6060
```
6161

6262
**both the script & api server should run at the same time order to work.**
6363

64+
## Dependencies
65+
66+
- [pytelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI/)
67+
- [python-dotenv](https://pypi.org/project/python-dotenv/)
68+
- [yt-dlp](https://github.com/yt-dlp/yt-dlp)
6469

6570
## Disclaimer
6671

6772
This repository is intended for educational and personal use only. The use of this repository for any commercial or illegal purposes is strictly prohibited. The repository owner does not endorse or encourage the downloading or sharing of copyrighted material without permission. The repository owner is not responsible for any misuse of the software or any legal consequences that may arise from such misuse
68-
69-
- **APIs : [y2mate-api](https://github.com/Simatwa/y2mate-api/) , [pytelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI/)**
70-
- **Contact for issues : [@dev00111](https://t.me/dev00111)**
71-

bot.py

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,69 @@
1-
import os
2-
import telebot
1+
import os, re
32
import threading
3+
import telebot
44

5-
from modules import checker, myqueues
5+
from dotenv import load_dotenv
66

7-
from dotenv import load_dotenv
87
load_dotenv()
98

9+
from modules.checker import youtube_regex
10+
from modules.downloader import download_video
11+
1012
TOKEN = os.getenv("BOT_API_KEY")
1113

1214
bot = telebot.TeleBot(TOKEN, parse_mode="HTML")
13-
14-
@bot.message_handler(commands=['start'])
15+
16+
17+
# '/start' command reply
18+
@bot.message_handler(commands=["start"])
1519
def send_welcome(message):
1620
bot.reply_to(
17-
message, "Hello, I'm a <b>Simple Youtube Downloader!👋</b>\n\nTo get started, just type the /help command.")
21+
message,
22+
"Hello, I'm a <b>Simple Youtube Downloader!👋</b>\n\nTo get started, just type the /help command.",
23+
)
24+
1825

19-
@bot.message_handler(commands=['help'])
26+
# '/help' command reply
27+
@bot.message_handler(commands=["help"])
2028
def send_help(message):
2129
bot.reply_to(
2230
message,
2331
"""
24-
<b>Just send your youtube link and select the video quality.</b> 😉
25-
<i>
26-
Developer: @dev00111
27-
Source: <a href="https://github.com/hansanaD/TelegramYTDLBot">TelegramYTDLBot</a></i>
28-
""", disable_web_page_preview=True,)
29-
30-
32+
<b>Just send me your video link.</b> ▶️
33+
34+
<i>Source: <a href="https://github.com/hansanaD/TelegramYTDLBot">TelegramYTDLBot</a> by <a href="https://github.com/DevHanza/">DevHanza</a></i>
35+
""",
36+
disable_web_page_preview=True,
37+
)
3138

39+
40+
# Youtube Link Listener
3241
@bot.message_handler(func=lambda m: True)
33-
def link_check(message):
34-
checker.linkCheck(bot=bot, message=message)
35-
# print(checker.videoURL)
36-
37-
# Callback handler for # getVidInfo()
38-
@bot.callback_query_handler(func=lambda call: [call.data == item for item in checker.showList])
39-
def callback_query(call):
40-
41-
data = call.data.split("#")
42-
receivedData = data[0]
43-
videoURL = data[1]
44-
# print(receivedData)
45-
46-
bot.answer_callback_query(call.id, f"Selected {receivedData} to download.")
47-
bot.delete_message(chat_id=call.message.chat.id, message_id=call.message.message_id)
48-
49-
myqueues.download_queue.put((call.message, videoURL, receivedData))
50-
queue_position = myqueues.download_queue.qsize()
51-
52-
53-
if queue_position == 0 & 1:
54-
bot.send_message(call.message.chat.id, f"Download has been added to the queue.")
55-
else:
56-
bot.send_message(call.message.chat.id, f"Download has been added to the queue at #{queue_position}.")
57-
58-
59-
# downloader.download(bot=bot, message=call.message, userInput=receivedData, videoURL=checker.videoURL)
60-
# bot.send_message(call.message.chat.id, f"{videoURL} \n{receivedData} : Download Triggered!")
61-
62-
# message, videoURL, receivedData
63-
64-
download_thread = threading.Thread(target=myqueues.download_worker, args=(bot, myqueues.download_queue))
65-
download_thread.daemon = True
66-
download_thread.start()
42+
def on_yt_link(message):
43+
# run download in background so bot stays responsive
44+
threading.Thread(target=yt_link_handler, args=(message,)).start()
45+
46+
47+
def yt_link_handler(message):
48+
49+
matches = re.findall(youtube_regex, message.text)
50+
51+
if matches:
52+
53+
url = matches[0]
54+
status_msg = bot.reply_to(message, f"Starting to download..")
55+
56+
try:
57+
file_path = download_video(url, bot, message.chat.id, status_msg.message_id)
58+
59+
with open(file_path, "rb") as file:
60+
bot.send_video(message.chat.id, file)
61+
62+
os.remove(file_path)
63+
64+
except Exception as e:
65+
bot.reply_to(message, f"❌ Error: {e}")
66+
6767

6868
print("TelegramYTDLBot is running..\n")
6969
bot.infinity_polling()

modules/checker.py

Lines changed: 2 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,2 @@
1-
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
2-
import re
3-
from y2mate_api import Handler
4-
5-
6-
def linkCheck(bot, message):
7-
8-
linkFilter = re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+')
9-
userLinks = re.findall(linkFilter, message.text)
10-
11-
yt_link = []
12-
for link in userLinks:
13-
if 'youtube.com' in link or 'youtu.be' in link:
14-
yt_link.append(link)
15-
16-
if yt_link:
17-
# bot.reply_to(message, "YouTube links found.")
18-
19-
# global videoURL
20-
# global ytApi
21-
22-
videoURL = yt_link[0]
23-
24-
25-
qualityChecker(bot=bot, message=message, videoURL=videoURL)
26-
27-
else:
28-
bot.reply_to(message, "No YouTube links found!")
29-
30-
31-
def qualityChecker(bot, message, videoURL):
32-
33-
qualityCheckerMsg = bot.reply_to(message, "Looking for Available Qualities..🔎")
34-
35-
ytApi = Handler(videoURL)
36-
37-
q_list = ['4k', '1080p', '720p', '480p', '360p', '240p']
38-
# q_list.reverse()
39-
40-
urlList = []
41-
42-
def getVidInfo(r):
43-
for video_metadata in ytApi.run(quality=r):
44-
45-
q = video_metadata.get("q")
46-
dlink = video_metadata.get("dlink")
47-
size = video_metadata.get("size")
48-
49-
if dlink == None:
50-
pass
51-
else:
52-
urlList.append([q, size, dlink])
53-
# print(r, " fetched")
54-
55-
# Iterate over q_list to check if res quality exist on that video
56-
for r in q_list:
57-
getVidInfo(r)
58-
59-
# print(urlList)
60-
61-
# Create a new list to show
62-
global showList
63-
showList = {}
64-
for count, item in enumerate(urlList, 1):
65-
del item[2] # Remove dlink from list
66-
q = item[0]
67-
# print(i)
68-
size = item[1]
69-
showList.update( { count: { "q":q, "size": size }} )
70-
71-
# print(showList)
72-
73-
74-
# Add Inline Buttons to get user input
75-
76-
def gen_markup():
77-
markup = InlineKeyboardMarkup()
78-
for value in showList.values():
79-
callbackData = f"{ value["q"] }#{ videoURL }"
80-
button = InlineKeyboardButton(text=f"{value['q']} ({value['size']})", callback_data=callbackData)
81-
markup.add(button)
82-
return markup
83-
84-
85-
bot.delete_message(qualityCheckerMsg.chat.id, qualityCheckerMsg.message_id)
86-
87-
bot.reply_to(message=message, text="Choose a stream:", reply_markup=gen_markup())
88-
89-
90-
91-
1+
# Simple YouTube URL regex
2+
youtube_regex = r"(https?://(?:www\.)?(?:youtube\.com/watch\?v=|youtu\.be/)[\w\-]+)"

modules/downloader.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from yt_dlp import YoutubeDL
2+
3+
from modules.progress import create_progress_bar
4+
5+
6+
def download_video(url: str, bot, chat_id: int, message_id: int) -> str:
7+
options = {
8+
"outtmpl": "downloads/%(title)s.%(ext)s",
9+
"format": "bestvideo+bestaudio/best",
10+
"noplaylist": True,
11+
"quiet": False,
12+
"merge_output_format": "mp4",
13+
"progress_hooks": [create_progress_bar(bot, chat_id, message_id)],
14+
}
15+
16+
try:
17+
with YoutubeDL(options) as ydl:
18+
info = ydl.extract_info(url, download=True)
19+
filename = ydl.prepare_filename(info)
20+
return filename
21+
22+
except Exception as e:
23+
print(f"Error downloading video: {e} ❌")

modules/myqueues.py

Lines changed: 0 additions & 21 deletions
This file was deleted.

modules/progress.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import time
2+
3+
4+
def progress_bar(percent, length=20):
5+
filled = int(length * percent // 100)
6+
return "█" * filled + "░" * (length - filled)
7+
8+
9+
def create_progress_bar(bot, chat_id, message_id):
10+
"""
11+
Returns a yt-dlp progress hook function
12+
that updates a Telegram message.
13+
"""
14+
last_update_time = 0
15+
16+
def hook(d):
17+
nonlocal last_update_time
18+
19+
if d["status"] == "downloading":
20+
total = d.get("total_bytes") or d.get("total_bytes_estimate")
21+
downloaded = d.get("downloaded_bytes", 0)
22+
23+
if total:
24+
percent = downloaded / total * 100
25+
26+
# throttle edits to avoid Telegram limits
27+
if time.time() - last_update_time > 2:
28+
bar = progress_bar(percent)
29+
30+
text = f"Downloading...\n" f"[{bar}] {percent:.1f}%"
31+
32+
try:
33+
bot.edit_message_text(text, chat_id, message_id)
34+
except Exception:
35+
pass
36+
37+
last_update_time = time.time()
38+
39+
elif d["status"] == "finished":
40+
try:
41+
bot.edit_message_text("Uploading...", chat_id, message_id)
42+
except Exception:
43+
pass
44+
45+
return hook

modules/queue.py

Whitespace-only changes.

modules/ytdownloader.py

Lines changed: 0 additions & 63 deletions
This file was deleted.

0 commit comments

Comments
 (0)