diff --git a/README.md b/README.md index c501477..2aacef6 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,11 @@ # Install Pypi packages pip install -U setuptools wheel + + # For Termux Env: + grep -v uvloop req.txt | xargs -n 1 pip install + + # For Non Termux Env: pip install -r req.txt #Setup config.env diff --git a/app/__init__.py b/app/__init__.py index 9b61328..bf8e43e 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -5,5 +5,10 @@ load_dotenv("config.env") from .config import Config from .core.client import BOT +import os + +if not os.environ.get("TERMUX_APK_RELEASE"): + import uvloop + uvloop.install() bot = BOT() diff --git a/app/__main__.py b/app/__main__.py index 07982af..4cb31b6 100755 --- a/app/__main__.py +++ b/app/__main__.py @@ -1,8 +1,6 @@ if __name__ == "__main__": import tracemalloc + tracemalloc.start() from app import bot - import app.social_dl - - tracemalloc.start() bot.run(bot.boot()) diff --git a/app/api/gallerydl.py b/app/api/gallerydl.py index 8e68ed8..4bbb185 100644 --- a/app/api/gallerydl.py +++ b/app/api/gallerydl.py @@ -24,4 +24,4 @@ class Gallery_DL(ScraperConfig): files = glob.glob(f"{self.path}/*") if not files: return self.cleanup() - self.link = self.success = True + self.link = self.group = self.success = True diff --git a/app/api/ytdl.py b/app/api/ytdl.py index a9bcd6e..f64a486 100755 --- a/app/api/ytdl.py +++ b/app/api/ytdl.py @@ -1,7 +1,6 @@ import asyncio import os import time -from urllib.parse import urlparse import yt_dlp @@ -40,19 +39,18 @@ 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: + if "/playlist" in self.url or "/live" in self.url or os.path.basename(self.url).startswith("@"): return self._opts["format"] = "bv[ext=mp4][res=480]+ba[ext=m4a]/b[ext=mp4]" - if "shorts" in self.url: + if "/shorts" in self.url: self._opts["format"] = "bv[ext=mp4][res=720]+ba[ext=m4a]/b[ext=mp4]" yt_obj = yt_dlp.YoutubeDL(self._opts) info = yt_obj.extract_info(self.url, download=False) - if info.get("duration", 0) >= 300: + + if not info or info.get("duration", 0) >= 300: return await asyncio.to_thread(yt_obj.download, self.url) diff --git a/app/core/MediaHandler.py b/app/core/MediaHandler.py index 787eae5..578b9c1 100644 --- a/app/core/MediaHandler.py +++ b/app/core/MediaHandler.py @@ -14,7 +14,6 @@ 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, @@ -136,9 +135,9 @@ class ExtractAndSendMedia: [os.rename(file_, file_ + ".png") for file_ in glob.glob(f"{path}/*.webp")] images, videos, animations = [], [], [] for file in glob.glob(f"{path}/*"): - if file.endswith((".png", ".jpg", ".jpeg")): + if file.lower().endswith((".png", ".jpg", ".jpeg")): images.append(InputMediaPhoto(file, caption=caption, has_spoiler=self.spoiler)) - if file.endswith((".mp4", ".mkv", ".webm")): + if file.lower().endswith((".mp4", ".mkv", ".webm")): has_audio = await shell.check_audio(file) if not has_audio: animations.append(file) @@ -150,11 +149,12 @@ class ExtractAndSendMedia: images, videos, animations = [], [], [] downloads = await asyncio.gather(*[aiohttp_tools.in_memory_dl(url) for url in urls]) for file_obj in downloads: - if file_obj.name.endswith((".png", ".jpg", ".jpeg")): + name = file_obj.name.lower() + if name.endswith((".png", ".jpg", ".jpeg")): images.append(InputMediaPhoto(file_obj, caption=caption, has_spoiler=self.spoiler)) - if file_obj.name.endswith((".mp4", ".mkv", ".webm")): + if name.endswith((".mp4", ".mkv", ".webm")): videos.append(InputMediaVideo(file_obj, caption=caption, has_spoiler=self.spoiler)) - if file_obj.name.endswith(".gif"): + if name.endswith(".gif"): animations.append(file_obj) return await self.make_chunks(images, videos, animations) diff --git a/app/core/aiohttp_tools.py b/app/core/aiohttp_tools.py index 407ecf5..cfb17bf 100755 --- a/app/core/aiohttp_tools.py +++ b/app/core/aiohttp_tools.py @@ -15,7 +15,7 @@ async def session_switch(): await SESSION.close() -async def get_json(url: str, headers: dict = None, params: dict = None, retry: bool = False, json_: bool = False, timeout: int = 10): +async def get_json(url: str, headers: dict = None, params: dict = None, json_: bool = False, timeout: int = 10): try: async with SESSION.get(url=url, headers=headers, params=params, timeout=timeout) as ses: if json_: @@ -31,8 +31,8 @@ async def in_memory_dl(url: str): async with SESSION.get(url) as remote_file: bytes_data = await remote_file.read() file = BytesIO(bytes_data) - name = os.path.basename(urlparse(url).path.rstrip("/")) - if name.endswith(".webp"): + name = os.path.basename(urlparse(url).path.rstrip("/")).lower() + if name.endswith((".webp", ".heic")): name = name + ".jpg" if name.endswith(".webm"): name = name + ".mp4" diff --git a/app/core/client.py b/app/core/client.py old mode 100755 new mode 100644 index c876d79..bb73d5a --- a/app/core/client.py +++ b/app/core/client.py @@ -54,15 +54,19 @@ class BOT(Client): await aiohttp_tools.session_switch() async def edit_restart_msg(self): - if (restart_msg := os.environ.get("RESTART_MSG")) and (restart_chat := os.environ.get("RESTART_CHAT")): + restart_msg = os.environ.get("RESTART_MSG") + restart_chat = os.environ.get("RESTART_CHAT") + if restart_msg and restart_chat: await super().get_chat(int(restart_chat)) await super().edit_message_text(chat_id=int(restart_chat), message_id=int(restart_msg), text="#Social-dl\n__Started__") os.environ.pop("RESTART_MSG", "") os.environ.pop("RESTART_CHAT", "") async def import_modules(self): - for module_ in glob.glob("app/*/*.py"): - importlib.import_module(os.path.splitext(module_.replace("/", "."))[0]) + for py_module in glob.glob("app/**/*.py", recursive=True): + name = os.path.splitext(py_module)[0] + py_name = name.replace("/", ".") + importlib.import_module(py_name) async def log(self, text, chat=None, func=None, name="log.txt",disable_web_page_preview=True): if chat or func: @@ -75,13 +79,18 @@ class BOT(Client): os.execl(sys.executable, sys.executable, "-m", "app") async def set_filter_list(self): - if chats_id := Config.AUTO_DL_MESSAGE_ID: + chats_id = Config.AUTO_DL_MESSAGE_ID + blocked_id = Config.BLOCKED_USERS_MESSAGE_ID + users = Config.USERS_MESSAGE_ID + + if chats_id: Config.CHATS = json.loads((await super().get_messages(Config.LOG_CHAT, chats_id)).text) - if blocked_id := Config.BLOCKED_USERS_MESSAGE_ID: + if blocked_id: Config.BLOCKED_USERS = json.loads((await super().get_messages(Config.LOG_CHAT, blocked_id)).text) - if users := Config.USERS_MESSAGE_ID: + if users: Config.USERS = json.loads((await super().get_messages(Config.LOG_CHAT, users)).text) + async def send_message(self, chat_id, text, name: str = "output.txt", **kwargs): if len(str(text)) < 4096: return Message.parse_message((await super().send_message(chat_id=chat_id, text=text, **kwargs))) diff --git a/app/core/filters.py b/app/core/filters.py index 649c440..83111d4 100644 --- a/app/core/filters.py +++ b/app/core/filters.py @@ -6,14 +6,19 @@ from app import Config from app.core.MediaHandler import url_map -def DYNAMIC_CHAT_FILTER(_, __, message): - if not message.text or not message.text.startswith("https"): +def Dynamic_Chat_Filter(_, __, message): + if ( + not message.text + or not message.text.startswith("https") + or message.chat.id not in Config.CHATS + or message.forward_from_chat + ): return False - chat_check = message.chat.id in Config.CHATS - user_check = True - if user := message.from_user: - user_check = user.id not in Config.BLOCKED_USERS and not user.is_bot - return bool(chat_check and user_check and check_for_urls(message.text.split())) + user = message.from_user + if user and ( user.id in Config.BLOCKED_USERS or user.is_bot ): + return False + url_check = check_for_urls(message.text.split()) + return bool(url_check) def check_for_urls(text_list): @@ -26,16 +31,21 @@ def check_for_urls(text_list): return True -def DYNAMIC_CMD_FILTER(_, __, message): - if not message.text or not message.text.startswith(Config.TRIGGER): +def Dynamic_Cmd_Filter(_, __, message): + if ( + not message.text + or not message.text.startswith(Config.TRIGGER) + or not message.from_user + or message.from_user.id not in Config.USERS + ): return False - cmd_check = message.text.split(maxsplit=1)[0].replace(Config.TRIGGER, "", 1) in Config.CMD_DICT - user_check = False - if user := message.from_user: - user_check = user.id in Config.USERS - reaction_check = bool(not message.reactions) - return bool(cmd_check and user_check and reaction_check) + + start_str = message.text.split(maxsplit=1)[0] + cmd = start_str.replace(Config.TRIGGER, "", 1) + cmd_check = cmd in Config.CMD_DICT + reaction_check = not message.reactions + return bool(cmd_check and reaction_check) -chat_filter = _filters.create(DYNAMIC_CHAT_FILTER) -user_filter = _filters.create(DYNAMIC_CMD_FILTER) +chat_filter = _filters.create(Dynamic_Chat_Filter) +user_filter = _filters.create(Dynamic_Cmd_Filter) diff --git a/app/core/shell.py b/app/core/shell.py index 2bd3d67..eb3989d 100755 --- a/app/core/shell.py +++ b/app/core/shell.py @@ -3,9 +3,10 @@ import os async def take_ss(video: str, path: str): - await run_shell_cmd(f'''ffmpeg -hide_banner -loglevel error -ss 0.1 -i "{video}" -vframes 1 "{path}/i.png"''') - if os.path.isfile(path + "/i.png"): - return path + "/i.png" + thumb = f"{path}/i.png" + await run_shell_cmd(f'''ffmpeg -hide_banner -loglevel error -ss 0.1 -i "{video}" -vframes 1 "{thumb}"''') + if os.path.isfile(thumb): + return thumb async def check_audio(file): diff --git a/app/plugins/loader.py b/app/plugins/loader.py index 2cb01a7..6649ba3 100644 --- a/app/plugins/loader.py +++ b/app/plugins/loader.py @@ -12,11 +12,13 @@ async def loader(bot, message): or not message.replied.document.file_name.endswith(".py") ): return await message.reply("reply to a plugin.") + reply = await message.reply("Loading....") file_name = message.replied.document.file_name.rstrip(".py") - sys.modules.pop(f"app.temp.{file_name}", None) + reload = sys.modules.pop(f"app.temp.{file_name}", None) + status = "Reloaded" if reload else "Loaded" 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.") + return await reply.edit(str(traceback.format_exc())) + await reply.edit(f"{status} {file_name}.py.") diff --git a/app/plugins/tgUtils.py b/app/plugins/tgUtils.py index dace3b9..51dc8f1 100644 --- a/app/plugins/tgUtils.py +++ b/app/plugins/tgUtils.py @@ -45,8 +45,6 @@ async def get_ids(bot, message): @bot.add_cmd(cmd="join") async def join_chat(bot, message): chat = message.input - if chat.isdigit(): - chat = int(f"-100{chat}") try: await bot.join_chat(chat) except (KeyError, BadRequest): diff --git a/app/plugins/tools.py b/app/plugins/tools.py index a640083..bd09943 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 import aiohttp_tools as aio +from app.core import aiohttp_tools as aio # isort:skip # Run shell commands diff --git a/req.txt b/req.txt index e69de29..1ac165a 100644 --- a/req.txt +++ b/req.txt @@ -0,0 +1,11 @@ +aiohttp>=3.8.4 +beautifulsoup4>=4.12.2 +Brotli>=1.0.9 +gallery_dl>=1.25.7 +pyrogram>=2.0.106 +python-dotenv==0.21.0 +PyExecJS>=1.5.1 +tenacity>=8.2.2 +tgCrypto>=1.2.3 +yt-dlp>=2023.6.22 +uvloop>=0.17.0 diff --git a/run b/run index 5b1ad16..fec2c3d 100644 --- a/run +++ b/run @@ -11,20 +11,4 @@ web.run_app(app, host='0.0.0.0', port=$API_PORT, reuse_port=True, print=None) fi -requirements=("aiohttp>=3.8.4" -"beautifulsoup4>=4.12.2" -"Brotli>=1.0.9" -"gallery_dl>=1.25.7" -"pyrogram>=2.0.106" -"python-dotenv==0.21.0" -"PyExecJS>=1.5.1" -"tenacity>=8.2.2" -"tgCrypto>=1.2.3" -"yt-dlp>=2023.6.22" -) - -for req in "${requirements[@]}"; do - pip install "${req}"; -done - python3 -m app \ No newline at end of file