diff --git a/README.md b/README.md index 1293936..35e4192 100644 --- a/README.md +++ b/README.md @@ -4,41 +4,42 @@ YouTube Download Bot🚀 -This Telegram bot allows you to download videos from YouTube and other supported platforms. +This Telegram bot allows you to download videos from YouTube and other supported platforms, including Instagram! ----- **READ [FAQ](FAQ.md) FIRST IF YOU ENCOUNTER ANY ISSUES.** ----- -[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) +
Deploy to heroku + +Deploy to Heroku If you are having trouble deploying, you can fork the project to your personal account and deploy it from there. **Starting November 28, 2022, free Heroku Dynos, free Heroku Postgres, and free Heroku Data for Redis® plans will no longer be available.** [Heroku Announcement](https://devcenter.heroku.com/articles/free-dyno-hours) +
# Usage [https://t.me/benny_ytdlbot](https://t.me/benny_ytdlbot) Send link directly to the bot. Any -Websites [supported by youtube-dl](https://ytdl-org.github.io/youtube-dl/supportedsites.html) will also work. +Websites [supported by youtube-dl](https://ytdl-org.github.io/youtube-dl/supportedsites.html) will work to. # Limitations of my bot -Due to limitations on servers and bandwidth, there are some restrictions on this service. +Due to limitations on servers and bandwidth, there are some restrictions on this free service. * Each user is limited to 5 free downloads per 24-hour period * there is a maximum of three subscriptions allowed for YouTube channels. -If you require more downloads, you can purchase additional tokens. Additionally, you have the option of deploying your -own bot. +If you need more downloads, you can purchase additional tokens. Additionally, you have the option of deploying your +own bot. See below instructions. # Features -![](assets/1.jpeg) - 1. fast download and upload. 2. ads free 3. support progress bar @@ -50,6 +51,19 @@ own bot. 9. supports celery worker distribution - faster than before. 10. subscriptions to YouTube Channels 11. cache mechanism - download once for the same video. +12. support instagram posts + +# Screenshots + +## Normal download + +![](assets/1.jpeg) + +## Instagram download + +![](assets/instagram.png) + +## celery ![](assets/2.jpeg) @@ -57,6 +71,8 @@ own bot. This bot can be deployed on any platform that supports Python. +Need help with deployment or exclusive features? I offer paid service - contact me at @BennyThink + ## Run natively on your machine To deploy this bot, follow these steps: @@ -162,16 +178,7 @@ Type "help", "copyright", "credits" or "license" for more information. ### 3.2.3 Setup instagram cookies -Required if you want to support instagram. - -You can use this extension -[Get cookies.txt](https://chrome.google.com/webstore/detail/get-cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid) -to get instagram cookies - -```shell -vim data/instagram.com_cookies.txt -# paste your cookies -``` +You don't need to do this anymore! This bot support instagram posts out of the box, including photos, videos and reels. ## 3.3 Tidy docker-compose.yml @@ -265,6 +272,8 @@ https://dmesg.app/m3u8/prog_index.m3u8 https://twitter.com/nitori_sayaka/status/1526199729864200192 https://twitter.com/BennyThinks/status/1475836588542341124 +## test instagram + # Donation * [Buy me a coffee](https://www.buymeacoffee.com/bennythink) diff --git a/assets/instagram.png b/assets/instagram.png new file mode 100644 index 0000000..f19a0e3 Binary files /dev/null and b/assets/instagram.png differ diff --git a/ytdlbot/constant.py b/ytdlbot/constant.py index 2b06da1..975eb4a 100644 --- a/ytdlbot/constant.py +++ b/ytdlbot/constant.py @@ -33,6 +33,8 @@ To prevent abuse, each user is limited to 5 downloads per 24 hours. 3. You have the option to buy more tokens. Type /buy for more information. 4. The source code for this bot will always remain open and can be found here: https://github.com/tgbot-collection/ytdlbot + +5. Need help with deployment or exclusive features? I offer paid service - contact me at @BennyThink """ about = "YouTube Downloader by @BennyThink.\n\nOpen source on GitHub: https://github.com/tgbot-collection/ytdlbot" diff --git a/ytdlbot/downloader.py b/ytdlbot/downloader.py index 38b46e3..0013f00 100644 --- a/ytdlbot/downloader.py +++ b/ytdlbot/downloader.py @@ -21,6 +21,7 @@ import fakeredis import ffmpeg import ffpb import filetype +import requests import yt_dlp as ytdl from tqdm import tqdm @@ -153,7 +154,7 @@ def can_convert_mp4(video_path, uid): return True -def ytdl_download(url, tempdir, bm, **kwargs) -> dict: +def ytdl_download(url, tempdir: "str", bm, **kwargs) -> dict: payment = Payment() chat_id = bm.chat.id hijack = kwargs.get("hijack") @@ -180,7 +181,8 @@ def ytdl_download(url, tempdir, bm, **kwargs) -> dict: None, ] adjust_formats(chat_id, url, formats, hijack) - add_instagram_cookies(url, ydl_opts) + if download_instagram(url, tempdir): + return {"status": True, "error": "", "filepath": list(pathlib.Path(tempdir).glob("*"))} address = ["::", "0.0.0.0"] if IPv6 else [None] for format_ in formats: @@ -252,9 +254,20 @@ def convert_audio_format(resp: "dict", bm): resp["filepath"][index] = new_path -def add_instagram_cookies(url: "str", opt: "dict"): +def download_instagram(url: "str", tempdir: "str"): if url.startswith("https://www.instagram.com"): - opt["cookiefile"] = pathlib.Path(__file__).parent.joinpath("instagram.com_cookies.txt").as_posix() + api = f"https://ssmstore.store/rami/index.php?url={url}" + res = requests.get(api).json() + if isinstance(res, dict): + downloadable = {i["url"]: i["ext"] for i in res["url"]} + else: + downloadable = {i["url"]: i["ext"] for item in res for i in item["url"]} + + for link, ext in downloadable.items(): + save_path = pathlib.Path(tempdir, f"{id(link)}.{ext}") + with open(save_path, "wb") as f: + f.write(requests.get(link, stream=True).content) + return True def split_large_video(response: "dict"): @@ -270,3 +283,8 @@ def split_large_video(response: "dict"): if split and original_video: response["filepath"] = [i.as_posix() for i in pathlib.Path(original_video).parent.glob("*")] + + +if __name__ == "__main__": + a = download_instagram("https://www.instagram.com/p/CrEAz-AI99Y/", "tmp") + print(a) diff --git a/ytdlbot/tasks.py b/ytdlbot/tasks.py index f3f6556..0951de2 100644 --- a/ytdlbot/tasks.py +++ b/ytdlbot/tasks.py @@ -22,6 +22,7 @@ import typing from hashlib import md5 from urllib.parse import quote_plus +import filetype import psutil import pyrogram.errors import requests @@ -42,7 +43,6 @@ from config import ( ENABLE_VIP, OWNER, RATE_LIMIT, - TG_MAX_SIZE, WORKERS, ) from constant import BotText @@ -127,7 +127,7 @@ def forward_video(client, bot_msg, url): try: res_msg: "Message" = upload_processor(client, bot_msg, url, cached_fid) - obj = res_msg.document or res_msg.video or res_msg.audio or res_msg.animation + obj = res_msg.document or res_msg.video or res_msg.audio or res_msg.animation or res_msg.photo caption, _ = gen_cap(bot_msg, url, obj) res_msg.edit_text(caption, reply_markup=gen_video_markup()) @@ -265,27 +265,19 @@ def ytdl_normal_download(bot_msg, client, url): logging.info("Download complete.") if result["status"]: client.send_chat_action(chat_id, "upload_document") - video_paths = result["filepath"] + video_paths: "list" = result["filepath"] bot_msg.edit_text("Download complete. Sending now...") - for video_path in video_paths: - # normally there's only one video in that path... - st_size = os.stat(video_path).st_size - if st_size > TG_MAX_SIZE: - bot_msg.edit_text(f"Your video({sizeof_fmt(st_size)}) is too large for Telegram.") - # client.send_chat_action(chat_id, 'upload_document') - # client.send_message(chat_id, upload_transfer_sh(bot_msg, video_paths)) - continue - try: - upload_processor(client, bot_msg, url, video_path) - except pyrogram.errors.Flood as e: - logging.critical("FloodWait from Telegram: %s", e) - client.send_message( - chat_id, - f"I'm being rate limited by Telegram. Your video will come after {e.x} seconds. Please wait patiently.", - ) - flood_owner_message(client, e) - time.sleep(e.x) - upload_processor(client, bot_msg, url, video_path) + try: + upload_processor(client, bot_msg, url, video_paths) + except pyrogram.errors.Flood as e: + logging.critical("FloodWait from Telegram: %s", e) + client.send_message( + chat_id, + f"I'm being rate limited by Telegram. Your video will come after {e.x} seconds. Please wait patiently.", + ) + flood_owner_message(client, e) + time.sleep(e.x) + upload_processor(client, bot_msg, url, video_paths) bot_msg.edit_text("Download success!✅") else: @@ -296,12 +288,43 @@ def ytdl_normal_download(bot_msg, client, url): temp_dir.cleanup() -def upload_processor(client, bot_msg, url, vp_or_fid: "typing.Any[str, pathlib.Path]"): +def generate_input_media(file_paths: "list", cap: "str") -> list: + input_media = [] + for path in file_paths: + mime = filetype.guess_mime(path) + if "video" in mime: + input_media.append(pyrogram.types.InputMediaVideo(media=path)) + elif "image" in mime: + input_media.append(pyrogram.types.InputMediaPhoto(media=path)) + elif "audio" in mime: + input_media.append(pyrogram.types.InputMediaAudio(media=path)) + else: + input_media.append(pyrogram.types.InputMediaDocument(media=path)) + + input_media[0].caption = cap + return input_media + + +def upload_processor(client, bot_msg, url, vp_or_fid: "typing.Any[str, list]"): # raise pyrogram.errors.exceptions.FloodWait(13) + # if is str, it's a file id; else it's a list of paths payment = Payment() chat_id = bot_msg.chat.id markup = gen_video_markup() - cap, meta = gen_cap(bot_msg, url, vp_or_fid) + if isinstance(vp_or_fid, list) and len(vp_or_fid) > 1: + # just generate the first for simplicity, send as media group(2-20) + cap, meta = gen_cap(bot_msg, url, vp_or_fid[0]) + res_msg = client.send_media_group(chat_id, generate_input_media(vp_or_fid, cap)) + # TODO no cache for now + return res_msg[0] + elif isinstance(vp_or_fid, list) and len(vp_or_fid) == 1: + # normal download, just contains one file in video_paths + vp_or_fid = vp_or_fid[0] + cap, meta = gen_cap(bot_msg, url, vp_or_fid) + else: + # just a file id as string + cap, meta = gen_cap(bot_msg, url, vp_or_fid) + settings = payment.get_user_settings(str(chat_id)) if ARCHIVE_ID and isinstance(vp_or_fid, pathlib.Path): chat_id = ARCHIVE_ID @@ -364,9 +387,19 @@ def upload_processor(client, bot_msg, url, vp_or_fid: "typing.Any[str, pathlib.P reply_markup=markup, **meta, ) + except FileNotFoundError: + # this is likely a photo + logging.info("Retry to send as photo") + res_msg = client.send_photo( + chat_id, + vp_or_fid, + caption=cap, + progress=upload_hook, + progress_args=(bot_msg,), + ) unique = get_unique_clink(url, bot_msg.chat.id) - obj = res_msg.document or res_msg.video or res_msg.audio or res_msg.animation + obj = res_msg.document or res_msg.video or res_msg.audio or res_msg.animation or res_msg.photo redis.add_send_cache(unique, getattr(obj, "file_id", None)) redis.update_metrics("video_success") if ARCHIVE_ID and isinstance(vp_or_fid, pathlib.Path):