diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f44e9d3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM ghcr.io/anonymousx97/build_essentials:main + +# adding email and username to the bot +RUN git config --global user.email "88324835+anonymousx97@users.noreply.github" \ + git config --global user.name "anonymousx97" + +# Exposing Ports for Web Server +EXPOSE 8080 22 8022 + +# Adding Remote Container Start Command +CMD bash -c "$(curl -fsSL https://raw.githubusercontent.com/anonymousx97/Docker/main/start)" \ No newline at end of file diff --git a/README.md b/README.md index fe25169..c501477 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ ``` ### Installation: + Note: If deploying using Dockerfile add UPSTREAM_REPO var with repo link as it's value. + ```bash # Install required packages. apt install -y python3 git curl python-pip ffmpeg diff --git a/app/api/instagram.py b/app/api/instagram.py index f92543e..26e3f13 100755 --- a/app/api/instagram.py +++ b/app/api/instagram.py @@ -30,6 +30,8 @@ class Instagram(ScraperConfig): async def api_dl(self): + if not Config.API_KEYS: + return param = {"api_key": await self.get_key(), "url": self.url, "proxy": "residential", "js": False} response = await aiohttp_tools.get_json(url="https://api.webscraping.ai/html", timeout=30, params=param) if not response or "data" not in response or not response["data"]["shortcode_media"]: @@ -62,4 +64,4 @@ class Instagram(ScraperConfig): count = 0 ret_key = keys[count] API_KEYS["counter"] = count - return ret_key + return ret_key \ No newline at end of file diff --git a/app/api/reddit.py b/app/api/reddit.py index fe43705..efcee49 100755 --- a/app/api/reddit.py +++ b/app/api/reddit.py @@ -45,7 +45,8 @@ class Reddit(ScraperConfig): else: self.link = json_.get("preview", {}).get("reddit_video_preview", {}).get("fallback_url", json_.get("url_overridden_by_dest", "")).strip() - + if not self.link: + return if self.link.endswith(".gif"): self.gif = self.success = True else: diff --git a/app/api/ytdl.py b/app/api/ytdl.py index 01555b5..a9bcd6e 100755 --- a/app/api/ytdl.py +++ b/app/api/ytdl.py @@ -1,6 +1,7 @@ import asyncio import os import time +from urllib.parse import urlparse import yt_dlp @@ -39,7 +40,11 @@ class YT_DL(ScraperConfig): } async def download_or_extract(self): + if "/playlist" in self.url: + return if "youtu" in self.url: + if not urlparse(self.url).query: + return self._opts["format"] = "bv[ext=mp4][res=480]+ba[ext=m4a]/b[ext=mp4]" if "shorts" in self.url: self._opts["format"] = "bv[ext=mp4][res=720]+ba[ext=m4a]/b[ext=mp4]" diff --git a/app/core/MediaHandler.py b/app/core/MediaHandler.py index dd2cc77..787eae5 100644 --- a/app/core/MediaHandler.py +++ b/app/core/MediaHandler.py @@ -4,9 +4,6 @@ import os import traceback from urllib.parse import urlparse -from pyrogram.errors import MediaEmpty, PhotoSaveFileInvalid, WebpageCurlFailed -from pyrogram.types import InputMediaPhoto, InputMediaVideo - from app.api.gallerydl import Gallery_DL from app.api.instagram import Instagram from app.api.reddit import Reddit @@ -14,9 +11,10 @@ from app.api.threads import Threads from app.api.tiktok import Tiktok from app.api.ytdl import YT_DL from app.core import aiohttp_tools, shell +from pyrogram.errors import MediaEmpty, PhotoSaveFileInvalid, WebpageCurlFailed +from pyrogram.types import InputMediaPhoto, InputMediaVideo # Thanks Jeel Patel for the concept TG[@jeelpatel231] - url_map = { "tiktok.com": Tiktok, "www.instagram.com": Instagram, @@ -33,11 +31,15 @@ url_map = { class ExtractAndSendMedia: def __init__(self, message): self.exceptions, self.media_objects = [], [] + self.__client = message._client self.message = message self.doc = "-d" in message.flags self.spoiler = "-s" in message.flags self.sender = "" if "-ns" in message.flags else f"\nShared by : {self.extract_sender()}" - self.args_ = {"chat_id": self.message.chat.id, "reply_to_message_id": message.reply_id} + self.args_ = { + "chat_id": self.message.chat.id, + "reply_to_message_id": message.reply_id, + } def extract_sender(self): author = self.message.author_signature @@ -68,41 +70,41 @@ class ExtractAndSendMedia: elif obj.group: await self.send_group(obj, caption=caption) elif obj.photo: - await self.send_photo(obj.link, caption=caption) + await self.send( + media={"photo":obj.link}, + method=self.__client.send_photo, + caption=caption, + has_spoiler=self.spoiler, + ) elif obj.video: - await self.send_video(obj.link, thumb=obj.thumb, caption=caption) + await self.send( + media={"video":obj.link}, + method=self.__client.send_video, + thumb=await aiohttp_tools.thumb_dl(obj.thumb), + caption=caption, + has_spoiler=self.spoiler, + ) elif obj.gif: - await self.send_animation(obj.link, caption=caption) + await self.send( + media={"animation":obj.link}, + method=self.__client.send_animation, + caption=caption, + has_spoiler=self.spoiler, + unsave=True, + ) except BaseException: - self.exceptions.append(traceback.format_exc()) + self.exceptions.append("\n".join([obj.caption_url.strip(), traceback.format_exc()])) - async def send_photo(self, photo, caption): + async def send(self, media, method, **kwargs): try: try: - await self.message._client.send_photo(**self.args_, photo=photo, caption=caption, has_spoiler=self.spoiler) + await method(**media, **self.args_, **kwargs) except (MediaEmpty, WebpageCurlFailed): - photo = await aiohttp_tools.in_memory_dl(photo) - await self.message._client.send_photo(**self.args_, photo=photo, caption=caption, has_spoiler=self.spoiler) + key, value = list(media.items())[0] + media[key] = await aiohttp_tools.in_memory_dl(value) + await method(**media, **self.args_, **kwargs) except PhotoSaveFileInvalid: - await self.message._client.send_document(**self.args, document=photo, caption=caption, force_document=True) - - async def send_video(self, video, caption, thumb): - try: - await self.message._client.send_video( - **self.args_, video=video, caption=caption, thumb=await aiohttp_tools.thumb_dl(thumb), has_spoiler=self.spoiler - ) - except (MediaEmpty, WebpageCurlFailed): - video = await aiohttp_tools.in_memory_dl(video) - await self.message._client.send_video( - **self.args_, video=video, caption=caption, thumb=await aiohttp_tools.thumb_dl(thumb), has_spoiler=self.spoiler - ) - - async def send_animation(self, gif, caption): - try: - await self.message._client.send_animation(**self.args_, animation=gif, caption=caption, unsave=True, has_spoiler=self.spoiler) - except (MediaEmpty, WebpageCurlFailed): - gif = await aiohttp_tools.in_memory_dl(gif) - await self.message._client.send_animation(**self.args_, animation=gif, caption=caption, unsave=True, has_spoiler=self.spoiler) + await self.__client.send_document(**self.args_, document=media, caption=caption, force_document=True) async def send_document(self, docs, caption, path=""): if not path: @@ -112,10 +114,10 @@ class ExtractAndSendMedia: docs = glob.glob(f"{path}/*") for doc in docs: try: - await self.message._client.send_document(**self.args_, document=doc, caption=caption, force_document=True) + await self.__client.send_document(**self.args_, document=doc, caption=caption, force_document=True) except (MediaEmpty, WebpageCurlFailed): doc = await aiohttp_tools.in_memory_dl(doc) - await self.message._client.send_document(**self.args_, document=doc, caption=caption, force_document=True) + await self.__client.send_document(**self.args_, document=doc, caption=caption, force_document=True) await asyncio.sleep(0.5) async def send_group(self, media, caption): @@ -125,9 +127,9 @@ class ExtractAndSendMedia: sorted = await self.sort_media_urls(media.link, caption) for data in sorted: if isinstance(data, list): - await self.message._client.send_media_group(**self.args_, media=data) + await self.__client.send_media_group(**self.args_, media=data) else: - await self.send_animation(data, caption=caption) + await self.send(media={"animation": data}, method=self.__client.send_animation, caption=caption, has_spoiler=self.spoiler, unsave=True) await asyncio.sleep(1) async def sort_media_path(self, path, caption): diff --git a/app/core/client.py b/app/core/client.py index 375dc9e..c876d79 100755 --- a/app/core/client.py +++ b/app/core/client.py @@ -16,12 +16,15 @@ from app.core.message import Message class BOT(Client): - _LOADED = False def __init__(self): + if string := os.environ.get("STRING_SESSION"): + mode_arg = { "session_string": string } + else: + mode_arg = { "bot_token": os.environ.get("BOT_TOKEN") } super().__init__( name="bot", - session_string=os.environ.get("STRING_SESSION"), + **mode_arg, api_id=int(os.environ.get("API_ID")), api_hash=os.environ.get("API_HASH"), in_memory=True, @@ -60,12 +63,11 @@ class BOT(Client): async def import_modules(self): for module_ in glob.glob("app/*/*.py"): importlib.import_module(os.path.splitext(module_.replace("/", "."))[0]) - self._LOADED = True - async def log(self, text, chat=None, func=None, name="log.txt"): + async def log(self, text, chat=None, func=None, name="log.txt",disable_web_page_preview=True): if chat or func: text = f"Function: {func}\nChat: {chat}\nTraceback:\n{text}" - return await self.send_message(chat_id=Config.LOG_CHAT, text=text, name=name) + return await self.send_message(chat_id=Config.LOG_CHAT, text=text, name=name, disable_web_page_preview=disable_web_page_preview) async def restart(self): await aiohttp_tools.session_switch() diff --git a/app/core/message.py b/app/core/message.py index 2e47d08..1b98829 100755 --- a/app/core/message.py +++ b/app/core/message.py @@ -24,7 +24,7 @@ class Message(MSG): self.reply_id = replied.id def flags_n_input(self): - self.flags = [i for i in self.text.split() if i.startswith("-")] + self.flags = [i for i in self.text.split() if i.startswith("-") ] split_cmd_str = self.text.split(maxsplit=1) if len(split_cmd_str) > 1: self.input = split_cmd_str[-1] diff --git a/app/core/scraper_config.py b/app/core/scraper_config.py index a9232f7..5f83ef9 100644 --- a/app/core/scraper_config.py +++ b/app/core/scraper_config.py @@ -15,7 +15,7 @@ class ScraperConfig: self.gif = False def set_sauce(self, url): - self.caption_url = f"\n\nSauce" + self.caption_url = f"\n\nSauce" @classmethod async def start(cls, url): diff --git a/app/plugins/authorise.py b/app/plugins/authorise.py index a94d6ac..b5a882e 100644 --- a/app/plugins/authorise.py +++ b/app/plugins/authorise.py @@ -94,7 +94,7 @@ async def add_chat(bot, message): message_id=Config.AUTO_DL_MESSAGE_ID, ): return await message.reply(err) - await message.reply(f"{message.chat.title} Added to Authorised List.") + await message.reply(f"{message.chat.title or message.chat.first_name} Added to Authorised List.") @bot.add_cmd(cmd="delchat") @@ -110,7 +110,7 @@ async def add_chat(bot, message): message_id=Config.AUTO_DL_MESSAGE_ID, ): return await message.reply(err) - await message.reply(f"{message.chat.title} Added Removed from Authorised List.") + await message.reply(f"{message.chat.title or message.chat.first_name} Added Removed from Authorised List.") diff --git a/app/plugins/bot.py b/app/plugins/bot.py index 20d5f16..a49cf76 100755 --- a/app/plugins/bot.py +++ b/app/plugins/bot.py @@ -29,4 +29,4 @@ async def restart(bot, message): @bot.add_cmd(cmd="update") async def chat_update(bot, message): await bot.set_filter_list() - await message.reply("Filters Refreshed") + await message.reply("Filters Refreshed") \ No newline at end of file diff --git a/app/plugins/loader.py b/app/plugins/loader.py new file mode 100644 index 0000000..2cb01a7 --- /dev/null +++ b/app/plugins/loader.py @@ -0,0 +1,22 @@ +import importlib +import sys +import traceback + +from app import bot + +@bot.add_cmd(cmd="load") +async def loader(bot, message): + if ( + not message.replied + or not message.replied.document + or not message.replied.document.file_name.endswith(".py") + ): + return await message.reply("reply to a plugin.") + file_name = message.replied.document.file_name.rstrip(".py") + sys.modules.pop(f"app.temp.{file_name}", None) + plugin = await message.replied.download("app/temp/") + try: + importlib.import_module(f"app.temp.{file_name}") + except BaseException: + return await message.reply(str(traceback.format_exc())) + await message.reply(f"Loaded {file_name}.py.") diff --git a/app/plugins/song.py b/app/plugins/song.py new file mode 100644 index 0000000..346a10c --- /dev/null +++ b/app/plugins/song.py @@ -0,0 +1,56 @@ +import asyncio +import glob +import shutil +from time import time +from urllib.parse import urlparse + +import yt_dlp +from app import bot +from app.api.ytdl import FakeLogger +from app.core.aiohttp_tools import in_memory_dl + +domains = ["www.youtube.com", "youtube.com", "m.youtube.com", "youtu.be", "www.youtube-nocookie.com", "music.youtube.com"] + + +@bot.add_cmd(cmd="song") +async def song_dl(bot, message): + reply_query = None + audio_file = None + artist = None + if message.replied: + for link in message.replied.text.split(): + if urlparse(link).netloc in domains: + reply_query = link + break + query = reply_query or message.flt_input + if not query: + return await message.reply("Give a song name or link to download.") + response = await message.reply("Searching....") + dl_path = f"downloads/{time()}/" + query_or_search = query if query.startswith("http") else f"ytsearch:{query}" + if "-m" in message.flags: + aformat = "mp3" + else: + aformat = "opus" + yt_opts = { + "logger": FakeLogger(), + "outtmpl": dl_path + "%(title)s.%(ext)s", + "format": "bestaudio", + "postprocessors": [{"key": "FFmpegExtractAudio", "preferredcodec": aformat}, {"key": "FFmpegMetadata"}, {"key": "EmbedThumbnail"}], + } + ytdl = yt_dlp.YoutubeDL(yt_opts) + yt_info = await asyncio.to_thread(ytdl.extract_info, query_or_search) + if not query_or_search.startswith("http"): + yt_info = yt_info["entries"][0] + duration = yt_info["duration"] + artist = yt_info["channel"] + thumb = await in_memory_dl(yt_info["thumbnail"]) + down_path = glob.glob(dl_path + "*") + if not down_path: + return await response.edit("Not found") + await response.edit("Uploading....") + for audio_file in down_path: + if audio_file.endswith((".opus", ".mp3")): + await message.reply_audio(audio=audio_file, duration=int(duration), performer=str(artist), thumb=thumb) + await response.delete() + shutil.rmtree(dl_path, ignore_errors=True) \ No newline at end of file diff --git a/app/plugins/tools.py b/app/plugins/tools.py index 742ad13..a640083 100644 --- a/app/plugins/tools.py +++ b/app/plugins/tools.py @@ -7,7 +7,7 @@ from pyrogram.enums import ParseMode from app import Config from app.core import shell -from app.core.aiohttp_tools import SESSION, in_memory_dl +from app.core import aiohttp_tools as aio # Run shell commands diff --git a/app/social_dl.py b/app/social_dl.py index 85a322a..449b400 100644 --- a/app/social_dl.py +++ b/app/social_dl.py @@ -11,7 +11,7 @@ async def dl(bot, message): media = await ExtractAndSendMedia.process(message) if media.exceptions: exceptions = "\n".join(media.exceptions) - await bot.log(text=exceptions, func="DL", chat=message.chat.id, name="traceback.txt") + await bot.log(text=exceptions, func="DL", chat=message.chat.title or message.chat.first_name, name="traceback.txt") return await reply.edit(f"Media Download Failed.") if media.media_objects: await message.delete() @@ -26,7 +26,7 @@ async def cmd_dispatcher(bot, message): try: await func(bot, parsed_message) except BaseException: - await bot.log(text=str(traceback.format_exc()), chat=message.chat.id, func=func.__name__, name="traceback.txt") + await bot.log(text=str(traceback.format_exc()), chat=message.chat.title or message.chat.first_name, func=func.__name__, name="traceback.txt") @bot.on_message(filters.chat_filter) @@ -36,4 +36,4 @@ async def dl_dispatcher(bot, message): try: await func(bot, parsed_message) except BaseException: - await bot.log(text=str(traceback.format_exc()), chat=message.chat.id, func=func.__name__, name="traceback.txt") + await bot.log(text=str(traceback.format_exc()), chat=message.chat.title or message.chat.first_name, func=func.__name__, name="traceback.txt") diff --git a/run b/run new file mode 100644 index 0000000..fec2c3d --- /dev/null +++ b/run @@ -0,0 +1,14 @@ +#!/bin/sh + +if [ "$API_PORT" ] ; then +py_code=" +from aiohttp import web +app = web.Application() +app.router.add_get('/', lambda _: web.Response(text='Web Server Running...')) +web.run_app(app, host='0.0.0.0', port=$API_PORT, reuse_port=True, print=None) +" + python3 -q -c "$py_code" & echo "Dummy Web Server Started..." + +fi + +python3 -m app \ No newline at end of file