├── runtime.txt ├── start.sh ├── Procfile ├── MyTestBotZ ├── __init__.py ├── a.txt ├── helper │ ├── a.txt │ ├── ffmfunc.py │ └── ytdlfunc.py ├── plugins │ ├── a.txt │ ├── start.py │ ├── youtube.py │ └── youtube_callback_data.py └── __main__.py ├── .gitignore ├── requirements.txt ├── config.py ├── app.json └── README.md /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.8.7 2 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | python3 -m MyTestBotZ 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker : chmod +x start.sh && bash start.sh 2 | -------------------------------------------------------------------------------- /MyTestBotZ/__init__.py: -------------------------------------------------------------------------------- 1 | users ={} 2 | user_time = {} 3 | 4 | 5 | -------------------------------------------------------------------------------- /MyTestBotZ/a.txt: -------------------------------------------------------------------------------- 1 | ##### This is Modified version of YouTube Download Bot ##### 2 | -------------------------------------------------------------------------------- /MyTestBotZ/helper/a.txt: -------------------------------------------------------------------------------- 1 | ##### This is Modified version of YouTube Download Bot ##### 2 | -------------------------------------------------------------------------------- /MyTestBotZ/plugins/a.txt: -------------------------------------------------------------------------------- 1 | ##### This is Modified version of YouTube Download Bot ##### 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | downloads/* 2 | *.session 3 | __pycache__/ 4 | *.pyc 5 | log.txt 6 | venv/ 7 | .idea/ 8 | pythonenv3.8/ 9 | .vscode/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | TgCrypto 3 | https://raw.githubusercontent.com/HasibulKabir/pyrogramarchive/master/pyrogramasyncv0.18.0.zip 4 | git+https://github.com/OO7ROBot/youtube_dl 5 | wget 6 | pillow 7 | hachoir 8 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BOT_TOKEN = os.environ.get("BOT_TOKEN") 4 | APP_ID = int(os.environ.get("API_ID")) 5 | API_HASH = os.environ.get("API_HASH") 6 | 7 | youtube_next_fetch = 0.5 # time in minute 8 | 9 | 10 | EDIT_TIME = 5 11 | -------------------------------------------------------------------------------- /MyTestBotZ/__main__.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client 2 | import config 3 | 4 | DOWNLOAD_LOCATION = "./Downloads" 5 | BOT_TOKEN = config.BOT_TOKEN 6 | APP_ID = config.APP_ID 7 | API_HASH = config.API_HASH 8 | 9 | plugins = dict( 10 | root="MyTestBotZ/plugins", 11 | ) 12 | 13 | Client( 14 | "YouTubeDLBot", 15 | bot_token=BOT_TOKEN, 16 | api_id=APP_ID, 17 | api_hash=API_HASH, 18 | plugins=plugins, 19 | workers=100 20 | ).run() 21 | -------------------------------------------------------------------------------- /MyTestBotZ/plugins/start.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, Filters, StopPropagation, InlineKeyboardButton, InlineKeyboardMarkup 2 | 3 | 4 | @Client.on_message(Filters.command(["start"]), group=-2) 5 | async def start(client, message): 6 | # return 7 | joinButton = InlineKeyboardMarkup([ 8 | [InlineKeyboardButton("Channel", url="https://t.me/MyTestBotz"), InlineKeyboardButton("Creator", url="https://telegram.me/OO7ROBot") ], 9 | [InlineKeyboardButton( 10 | "🍿 Source Code 🍿", url="https://github.com/OO7ROBot/YoutubeDownloaderBot")] 11 | ]) 12 | welcomed = f"""Hey {message.from_user.first_name}\nA Simple YouTube Downloader Bot that can: 13 | ➠ Download YouTube videos 14 | ➠ Download audio from YouTube videos \n\n Made with ♥️ by @MyTestBotZ""" 15 | await message.reply_text(welcomed, reply_markup=joinButton) 16 | raise StopPropagation 17 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YouTube Downloader Bot", 3 | "description": "A telegram bot To Download Youtube Files ", 4 | "logo": "https://telegra.ph/file/6a3b1febade2313dd0dca.jpg", 5 | "keywords": [ 6 | "Youtube","YoutubeDownloader" 7 | ], 8 | "repository": "https://github.com/OO7ROBOT/YoutubeDownloaderBot", 9 | "success_url": "https://t.me/tg_utubebot", 10 | "website": "https://github.com/OO7ROBot/YoutubeDownloaderBot", 11 | "env": { 12 | "API_ID": {"description": "Get this value from https://my.telegram.org", "required": true}, 13 | "API_HASH": {"description": "Get this value from https://my.telegram.org" , "required": true}, 14 | "BOT_TOKEN": {"description": "Get Bot Token From BotFather Bot","required": true}, 15 | "AUTH_USERS": {"description": "Authorize user(s) ID separate by space","required": true} 16 | }, 17 | "buildpacks": [ 18 | {"url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git"}, 19 | {"url": "heroku/python"} 20 | ], 21 | "formation": { 22 | "worker": { 23 | "quantity": 1, 24 | "size": "free" 25 | } 26 | }, 27 | "stack": "heroku-20" 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /MyTestBotZ/helper/ffmfunc.py: -------------------------------------------------------------------------------- 1 | import subprocess as sp 2 | import json 3 | 4 | 5 | def probe(vid_file_path): 6 | """ 7 | Give a json from ffprobe command line 8 | @vid_file_path : The absolute (full) path of the video file, string. 9 | """ 10 | if type(vid_file_path) != str: 11 | raise Exception('Give ffprobe a full file path of the file') 12 | 13 | command = ["ffprobe", 14 | "-loglevel", "quiet", 15 | "-print_format", "json", 16 | "-show_format", 17 | "-show_streams", 18 | vid_file_path 19 | ] 20 | 21 | pipe = sp.Popen(command, stdout=sp.PIPE, stderr=sp.STDOUT) 22 | out, err = pipe.communicate() 23 | return json.loads(out) 24 | 25 | 26 | def duration(vid_file_path): 27 | """ 28 | Video's duration in seconds, return a float number 29 | """ 30 | _json = probe(vid_file_path) 31 | 32 | if 'format' in _json: 33 | if 'duration' in _json['format']: 34 | return float(_json['format']['duration']) 35 | 36 | if 'streams' in _json: 37 | # commonly stream 0 is the video 38 | for s in _json['streams']: 39 | if 'duration' in s: 40 | return float(s['duration']) 41 | 42 | raise Exception('duration Not found') 43 | 44 | 45 | if __name__ == "__main__": 46 | print(duration("examplefile.mp4")) # 10.008 47 | -------------------------------------------------------------------------------- /MyTestBotZ/plugins/youtube.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from pyrogram import Client, Filters, InlineKeyboardMarkup, InlineKeyboardButton 3 | from MyTestBotZ import user_time 4 | from config import youtube_next_fetch 5 | from MyTestBotZ.helper.ytdlfunc import extractYt, create_buttons 6 | import wget 7 | import os 8 | from PIL import Image 9 | from pyrogram.errors import UserNotParticipant, UserBannedInChannel 10 | AUTH_USERS = set(int(x) for x in os.environ.get("AUTH_USERS", "1248974748 1401477467").split()) 11 | 12 | 13 | ytregex = r"^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$" 14 | 15 | @Client.on_message(Filters.regex(ytregex)) 16 | async def ytdl(bot, message): 17 | if message.from_user.id not in AUTH_USERS: 18 | return 19 | userLastDownloadTime = user_time.get(message.chat.id) 20 | try: 21 | if userLastDownloadTime > datetime.now(): 22 | wait_time = round((userLastDownloadTime - datetime.now()).total_seconds() / 60, 2) 23 | await message.reply_text(f"`Wait {wait_time} Minutes before next Request`") 24 | return 25 | except: 26 | pass 27 | 28 | url = message.text.strip() 29 | await message.reply_chat_action("typing") 30 | try: 31 | title, thumbnail_url, formats = extractYt(url) 32 | 33 | now = datetime.now() 34 | user_time[message.chat.id] = now + \ 35 | timedelta(minutes=youtube_next_fetch) 36 | 37 | except Exception: 38 | await message.reply_text("`Failed To Fetch Youtube Data... 😔 \nPossible Youtube Blocked server ip \n#error`") 39 | return 40 | buttons = InlineKeyboardMarkup(list(create_buttons(formats))) 41 | sentm = await message.reply_text("Processing Youtube Url 🔎 🔎 🔎") 42 | try: 43 | # Todo add webp image support in thumbnail by default not supported by pyrogram 44 | # https://www.youtube.com/watch?v=lTTajzrSkCw 45 | img = wget.download(thumbnail_url) 46 | im = Image.open(img).convert("RGB") 47 | output_directory = os.path.join(os.getcwd(), "downloads", str(message.chat.id)) 48 | if not os.path.isdir(output_directory): 49 | os.makedirs(output_directory) 50 | thumb_image_path = f"{output_directory}.jpg" 51 | im.save(thumb_image_path,"jpeg") 52 | await message.reply_photo(thumb_image_path, caption=title, reply_markup=buttons) 53 | await sentm.delete() 54 | except Exception as e: 55 | print(e) 56 | try: 57 | thumbnail_url = "https://telegra.ph/file/ce37f8203e1903feed544.png" 58 | await message.reply_photo(thumbnail_url, caption=title, reply_markup=buttons) 59 | except Exception as e: 60 | await sentm.edit( 61 | f"{e} #Error") 62 | 63 | -------------------------------------------------------------------------------- /MyTestBotZ/helper/ytdlfunc.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from pyrogram import Client, Filters, StopPropagation, InlineKeyboardButton, InlineKeyboardMarkup 3 | import yt_dlp 4 | import asyncio 5 | 6 | def humanbytes(num, suffix='B'): 7 | if num is None: 8 | num = 0 9 | else: 10 | num = int(num) 11 | 12 | for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: 13 | if abs(num) < 1024.0: 14 | return "%3.1f%s%s" % (num, unit, suffix) 15 | num /= 1024.0 16 | return "%.1f%s%s" % (num, 'Yi', suffix) 17 | 18 | 19 | def buttonmap(item): 20 | quality = item['format'] 21 | if "audio" in quality: 22 | return [InlineKeyboardButton(f"{quality} 🎵 {humanbytes(item['filesize'])}", 23 | callback_data=f"ytdata||audio||{item['format_id']}||{item['yturl']}")] 24 | else: 25 | return [InlineKeyboardButton(f"{quality} 📹 {humanbytes(item['filesize'])}", 26 | callback_data=f"ytdata||video||{item['format_id']}||{item['yturl']}")] 27 | 28 | def create_buttons(quailitylist): 29 | return map(buttonmap, quailitylist) 30 | 31 | def extractYt(yturl): 32 | ydl = yt_dlp.YoutubeDL() 33 | with ydl: 34 | qualityList = [] 35 | r = ydl.extract_info(yturl, download=False) 36 | for format in r['formats']: 37 | # Filter dash video(without audio) 38 | if not "dash" in str(format['format']).lower(): 39 | qualityList.append( 40 | {"format": format['format'], "filesize": format['filesize'], "format_id": format['format_id'], 41 | "yturl": yturl}) 42 | 43 | return r['title'], r['thumbnail'], qualityList 44 | 45 | async def downloadvideocli(command_to_exec): 46 | process = await asyncio.create_subprocess_exec( 47 | *command_to_exec, 48 | # stdout must a pipe to be accessible as process.stdout 49 | stdout=asyncio.subprocess.PIPE, 50 | stderr=asyncio.subprocess.PIPE, ) 51 | stdout, stderr = await process.communicate() 52 | e_response = stderr.decode().strip() 53 | t_response = stdout.decode().strip() 54 | print(e_response) 55 | filename = t_response.split("Merging formats into")[-1].split('"')[1] 56 | return filename 57 | 58 | 59 | async def downloadaudiocli(command_to_exec): 60 | process = await asyncio.create_subprocess_exec( 61 | *command_to_exec, 62 | # stdout must a pipe to be accessible as process.stdout 63 | stdout=asyncio.subprocess.PIPE, 64 | stderr=asyncio.subprocess.PIPE, ) 65 | stdout, stderr = await process.communicate() 66 | e_response = stderr.decode().strip() 67 | t_response = stdout.decode().strip() 68 | print("Download error:", e_response) 69 | 70 | return t_response.split("Destination")[-1].split("Deleting")[0].split(":")[-1].strip() 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Youtube Downloader bot 2 | #### modified version of Aryan Vikash [YouTube download bot](https://github.com/aryanvikash/Youtube-Downloader-Bot) 3 | 4 | # 5 | 6 |

