[
  {
    "path": ".gitignore",
    "content": "downloads/*\n*.session\n__pycache__/\n*.pyc\nlog.txt\nvenv/\n.idea/\npythonenv3.8/\n.vscode/"
  },
  {
    "path": "Procfile",
    "content": "worker : chmod +x start.sh && bash start.sh\n"
  },
  {
    "path": "README.md",
    "content": "# Youtube Dl bot 😉\n## Prerequisite\n    ffmpeg\n  \n    \n## install dependencies\n    pip3 install -r requirements.txt\n\n\n## Setup Bot\n    - Change configuration config.py  File\n    - install dependencies\n    - python3 -m bot\n    \n## Thanks ❤️\n* [Spechide](https://telegram.dog/SpEcHIDe) for his [AnyDlBot](https://github.com/SpEcHiDe/AnyDLBot)\n* [HasibulKabir](https://telegram.dog/HasibulKabir)\n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/aryanvikash/Youtube-Downloader-Bot/tree/master)"
  },
  {
    "path": "app.json",
    "content": "{\n  \"name\": \"YouTube Downloader Bot\",\n  \"description\": \"A telegram bot To Download Youtube Files \",\n  \"logo\": \"https://telegra.ph/file/6a3b1febade2313dd0dca.jpg\",\n  \"keywords\": [\n    \"Youtube\",\"YoutubeDownloader\"\n  ],\n  \"repository\": \"https://github.com/aryanvikash/Youtube-Downloader-Bot\",\n  \"success_url\": \"https://t.me/youtubdlbot\",\n  \"website\": \"https://github.com/aryanvikash/Youtube-Downloader-Bot\",\n  \"env\": {\n        \"API_ID\": {\"description\": \"Get this value from https://my.telegram.org\", \"required\": true},\n        \"API_HASH\": {\"description\": \"Get this value from https://my.telegram.org\" , \"required\": true},\n        \"BOT_TOKEN\": {\"description\": \"Get Bot Token From BotFather Bot\",\"required\": true}\n  },\n      \"buildpacks\": [\n        {\"url\": \"https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git\"},\n        {\"url\": \"heroku/python\"}\n    ],\n  \"formation\": {\n        \"worker\": {\n            \"quantity\": 1,\n            \"size\": \"free\"\n        }\n    },\n      \"stack\": \"heroku-20\"\n\n\n}"
  },
  {
    "path": "bot/__init__.py",
    "content": "users ={}\nuser_time = {}\n\n\n"
  },
  {
    "path": "bot/__main__.py",
    "content": "from pyrogram import Client\nimport config\n\nDOWNLOAD_LOCATION = \"./Downloads\"\nBOT_TOKEN = config.BOT_TOKEN\n\nAPP_ID = config.APP_ID\nAPI_HASH = config.API_HASH\n\n\nplugins = dict(\n    root=\"plugins\",\n)\n\nClient(\n    \"YouTubeDlBot\",\n    bot_token=BOT_TOKEN,\n    api_id=APP_ID,\n    api_hash=API_HASH,\n    plugins=plugins,\n    workers=100\n).run()\n"
  },
  {
    "path": "config.py",
    "content": "import  os\n\nBOT_TOKEN = os.environ.get(\"BOT_TOKEN\")\nAPP_ID = int(os.environ.get(\"API_ID\"))\nAPI_HASH = os.environ.get(\"API_HASH\")\n\nyoutube_next_fetch = 0  # time in minute\n\n\nEDIT_TIME = 5"
  },
  {
    "path": "helper/ffmfunc.py",
    "content": "import subprocess as sp\nimport json\n\n\ndef probe(vid_file_path):\n    \"\"\"\n    Give a json from ffprobe command line\n    @vid_file_path : The absolute (full) path of the video file, string.\n    \"\"\"\n    if type(vid_file_path) != str:\n        raise Exception('Give ffprobe a full file path of the file')\n\n    command = [\"ffprobe\",\n               \"-loglevel\", \"quiet\",\n               \"-print_format\", \"json\",\n               \"-show_format\",\n               \"-show_streams\",\n               vid_file_path\n               ]\n\n    pipe = sp.Popen(command, stdout=sp.PIPE, stderr=sp.STDOUT)\n    out, err = pipe.communicate()\n    return json.loads(out)\n\n\ndef duration(vid_file_path):\n    \"\"\"\n    Video's duration in seconds, return a float number\n    \"\"\"\n    _json = probe(vid_file_path)\n\n    if 'format' in _json:\n        if 'duration' in _json['format']:\n            return float(_json['format']['duration'])\n\n    if 'streams' in _json:\n        # commonly stream 0 is the video\n        for s in _json['streams']:\n            if 'duration' in s:\n                return float(s['duration'])\n\n    raise Exception('duration Not found')\n\n\nif __name__ == \"__main__\":\n    print(duration(\"examplefile.mp4\"))  # 10.008\n"
  },
  {
    "path": "helper/ytdlfunc.py",
    "content": "from __future__ import unicode_literals\nfrom pyrogram import Client, Filters, StopPropagation, InlineKeyboardButton, InlineKeyboardMarkup\nimport youtube_dl\nfrom utils.util import humanbytes\nimport asyncio\n\n\ndef buttonmap(item):\n    quality = item['format']\n    if \"audio\" in quality:\n        return [InlineKeyboardButton(f\"{quality} 🎵 {humanbytes(item['filesize'])}\",\n                                     callback_data=f\"ytdata||audio||{item['format_id']}||{item['yturl']}\")]\n    else:\n        return [InlineKeyboardButton(f\"{quality} 📹 {humanbytes(item['filesize'])}\",\n                                     callback_data=f\"ytdata||video||{item['format_id']}||{item['yturl']}\")]\n\n# Return a array of Buttons\ndef create_buttons(quailitylist):\n    return map(buttonmap, quailitylist)\n\n\n# extract Youtube info\ndef extractYt(yturl):\n    ydl = youtube_dl.YoutubeDL()\n    with ydl:\n        qualityList = []\n        r = ydl.extract_info(yturl, download=False)\n        for format in r['formats']:\n            # Filter dash video(without audio)\n            if not \"dash\" in str(format['format']).lower():\n                qualityList.append(\n                {\"format\": format['format'], \"filesize\": format['filesize'], \"format_id\": format['format_id'],\n                 \"yturl\": yturl})\n\n        return r['title'], r['thumbnail'], qualityList\n\n\n#  Need to work on progress\n\n# def downloadyt(url, fmid, custom_progress):\n#     ydl_opts = {\n#         'format': f\"{fmid}+bestaudio\",\n#         \"outtmpl\": \"test+.%(ext)s\",\n#         'noplaylist': True,\n#         'progress_hooks': [custom_progress],\n#     }\n#     with youtube_dl.YoutubeDL(ydl_opts) as ydl:\n#         ydl.download([url])\n\n\n# https://github.com/SpEcHiDe/AnyDLBot\n\nasync def downloadvideocli(command_to_exec):\n    process = await asyncio.create_subprocess_exec(\n        *command_to_exec,\n        # stdout must a pipe to be accessible as process.stdout\n        stdout=asyncio.subprocess.PIPE,\n        stderr=asyncio.subprocess.PIPE, )\n    stdout, stderr = await process.communicate()\n    e_response = stderr.decode().strip()\n    t_response = stdout.decode().strip()\n    print(e_response)\n    filename = t_response.split(\"Merging formats into\")[-1].split('\"')[1]\n    return filename\n\n\nasync def downloadaudiocli(command_to_exec):\n    process = await asyncio.create_subprocess_exec(\n        *command_to_exec,\n        # stdout must a pipe to be accessible as process.stdout\n        stdout=asyncio.subprocess.PIPE,\n        stderr=asyncio.subprocess.PIPE, )\n    stdout, stderr = await process.communicate()\n    e_response = stderr.decode().strip()\n    t_response = stdout.decode().strip()\n    print(\"Download error:\", e_response)\n\n    return t_response.split(\"Destination\")[-1].split(\"Deleting\")[0].split(\":\")[-1].strip()\n"
  },
  {
    "path": "plugins/help.py",
    "content": "from pyrogram import Client, Filters\n\n\n@Client.on_message(Filters.command([\"help\"]))\nasync def start(client, message):\n    helptxt = f\"Currently Only supports Youtube Single  (No playlist) Just Send Youtube Url\"\n    await message.reply_text(helptxt)\n"
  },
  {
    "path": "plugins/start.py",
    "content": "from pyrogram import Client, Filters, StopPropagation, InlineKeyboardButton, InlineKeyboardMarkup\n\n\n@Client.on_message(Filters.command([\"start\"]), group=-2)\nasync def start(client, message):\n    # return\n    joinButton = InlineKeyboardMarkup([\n        [InlineKeyboardButton(\"Channel\", url=\"https://t.me/aryan_bots\")],\n        [InlineKeyboardButton(\n            \"Report Bugs 😊\", url=\"https://t.me/aryanvikash\")]\n    ])\n    welcomed = f\"Hey <b>{message.from_user.first_name}</b>\\n/help for More info\"\n    await message.reply_text(welcomed, reply_markup=joinButton)\n    raise StopPropagation\n"
  },
  {
    "path": "plugins/youtube.py",
    "content": "from datetime import datetime, timedelta\nfrom pyrogram import Client, Filters, InlineKeyboardMarkup, InlineKeyboardButton\nfrom bot import user_time\nfrom config import youtube_next_fetch\nfrom helper.ytdlfunc import extractYt, create_buttons\nimport wget\nimport os\nfrom PIL import Image\n\nytregex = r\"^((?:https?:)?\\/\\/)?((?:www|m)\\.)?((?:youtube\\.com|youtu.be))(\\/(?:[\\w\\-]+\\?v=|embed\\/|v\\/)?)([\\w\\-]+)(\\S+)?$\"\n\n\n@Client.on_message(Filters.regex(ytregex))\nasync def ytdl(_, message):\n    userLastDownloadTime = user_time.get(message.chat.id)\n    try:\n        if userLastDownloadTime > datetime.now():\n            wait_time = round((userLastDownloadTime - datetime.now()).total_seconds() / 60, 2)\n            await message.reply_text(f\"`Wait {wait_time} Minutes before next Request`\")\n            return\n    except:\n        pass\n\n    url = message.text.strip()\n    await message.reply_chat_action(\"typing\")\n    try:\n        title, thumbnail_url, formats = extractYt(url)\n\n        now = datetime.now()\n        user_time[message.chat.id] = now + \\\n                                     timedelta(minutes=youtube_next_fetch)\n\n    except Exception:\n        await message.reply_text(\"`Failed To Fetch Youtube Data... 😔 \\nPossible Youtube Blocked server ip \\n#error`\")\n        return\n    buttons = InlineKeyboardMarkup(list(create_buttons(formats)))\n    sentm = await message.reply_text(\"Processing Youtube Url 🔎 🔎 🔎\")\n    try:\n        # Todo add webp image support in thumbnail by default not supported by pyrogram\n        # https://www.youtube.com/watch?v=lTTajzrSkCw\n        img = wget.download(thumbnail_url)\n        im = Image.open(img).convert(\"RGB\")\n        output_directory = os.path.join(os.getcwd(), \"downloads\", str(message.chat.id))\n        if not os.path.isdir(output_directory):\n            os.makedirs(output_directory)\n        thumb_image_path = f\"{output_directory}.jpg\"\n        im.save(thumb_image_path,\"jpeg\")\n        await message.reply_photo(thumb_image_path, caption=title, reply_markup=buttons)\n        await sentm.delete()\n    except Exception as e:\n        print(e)\n        try:\n            thumbnail_url = \"https://telegra.ph/file/ce37f8203e1903feed544.png\"\n            await message.reply_photo(thumbnail_url, caption=title, reply_markup=buttons)\n        except Exception as e:\n            await sentm.edit(\n            f\"<code>{e}</code> #Error\")\n\n"
  },
  {
    "path": "plugins/youtube_callback_data.py",
    "content": "import asyncio\nimport os\n\nfrom pyrogram import (Client,\n                      InlineKeyboardButton,\n                      InlineKeyboardMarkup,\n                      ContinuePropagation,\n                      InputMediaDocument,\n                      InputMediaVideo,\n                      InputMediaAudio)\n\nfrom helper.ffmfunc import duration\nfrom helper.ytdlfunc import downloadvideocli, downloadaudiocli\nfrom PIL import Image\nfrom hachoir.metadata import extractMetadata\nfrom hachoir.parser import createParser\n\n@Client.on_callback_query()\nasync def catch_youtube_fmtid(c, m):\n    cb_data = m.data\n    if cb_data.startswith(\"ytdata||\"):\n        yturl = cb_data.split(\"||\")[-1]\n        format_id = cb_data.split(\"||\")[-2]\n        media_type = cb_data.split(\"||\")[-3].strip()\n        print(media_type)\n        if media_type == 'audio':\n            buttons = InlineKeyboardMarkup([[InlineKeyboardButton(\n                \"Audio\", callback_data=f\"{media_type}||{format_id}||{yturl}\"), InlineKeyboardButton(\"Document\",\n                                                                                                    callback_data=f\"docaudio||{format_id}||{yturl}\")]])\n        else:\n            buttons = InlineKeyboardMarkup([[InlineKeyboardButton(\n                \"Video\", callback_data=f\"{media_type}||{format_id}||{yturl}\"), InlineKeyboardButton(\"Document\",\n                                                                                                    callback_data=f\"docvideo||{format_id}||{yturl}\")]])\n\n        await m.edit_message_reply_markup(buttons)\n\n    else:\n        raise ContinuePropagation\n\n\n@Client.on_callback_query()\nasync def catch_youtube_dldata(c, q):\n    cb_data = q.data.strip()\n    #print(q.message.chat.id)\n    # Callback Data Check\n    yturl = cb_data.split(\"||\")[-1]\n    format_id = cb_data.split(\"||\")[-2]\n    thumb_image_path = \"/app/downloads\" + \\\n        \"/\" + str(q.message.chat.id) + \".jpg\"\n    print(thumb_image_path)\n    if os.path.exists(thumb_image_path):\n        width = 0\n        height = 0\n        metadata = extractMetadata(createParser(thumb_image_path))\n        #print(metadata)\n        if metadata.has(\"width\"):\n            width = metadata.get(\"width\")\n        if metadata.has(\"height\"):\n            height = metadata.get(\"height\")\n        img = Image.open(thumb_image_path)\n        if cb_data.startswith((\"audio\", \"docaudio\", \"docvideo\")):\n            img.resize((320, height))\n        else:\n            img.resize((90, height))\n        img.save(thumb_image_path, \"JPEG\")\n     #   print(thumb_image_path)\n    if not cb_data.startswith((\"video\", \"audio\", \"docaudio\", \"docvideo\")):\n        print(\"no data found\")\n        raise ContinuePropagation\n\n    filext = \"%(title)s.%(ext)s\"\n    userdir = os.path.join(os.getcwd(), \"downloads\", str(q.message.chat.id))\n\n    if not os.path.isdir(userdir):\n        os.makedirs(userdir)\n    await q.edit_message_reply_markup(\n        InlineKeyboardMarkup([[InlineKeyboardButton(\"Downloading...\", callback_data=\"down\")]]))\n    filepath = os.path.join(userdir, filext)\n    # await q.edit_message_reply_markup([[InlineKeyboardButton(\"Processing..\")]])\n\n    audio_command = [\n        \"youtube-dl\",\n        \"-c\",\n        \"--prefer-ffmpeg\",\n        \"--extract-audio\",\n        \"--audio-format\", \"mp3\",\n        \"--audio-quality\", format_id,\n        \"-o\", filepath,\n        yturl,\n\n    ]\n\n    video_command = [\n        \"youtube-dl\",\n        \"-c\",\n        \"--embed-subs\",\n        \"-f\", f\"{format_id}+bestaudio\",\n        \"-o\", filepath,\n        \"--hls-prefer-ffmpeg\", yturl]\n\n    loop = asyncio.get_event_loop()\n\n    med = None\n    if cb_data.startswith(\"audio\"):\n        filename = await downloadaudiocli(audio_command)\n        med = InputMediaAudio(\n            media=filename,\n            thumb=thumb_image_path,\n            caption=os.path.basename(filename),\n            title=os.path.basename(filename)\n        )\n\n    if cb_data.startswith(\"video\"):\n        filename = await downloadvideocli(video_command)\n        dur = round(duration(filename))\n        med = InputMediaVideo(\n            media=filename,\n            duration=dur,\n            width=width,\n            height=height,\n            thumb=thumb_image_path,\n            caption=os.path.basename(filename),\n            supports_streaming=True\n        )\n\n    if cb_data.startswith(\"docaudio\"):\n        filename = await downloadaudiocli(audio_command)\n        med = InputMediaDocument(\n            media=filename,\n            thumb=thumb_image_path,\n            caption=os.path.basename(filename),\n        )\n\n    if cb_data.startswith(\"docvideo\"):\n        filename = await downloadvideocli(video_command)\n        dur = round(duration(filename))\n        med = InputMediaDocument(\n            media=filename,\n            thumb=thumb_image_path,\n            caption=os.path.basename(filename),\n        )\n    if med:\n        loop.create_task(send_file(c, q, med, filename))\n    else:\n        print(\"med not found\")\n\n\nasync def send_file(c, q, med, filename):\n    print(med)\n    try:\n        await q.edit_message_reply_markup(\n            InlineKeyboardMarkup([[InlineKeyboardButton(\"Uploading...\", callback_data=\"down\")]]))\n        await c.send_chat_action(chat_id=q.message.chat.id, action=\"upload_document\")\n        # this one is not working\n        await q.edit_message_media(media=med)\n    except Exception as e:\n        print(e)\n        await q.edit_message_text(e)\n    finally:\n        try:\n            os.remove(filename)\n            os.remove(thumb_image_path)\n        except:\n            pass\n"
  },
  {
    "path": "requirements.txt",
    "content": "\nTgCrypto\nhttps://raw.githubusercontent.com/HasibulKabir/pyrogramarchive/master/pyrogramasyncv0.18.0.zip\ngit+https://github.com/ytdl-org/youtube-dl\nwget\npillow\nhachoir\n"
  },
  {
    "path": "runtime.txt",
    "content": "python-3.8.7\n"
  },
  {
    "path": "start.sh",
    "content": "python3 -m  bot\n"
  },
  {
    "path": "utils/util.py",
    "content": "def humanbytes(num, suffix='B'):\n    if num is None:\n        num = 0\n    else:\n        num = int(num)\n\n    for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:\n        if abs(num) < 1024.0:\n            return \"%3.1f%s%s\" % (num, unit, suffix)\n        num /= 1024.0\n    return \"%.1f%s%s\" % (num, 'Yi', suffix)\n\n\n"
  }
]