7 | github 8 | github 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | 17 | 18 | # 19 | 20 |
21 | Features 22 | 23 | - [x] Lightning Fast Download 24 | 25 | - [ ] YouTube Shorts video 26 | 27 | - [ ] Playlist support 28 |
29 | 30 | 31 |
32 | Vars and Details : 33 | 34 | `API_ID` : Goto [my.telegram.org](https://my.telegram.org) to obtain this. 35 | 36 | `API_HASH` : Goto [my.telegram.org](https://my.telegram.org) to obtain this. 37 | 38 | `BOT_TOKEN` : Get the bot token from [@BotFather](https://telegram.dog/BotFather) 39 | 40 | `AUTH_USERS` : Get your user id, Goto [@MissRose_bot](https://telegram.me/MissRose_bot) and type `/id` 41 |
42 | 43 |
44 | Deploy on Heroku: 45 | 46 | 47 | 1. Fork This Repo
48 | 2. Click on the button to Deploy and fill the variables
49 | 50 | Press the below button to deploy on Heroku
51 | 52 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/OO7ROBOT/YoutubeDownloaderBot) 53 | 54 | 55 |
56 | 57 | # Demo Bot: 58 | 59 | 60 | ### Support: 61 | 62 | * Join MyTestBotZ Channel 63 | 64 | 65 | 66 | ### Follow on: 67 | 68 |

69 | 70 |

71 | 72 | ## Credit & Thanks ❤️ 73 |
74 | Everyone in this journey and 👇 75 | 76 | * [Aryan Vikash](https://github.com/aryanvikash) Original base Repo Owner 77 | * [Spechide](https://telegram.dog/SpEcHIDe) for his [AnyDlBot](https://github.com/SpEcHiDe/AnyDLBot) 78 | * [Meeee](https://telegram.me/OO7ROBot) for No Reason 😌 79 |
80 | 81 | * I dont own the source code . As mention above I just made some changes I dont own it's base code 😇 82 | -------------------------------------------------------------------------------- /MyTestBotZ/plugins/youtube_callback_data.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from pyrogram import (Client, 5 | InlineKeyboardButton, 6 | InlineKeyboardMarkup, 7 | ContinuePropagation, 8 | InputMediaDocument, 9 | InputMediaVideo, 10 | InputMediaAudio) 11 | 12 | from MyTestBotZ.helper.ffmfunc import duration 13 | from MyTestBotZ.helper.ytdlfunc import downloadvideocli, downloadaudiocli 14 | from PIL import Image 15 | from hachoir.metadata import extractMetadata 16 | from hachoir.parser import createParser 17 | 18 | @Client.on_callback_query() 19 | async def catch_youtube_fmtid(c, m): 20 | cb_data = m.data 21 | if cb_data.startswith("ytdata||"): 22 | yturl = cb_data.split("||")[-1] 23 | format_id = cb_data.split("||")[-2] 24 | media_type = cb_data.split("||")[-3].strip() 25 | print(media_type) 26 | if media_type == 'audio': 27 | buttons = InlineKeyboardMarkup([[InlineKeyboardButton( 28 | "Audio", callback_data=f"{media_type}||{format_id}||{yturl}"), InlineKeyboardButton("Document", 29 | callback_data=f"docaudio||{format_id}||{yturl}")]]) 30 | else: 31 | buttons = InlineKeyboardMarkup([[InlineKeyboardButton( 32 | "Video", callback_data=f"{media_type}||{format_id}||{yturl}"), InlineKeyboardButton("Document", 33 | callback_data=f"docvideo||{format_id}||{yturl}")]]) 34 | 35 | await m.edit_message_reply_markup(buttons) 36 | 37 | else: 38 | raise ContinuePropagation 39 | 40 | 41 | @Client.on_callback_query() 42 | async def catch_youtube_dldata(c, q): 43 | cb_data = q.data.strip() 44 | #print(q.message.chat.id) 45 | # Callback Data Check 46 | yturl = cb_data.split("||")[-1] 47 | format_id = cb_data.split("||")[-2] 48 | thumb_image_path = "/app/downloads" + \ 49 | "/" + str(q.message.chat.id) + ".jpg" 50 | print(thumb_image_path) 51 | if os.path.exists(thumb_image_path): 52 | width = 0 53 | height = 0 54 | metadata = extractMetadata(createParser(thumb_image_path)) 55 | #print(metadata) 56 | if metadata.has("width"): 57 | width = metadata.get("width") 58 | if metadata.has("height"): 59 | height = metadata.get("height") 60 | img = Image.open(thumb_image_path) 61 | if cb_data.startswith(("audio", "docaudio", "docvideo")): 62 | img.resize((320, height)) 63 | else: 64 | img.resize((90, height)) 65 | img.save(thumb_image_path, "JPEG") 66 | # print(thumb_image_path) 67 | if not cb_data.startswith(("video", "audio", "docaudio", "docvideo")): 68 | print("no data found") 69 | raise ContinuePropagation 70 | 71 | filext = "%(title)s.%(ext)s" 72 | userdir = os.path.join(os.getcwd(), "downloads", str(q.message.chat.id)) 73 | 74 | if not os.path.isdir(userdir): 75 | os.makedirs(userdir) 76 | await q.edit_message_reply_markup( 77 | InlineKeyboardMarkup([[InlineKeyboardButton("📥 Downloading...", callback_data="down")]])) 78 | filepath = os.path.join(userdir, filext) 79 | # await q.edit_message_reply_markup([[InlineKeyboardButton("Processing..")]]) 80 | 81 | audio_command = [ 82 | "yt-dlp", 83 | "-c", 84 | "--prefer-ffmpeg", 85 | "--extract-audio", 86 | "--audio-format", "mp3", 87 | "--audio-quality", format_id, 88 | "-o", filepath, 89 | yturl, 90 | 91 | ] 92 | 93 | video_command = [ 94 | "yt-dlp", 95 | "-c", 96 | "--embed-subs", 97 | "-f", f"{format_id}+bestaudio", 98 | "-o", filepath, 99 | "--hls-prefer-ffmpeg", yturl] 100 | 101 | loop = asyncio.get_event_loop() 102 | 103 | med = None 104 | if cb_data.startswith("audio"): 105 | filename = await downloadaudiocli(audio_command) 106 | med = InputMediaAudio( 107 | media=filename, 108 | thumb=thumb_image_path, 109 | caption=os.path.basename(filename), 110 | title=os.path.basename(filename) + f"\n\n© @TG_Utubebot" 111 | ) 112 | 113 | if cb_data.startswith("video"): 114 | filename = await downloadvideocli(video_command) 115 | dur = round(duration(filename)) 116 | med = InputMediaVideo( 117 | media=filename, 118 | duration=dur, 119 | width=width, 120 | height=height, 121 | thumb=thumb_image_path, 122 | caption=os.path.basename(filename) + f"\n\n© @TG_Utubebot", 123 | supports_streaming=True 124 | ) 125 | 126 | if cb_data.startswith("docaudio"): 127 | filename = await downloadaudiocli(audio_command) 128 | med = InputMediaDocument( 129 | media=filename, 130 | thumb=thumb_image_path, 131 | caption=os.path.basename(filename) + f"\n\n© @TG_Utubebot", 132 | ) 133 | 134 | if cb_data.startswith("docvideo"): 135 | filename = await downloadvideocli(video_command) 136 | dur = round(duration(filename)) 137 | med = InputMediaDocument( 138 | media=filename, 139 | thumb=thumb_image_path, 140 | caption=os.path.basename(filename) + f"\n\n© @TG_Utubebot", 141 | ) 142 | if med: 143 | loop.create_task(send_file(c, q, med, filename)) 144 | else: 145 | print("med not found") 146 | 147 | 148 | async def send_file(c, q, med, filename): 149 | print(med) 150 | try: 151 | await q.edit_message_reply_markup( 152 | InlineKeyboardMarkup([[InlineKeyboardButton("📤 Uploading to TG...", callback_data="down")]])) 153 | await c.send_chat_action(chat_id=q.message.chat.id, action="upload_document") 154 | # this one is not working 155 | await q.edit_message_media(media=med) 156 | except Exception as e: 157 | print(e) 158 | await q.edit_message_text(e) 159 | finally: 160 | try: 161 | os.remove(filename) 162 | os.remove(thumb_image_path) 163 | except: 164 | pass 165 | --------------------------------------------------------------------------------