From 3bbc4b00715c9530e812e505605e877c4eeedc4c Mon Sep 17 00:00:00 2001 From: thedragonsinn <98635854+thedragonsinn@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:17:20 +0530 Subject: [PATCH] a shit ton of changes not gonna write changelog. --- .gitignore | 1 + app/__init__.py | 16 +-- app/config.py | 17 +-- app/core/__init__.py | 6 +- app/core/client.py | 76 ++++++++++ app/core/client/client.py | 134 ------------------ app/core/{client => }/conversation.py | 56 ++++---- app/core/db.py | 6 +- app/core/decorators/add_cmd.py | 8 +- app/core/{client => handlers}/filters.py | 15 +- app/core/{client => handlers}/handler.py | 36 ++--- app/core/logger.py | 30 ++-- app/core/methods/__init__.py | 2 + app/core/methods/channel_loggers.py | 36 +++++ app/core/methods/send_message.py | 31 ++++ app/core/types/message.py | 55 +++---- app/plugins/admin/ban.py | 4 +- app/plugins/admin/fbans.py | 50 +++---- app/plugins/admin/promote.py | 2 - app/plugins/dev/exec.py | 1 - app/plugins/dev/loader.py | 27 ++-- app/plugins/dev/shell.py | 29 ++-- app/plugins/sudo/commands.py | 2 +- app/plugins/sudo/users.py | 55 +++---- app/plugins/{utils => sys_utils}/cmdinfo.py | 0 app/plugins/{utils => sys_utils}/help.py | 0 app/plugins/{utils => sys_utils}/logs.py | 0 app/plugins/{utils => sys_utils}/ping.py | 0 app/plugins/{utils => sys_utils}/repo.py | 0 app/plugins/{utils => sys_utils}/restart.py | 0 app/plugins/{utils => sys_utils}/update.py | 2 +- app/plugins/{tools => tg_tools}/cancel.py | 0 app/plugins/{tools => tg_tools}/chat.py | 0 app/plugins/{tools => tg_tools}/click.py | 0 app/plugins/{tools => tg_tools}/delete.py | 2 +- .../{tools => tg_tools}/get_message.py | 0 app/plugins/{tools => tg_tools}/kang.py | 16 +-- .../{tools => tg_tools}/pm_n_tag_logger.py | 69 +++------ app/plugins/{tools => tg_tools}/pm_permit.py | 27 ++-- app/plugins/{tools => tg_tools}/reply.py | 2 +- app/plugins/{tools => tg_tools}/respond.py | 0 app/utils/__init__.py | 6 + app/utils/downloader.py | 13 +- app/utils/shell.py | 21 +-- 44 files changed, 423 insertions(+), 430 deletions(-) create mode 100644 app/core/client.py delete mode 100644 app/core/client/client.py rename app/core/{client => }/conversation.py (79%) rename app/core/{client => handlers}/filters.py (85%) rename app/core/{client => handlers}/handler.py (59%) create mode 100644 app/core/methods/__init__.py create mode 100644 app/core/methods/channel_loggers.py create mode 100644 app/core/methods/send_message.py rename app/plugins/{utils => sys_utils}/cmdinfo.py (100%) rename app/plugins/{utils => sys_utils}/help.py (100%) rename app/plugins/{utils => sys_utils}/logs.py (100%) rename app/plugins/{utils => sys_utils}/ping.py (100%) rename app/plugins/{utils => sys_utils}/repo.py (100%) rename app/plugins/{utils => sys_utils}/restart.py (100%) rename app/plugins/{utils => sys_utils}/update.py (97%) rename app/plugins/{tools => tg_tools}/cancel.py (100%) rename app/plugins/{tools => tg_tools}/chat.py (100%) rename app/plugins/{tools => tg_tools}/click.py (100%) rename app/plugins/{tools => tg_tools}/delete.py (94%) rename app/plugins/{tools => tg_tools}/get_message.py (100%) rename app/plugins/{tools => tg_tools}/kang.py (95%) rename app/plugins/{tools => tg_tools}/pm_n_tag_logger.py (73%) rename app/plugins/{tools => tg_tools}/pm_permit.py (87%) rename app/plugins/{tools => tg_tools}/reply.py (94%) rename app/plugins/{tools => tg_tools}/respond.py (100%) create mode 100644 app/utils/__init__.py diff --git a/.gitignore b/.gitignore index 8788524..adebd1e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ __pycache__ .idea/ conf_backup/ logs/ +.mypy_cache diff --git a/app/__init__.py b/app/__init__.py index 3076adf..f10dcbd 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -7,20 +7,14 @@ tracemalloc.start() load_dotenv("config.env") -from app.config import Config # NOQA -from app.core.db import DB, DB_CLIENT, CustomDB # NOQA -from app.core import Message # NOQA - -from app.core.logger import getLogger # NOQA - -LOGGER = getLogger("PLAIN-UB") - -from app.core.client.client import BOT # NOQA - if "com.termux" not in os.environ.get("PATH", ""): import uvloop uvloop.install() -bot: BOT = BOT() + +from app.config import Config # NOQA +from app.core import DB, DB_CLIENT, CustomDB, Message, Convo # NOQA +from app.core.client import BOT, bot # NOQA +from app.core.logger import LOGGER # NOQA diff --git a/app/config.py b/app/config.py index 5f73b60..d544647 100644 --- a/app/config.py +++ b/app/config.py @@ -1,22 +1,20 @@ -import json import os from git import Repo +from app.utils import Str -class _Config: - class CMD: - def __init__(self, cmd: str, func, path: str, doc: str, sudo: bool): + +class _Config(Str): + class CMD(Str): + def __init__(self, cmd: str, func, path: str, sudo: bool): self.cmd = cmd self.func = func self.path: str = path self.dirname: str = os.path.basename(os.path.dirname(path)) - self.doc: str = doc or "Not Documented." + self.doc: str = func.__doc__ or "Not Documented." self.sudo: bool = sudo - def __str__(self): - return json.dumps(self.__dict__, indent=4, ensure_ascii=False, default=str) - def __init__(self): self.CMD_DICT: dict[str, _Config.CMD] = {} @@ -64,8 +62,5 @@ class _Config: "UPSTREAM_REPO", "https://github.com/thedragonsinn/plain-ub" ) - def __str__(self): - return json.dumps(self.__dict__, indent=4, ensure_ascii=False, default=str) - Config = _Config() diff --git a/app/core/__init__.py b/app/core/__init__.py index badae98..0309fce 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -1,3 +1,3 @@ -from app.core.client import filters - -from app.core.types.message import Message # NOQA +from app.core.conversation import Conversation as Convo +from app.core.db import DB, DB_CLIENT, CustomDB +from app.core.types.message import Message diff --git a/app/core/client.py b/app/core/client.py new file mode 100644 index 0000000..942f421 --- /dev/null +++ b/app/core/client.py @@ -0,0 +1,76 @@ +import asyncio +import glob +import importlib +import logging +import os +import sys + +from pyrogram import Client, idle +from pyrogram.enums import ParseMode + +from app import DB_CLIENT, Config +from app.core.conversation import Conversation +from app.core.decorators.add_cmd import AddCmd +from app.core.methods import ChannelLogger, SendMessage +from app.utils.aiohttp_tools import aio + +LOGGER = logging.getLogger("PLAIN-UB") + + +def import_modules(): + for py_module in glob.glob(pathname="app/**/[!^_]*.py", recursive=True): + name = os.path.splitext(py_module)[0] + py_name = name.replace("/", ".") + try: + mod = importlib.import_module(py_name) + if hasattr(mod, "init_task"): + Config.INIT_TASKS.append(mod.init_task()) + except Exception as ie: + LOGGER.error(ie, exc_info=True) + + +class BOT(AddCmd, SendMessage, ChannelLogger, Client): + def __init__(self): + super().__init__( + name="bot", + api_id=int(os.environ.get("API_ID")), + api_hash=os.environ.get("API_HASH"), + session_string=os.environ.get("SESSION_STRING").strip(), + parse_mode=ParseMode.DEFAULT, + sleep_threshold=30, + max_concurrent_transmissions=2, + ) + self.log = LOGGER + self.Convo = Conversation + + async def boot(self) -> None: + await super().start() + LOGGER.info("Connected to TG.") + import_modules() + LOGGER.info("Plugins Imported.") + await asyncio.gather(*Config.INIT_TASKS) + Config.INIT_TASKS.clear() + LOGGER.info("Init Tasks Completed.") + await self.log_text(text="Started") + LOGGER.info("Idling...") + await idle() + await self.shut_down() + + @staticmethod + async def shut_down(): + await aio.close() + if Config.MESSAGE_LOGGER_TASK: + Config.MESSAGE_LOGGER_TASK.cancel() + LOGGER.info("DB Closed.") + DB_CLIENT.close() + + async def restart(self, hard=False) -> None: + await self.shut_down() + await super().stop(block=False) + if hard: + os.execl("/bin/bash", "/bin/bash", "run") + LOGGER.info("Restarting...") + os.execl(sys.executable, sys.executable, "-m", "app") + + +bot: BOT = BOT() diff --git a/app/core/client/client.py b/app/core/client/client.py deleted file mode 100644 index 2f9ae7d..0000000 --- a/app/core/client/client.py +++ /dev/null @@ -1,134 +0,0 @@ -import asyncio -import glob -import importlib -import os -import sys -import traceback -from io import BytesIO - -from pyrogram import Client, filters, idle -from pyrogram.enums import ParseMode -from pyrogram.types import Message as Msg - -from app import DB_CLIENT, LOGGER, Config, Message -from app.core.decorators.add_cmd import AddCmd -from app.utils.aiohttp_tools import aio - - -def import_modules(): - for py_module in glob.glob(pathname="app/**/[!^_]*.py", recursive=True): - name = os.path.splitext(py_module)[0] - py_name = name.replace("/", ".") - try: - mod = importlib.import_module(py_name) - if hasattr(mod, "init_task"): - Config.INIT_TASKS.append(mod.init_task()) - except BaseException: - LOGGER.error(traceback.format_exc()) - - -class BOT(Client, AddCmd): - def __init__(self): - super().__init__( - name="bot", - api_id=int(os.environ.get("API_ID")), - api_hash=os.environ.get("API_HASH"), - session_string=os.environ.get("SESSION_STRING"), - in_memory=True, - parse_mode=ParseMode.DEFAULT, - sleep_threshold=30, - max_concurrent_transmissions=2, - ) - from app.core.client.conversation import Conversation - - self.Convo = Conversation - self.log = LOGGER - - async def get_response( - self, chat_id: int, filters: filters.Filter = None, timeout: int = 8 - ) -> Message | None: - try: - async with self.Convo( - chat_id=chat_id, filters=filters, timeout=timeout - ) as convo: - response: Message | None = await convo.get_response() - return response - except TimeoutError: - return - - async def boot(self) -> None: - await super().start() - LOGGER.info("Connected to TG.") - import_modules() - LOGGER.info("Plugins Imported.") - await asyncio.gather(*Config.INIT_TASKS) - Config.INIT_TASKS.clear() - LOGGER.info("Init Tasks Completed.") - await self.log_text(text="Started") - LOGGER.info("Idling...") - await idle() - await self.shut_down() - - @staticmethod - async def shut_down(): - await aio.close() - if Config.MESSAGE_LOGGER_TASK: - Config.MESSAGE_LOGGER_TASK.cancel() - LOGGER.info("DB Closed.") - DB_CLIENT.close() - - async def log_text( - self, - text, - name="log.txt", - disable_web_page_preview=True, - parse_mode=ParseMode.HTML, - type: str = "", - ) -> Message | Msg: - if type: - if hasattr(LOGGER, type): - getattr(LOGGER, type)(text) - text = f"#{type.upper()}\n{text}" - - return (await self.send_message( - chat_id=Config.LOG_CHAT, - text=text, - name=name, - disable_web_page_preview=disable_web_page_preview, - parse_mode=parse_mode, - )) # fmt:skip - - @staticmethod - async def log_message(message: Message | Msg): - return (await message.copy(chat_id=Config.LOG_CHAT)) # fmt: skip - - async def restart(self, hard=False) -> None: - await self.shut_down() - await super().stop(block=False) - if hard: - os.execl("/bin/bash", "/bin/bash", "run") - LOGGER.info("Restarting...") - os.execl(sys.executable, sys.executable, "-m", "app") - - async def send_message( - self, - chat_id: int | str, - text, - name: str = "output.txt", - disable_web_page_preview: bool = False, - **kwargs, - ) -> Message | Msg: - text = str(text) - if len(text) < 4096: - message = await super().send_message( - chat_id=chat_id, - text=text, - disable_web_page_preview=disable_web_page_preview, - **kwargs, - ) - return Message.parse(message=message) - doc = BytesIO(bytes(text, encoding="utf-8")) - doc.name = name - return (await super().send_document( - chat_id=chat_id, document=doc, **kwargs - )) # fmt: skip diff --git a/app/core/client/conversation.py b/app/core/conversation.py similarity index 79% rename from app/core/client/conversation.py rename to app/core/conversation.py index d7c9aa0..befd1b8 100644 --- a/app/core/client/conversation.py +++ b/app/core/conversation.py @@ -1,11 +1,12 @@ import asyncio -import json from pyrogram.filters import Filter from pyrogram.types import Message +from app.utils import Str -class Conversation: + +class Conversation(Str): CONVO_DICT: dict[int, "Conversation"] = {} class DuplicateConvo(Exception): @@ -13,20 +14,41 @@ class Conversation: super().__init__(f"Conversation already started with {chat} ") def __init__( - self, chat_id: int | str, filters: Filter | None = None, timeout: int = 10 + self, + client, + chat_id: int | str, + filters: Filter | None = None, + timeout: int = 10, ): self.chat_id = chat_id + self._client = client self.filters = filters - self.timeout = timeout self.response = None self.responses: list = [] + self.timeout = timeout self.set_future() - from app import bot - self._client = bot + async def __aenter__(self) -> "Conversation": + if isinstance(self.chat_id, str): + self.chat_id = (await self._client.get_chat(self.chat_id)).id + if self.chat_id in Conversation.CONVO_DICT.keys(): + raise self.DuplicateConvo(self.chat_id) + Conversation.CONVO_DICT[self.chat_id] = self + return self - def __str__(self): - return json.dumps(self.__dict__, indent=4, ensure_ascii=False, default=str) + async def __aexit__(self, exc_type, exc_val, exc_tb): + Conversation.CONVO_DICT.pop(self.chat_id, None) + if not self.response.done(): + self.response.cancel() + + @classmethod + async def get_resp(cls, client, *args, **kwargs) -> Message | None: + try: + async with cls(*args, client=client, **kwargs) as convo: + response: Message | None = await convo.get_response() + return response + except TimeoutError: + return def set_future(self, *args, **kwargs): future = asyncio.Future() @@ -36,7 +58,7 @@ class Conversation: async def get_response(self, timeout: int | None = None) -> Message | None: try: resp_future: asyncio.Future.result = await asyncio.wait_for( - self.response, timeout=timeout or self.timeout + fut=self.response, timeout=timeout or self.timeout ) return resp_future except asyncio.TimeoutError: @@ -76,19 +98,3 @@ class Conversation: response = await self.get_response(timeout=timeout or self.timeout) return message, response return message - - async def __aenter__(self) -> "Conversation": - if isinstance(self.chat_id, str): - self.chat_id = (await self._client.get_chat(self.chat_id)).id - if ( - self.chat_id in Conversation.CONVO_DICT.keys() - and Conversation.CONVO_DICT[self.chat_id].filters == self.filters - ): - raise self.DuplicateConvo(self.chat_id) - Conversation.CONVO_DICT[self.chat_id] = self - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - Conversation.CONVO_DICT.pop(self.chat_id, None) - if not self.response.done(): - self.response.cancel() diff --git a/app/core/db.py b/app/core/db.py index 018d99f..3539cb8 100644 --- a/app/core/db.py +++ b/app/core/db.py @@ -4,15 +4,17 @@ import dns.resolver from motor.core import AgnosticClient, AgnosticDatabase from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorCollection +from app.utils import Str + dns.resolver.default_resolver = dns.resolver.Resolver(configure=False) dns.resolver.default_resolver.nameservers = ["8.8.8.8"] -DB_CLIENT: AgnosticClient = AsyncIOMotorClient(os.environ.get("DB_URL")) +DB_CLIENT: AgnosticClient = AsyncIOMotorClient(os.environ.get("DB_URL").strip()) DB: AgnosticDatabase = DB_CLIENT["plain_ub"] -class CustomDB(AsyncIOMotorCollection): +class CustomDB(AsyncIOMotorCollection, Str): def __init__(self, collection_name: str): super().__init__(database=DB, name=collection_name) diff --git a/app/core/decorators/add_cmd.py b/app/core/decorators/add_cmd.py index 361ec0a..d9ed5e9 100644 --- a/app/core/decorators/add_cmd.py +++ b/app/core/decorators/add_cmd.py @@ -15,15 +15,11 @@ class AddCmd: if isinstance(cmd, list): for _cmd in cmd: Config.CMD_DICT[_cmd] = Config.CMD( - cmd=_cmd, - func=func, - path=path, - doc=func.__doc__, - sudo=allow_sudo, + cmd=_cmd, func=func, path=path, sudo=allow_sudo ) else: Config.CMD_DICT[cmd] = Config.CMD( - cmd=cmd, func=func, path=path, doc=func.__doc__, sudo=allow_sudo + cmd=cmd, func=func, path=path, sudo=allow_sudo ) wrapper() diff --git a/app/core/client/filters.py b/app/core/handlers/filters.py similarity index 85% rename from app/core/client/filters.py rename to app/core/handlers/filters.py index 25c9ca2..953a3a1 100644 --- a/app/core/client/filters.py +++ b/app/core/handlers/filters.py @@ -2,7 +2,7 @@ from pyrogram import filters as _filters from pyrogram.types import Message from app import Config -from app.core.client.conversation import Conversation +from app.core.conversation import Conversation convo_filter = _filters.create( lambda _, __, message: (message.chat.id in Conversation.CONVO_DICT.keys()) @@ -13,11 +13,14 @@ convo_filter = _filters.create( def cmd_check(message: Message, trigger: str, sudo: bool = False) -> bool: start_str = message.text.split(maxsplit=1)[0] cmd = start_str.replace(trigger, "", 1) - return ( - bool(cmd in Config.SUDO_CMD_LIST) - if sudo - else bool(cmd in Config.CMD_DICT.keys()) - ) + cmd_obj = Config.CMD_DICT.get(cmd) + if not cmd_obj: + return False + if sudo: + in_sudo = cmd in Config.SUDO_CMD_LIST + has_access = Config.CMD_DICT[cmd].sudo + return in_sudo and has_access + return True def basic_check(message: Message): diff --git a/app/core/client/handler.py b/app/core/handlers/handler.py similarity index 59% rename from app/core/client/handler.py rename to app/core/handlers/handler.py index 88ad2bd..ce5babf 100644 --- a/app/core/client/handler.py +++ b/app/core/handlers/handler.py @@ -1,10 +1,9 @@ import asyncio -import traceback from pyrogram.types import Message as Msg -from app import BOT, Config, bot -from app.core import Message, filters +from app import BOT, Config, Message, bot +from app.core.handlers import filters @bot.on_message( @@ -16,10 +15,15 @@ from app.core import Message, filters async def cmd_dispatcher(bot: BOT, message: Message) -> None: message = Message.parse(message) func = Config.CMD_DICT[message.cmd].func - coro = func(bot, message) - x = await run_coro(coro, message) - if not x and message.is_from_owner: - await message.delete() + task = asyncio.Task(func(bot, message), name=message.task_id) + try: + await task + if message.is_from_owner: + await message.delete() + except asyncio.exceptions.CancelledError: + await bot.log_text(text=f"#Cancelled:\n{message.text}") + except Exception as e: + bot.log.error(e, exc_info=True, extra={"tg_message": message}) message.stop_propagation() @@ -32,21 +36,3 @@ async def convo_handler(bot: BOT, message: Msg): conv_obj.responses.append(message) conv_obj.response.set_result(message) message.continue_propagation() - - -async def run_coro(coro, message: Message) -> None | int: - try: - task = asyncio.Task(coro, name=message.task_id) - await task - except asyncio.exceptions.CancelledError: - await bot.log_text(text=f"#Cancelled:\n{message.text}") - except BaseException: - text = ( - "#Traceback" - f"\nFunction: {coro.__name__}" - f"\nChat: {message.chat.title or message.from_user.first_name}" - f"\nTraceback:" - f"\n
{traceback.format_exc()}
" - ) - await bot.log_text(text=text, type="error") - return 1 diff --git a/app/core/logger.py b/app/core/logger.py index ae69f29..64b53dd 100644 --- a/app/core/logger.py +++ b/app/core/logger.py @@ -1,5 +1,5 @@ -import os import asyncio +import os from logging import ( ERROR, INFO, @@ -11,29 +11,41 @@ from logging import ( handlers, ) -os.makedirs("logs", exist_ok=True) +from app import bot + +os.makedirs(name="logs", exist_ok=True) + +LOGGER = getLogger("PLAIN-UB") class TgErrorHandler(Handler): def emit(self, log_record): - if log_record.levelno < ERROR: - return - from app import bot if not bot.is_connected: - return + return + self.format(log_record) + chat = "" + if hasattr(log_record, "tg_message"): + chat = ( + log_record.tg_message.chat.title + or log_record.tg_message.chat.first_name + ) text = ( f"#{log_record.levelname} #TRACEBACK" + f"\nChat: {chat}" f"\nLine No: {log_record.lineno}" f"\nFunc: {log_record.funcName}" f"\nModule: {log_record.module}" f"\nTime: {log_record.asctime}" - f"\nError Message:\n
{log_record.message}
" + f"\nError Message:\n
{log_record.exc_text or log_record.message}
" ) asyncio.run_coroutine_threadsafe( coro=bot.log_text(text=text, name="traceback.txt"), loop=bot.loop ) +custom_handler = TgErrorHandler() +custom_handler.setLevel(ERROR) + basicConfig( level=INFO, format="[%(levelname)s] [%(asctime)s] [%(name)s] [%(module)s]: %(message)s", @@ -45,10 +57,10 @@ basicConfig( maxBytes=5 * 1024 * 1024, backupCount=2, encoding=None, - delay=0, + delay=False, ), StreamHandler(), - TgErrorHandler(), + custom_handler, }, ) diff --git a/app/core/methods/__init__.py b/app/core/methods/__init__.py new file mode 100644 index 0000000..2bb068b --- /dev/null +++ b/app/core/methods/__init__.py @@ -0,0 +1,2 @@ +from app.core.methods.channel_loggers import ChannelLogger +from app.core.methods.send_message import SendMessage diff --git a/app/core/methods/channel_loggers.py b/app/core/methods/channel_loggers.py new file mode 100644 index 0000000..50cd55b --- /dev/null +++ b/app/core/methods/channel_loggers.py @@ -0,0 +1,36 @@ +import logging + +from pyrogram import Client +from pyrogram.enums import ParseMode + +from app import Config +from app.core.types.message import Message + +LOGGER = logging.getLogger("PLAIN-UB") + + +class ChannelLogger(Client): + async def log_text( + self, + text, + name="log.txt", + disable_web_page_preview=True, + parse_mode=ParseMode.HTML, + type: str = "", + ) -> Message: + if type: + if hasattr(LOGGER, type): + getattr(LOGGER, type)(text) + text = f"#{type.upper()}\n{text}" + + return (await self.send_message( + chat_id=Config.LOG_CHAT, + text=text, + name=name, + disable_web_page_preview=disable_web_page_preview, + parse_mode=parse_mode, + )) # fmt:skip + + @staticmethod + async def log_message(message: Message): + return (await message.copy(chat_id=Config.LOG_CHAT)) # fmt: skip diff --git a/app/core/methods/send_message.py b/app/core/methods/send_message.py new file mode 100644 index 0000000..c002b9f --- /dev/null +++ b/app/core/methods/send_message.py @@ -0,0 +1,31 @@ +from io import BytesIO + +from pyrogram import Client + +from app.core.types.message import Message + + +class SendMessage(Client): + async def send_message( + self, + chat_id: int | str, + text, + name: str = "output.txt", + disable_web_page_preview: bool = False, + **kwargs, + ) -> Message: + if not isinstance(text, str): + text = str(text) + if len(text) < 4096: + message = await super().send_message( + chat_id=chat_id, + text=text, + disable_web_page_preview=disable_web_page_preview, + **kwargs, + ) + return Message.parse(message=message) + doc = BytesIO(bytes(text, encoding="utf-8")) + doc.name = name + return (await super().send_document( + chat_id=chat_id, document=doc, **kwargs + )) # fmt: skip diff --git a/app/core/types/message.py b/app/core/types/message.py index f479823..84f5059 100644 --- a/app/core/types/message.py +++ b/app/core/types/message.py @@ -1,12 +1,14 @@ import asyncio from functools import cached_property +from pyrogram.enums import MessageEntityType from pyrogram.errors import MessageDeleteForbidden from pyrogram.filters import Filter from pyrogram.types import Message as Msg from pyrogram.types import User from app import Config +from app.core.conversation import Conversation class Message(Msg): @@ -19,7 +21,7 @@ class Message(Msg): def cmd(self) -> str | None: raw_cmd = self.text_list[0] cmd = raw_cmd.replace(self.trigger, "", 1) - return cmd if cmd in Config.CMD_DICT else None + return cmd if cmd in Config.CMD_DICT.keys() else None @cached_property def flags(self) -> list: @@ -27,7 +29,7 @@ class Message(Msg): @cached_property def flt_input(self) -> str: - split_lines = self.input.split("\n", maxsplit=1) + split_lines = self.input.split(sep="\n", maxsplit=1) split_lines[0] = " ".join( [word for word in split_lines[0].split(" ") if word not in self.flags] ) @@ -91,9 +93,10 @@ class Message(Msg): except MessageDeleteForbidden: pass - async def edit(self, text, del_in: int = 0, block=True, **kwargs) -> "Message": + async def edit( + self, text, del_in: int = 0, block=True, name: str = "output.txt", **kwargs + ) -> "Message": if len(str(text)) < 4096: - kwargs.pop("name", "") task = super().edit_text(text=text, **kwargs) if del_in: reply = await self.async_deleter(task=task, del_in=del_in, block=block) @@ -102,41 +105,41 @@ class Message(Msg): self.text = reply.text else: _, reply = await asyncio.gather( - super().delete(), self.reply(text, **kwargs) + super().delete(), self.reply(text, name=name, **kwargs) ) return reply - async def extract_user_n_reason(self) -> list[User | str | Exception, str | None]: + async def extract_user_n_reason(self) -> tuple[User | str | Exception, str | None]: if self.replied: - return [self.replied.from_user, self.flt_input] - inp_list = self.flt_input.split(maxsplit=1) - if not inp_list: - return [ + return self.replied.from_user, self.flt_input + input_text_list = self.flt_input.split(maxsplit=1) + if not input_text_list: + return ( "Unable to Extract User info.\nReply to a user or input @ | id.", - "", - ] - user = inp_list[0] + None, + ) + user = input_text_list[0] reason = None - if len(inp_list) >= 2: - reason = inp_list[1] + if len(input_text_list) >= 2: + reason = input_text_list[1] + if self.entities: + for entity in self.entities: + if entity == MessageEntityType.MENTION: + return entity.user, reason if user.isdigit(): user = int(user) elif user.startswith("@"): user = user.strip("@") try: - return [await self._client.get_users(user_ids=user), reason] - except Exception as e: - return [e, reason] + return (await self._client.get_users(user_ids=user)), reason + except Exception: + return user, reason async def get_response(self, filters: Filter = None, timeout: int = 8): - try: - async with self._client.Convo( - chat_id=self.chat.id, filters=filters, timeout=timeout - ) as convo: - response: Message | None = await convo.get_response() - return response - except TimeoutError: - return + response: Message | None = await Conversation.get_resp( + client=self._client, chat_id=self.chat.id, filters=filters, timeout=timeout + ) + return response async def log(self): return (await self.copy(Config.LOG_CHAT)) # fmt:skip diff --git a/app/plugins/admin/ban.py b/app/plugins/admin/ban.py index 9bc3ebe..e71327d 100644 --- a/app/plugins/admin/ban.py +++ b/app/plugins/admin/ban.py @@ -23,7 +23,7 @@ async def ban_or_unban(bot: BOT, message: Message) -> None: try: await action await message.reply( - text=f"{message.cmd.capitalize()}ned: {user.mention}\nReason: {reason}." + text=f"{message.cmd.capitalize()}ned: {user.mention}\nReason: {reason}" ) except Exception as e: await message.reply(text=e, del_in=10) @@ -40,7 +40,7 @@ async def kick_user(bot: BOT, message: Message): await asyncio.sleep(1) await bot.unban_chat_member(chat_id=message.chat.id, user_id=user.id) await message.reply( - text=f"{message.cmd.capitalize()}ed: {user.mention}\nReason: {reason}." + text=f"{message.cmd.capitalize()}ed: {user.mention}\nReason: {reason}" ) except Exception as e: await message.reply(text=e, del_in=10) diff --git a/app/plugins/admin/fbans.py b/app/plugins/admin/fbans.py index 8829d71..0365cfd 100644 --- a/app/plugins/admin/fbans.py +++ b/app/plugins/admin/fbans.py @@ -1,8 +1,7 @@ import asyncio -from functools import cached_property from pyrogram import filters -from pyrogram.enums import ChatMemberStatus +from pyrogram.enums import ChatMemberStatus, ChatType from pyrogram.types import Chat, User from app import BOT, Config, CustomDB, Message, bot @@ -10,7 +9,7 @@ from app.utils.helpers import get_name DB = CustomDB("FED_LIST") -BASIC_FILTER = filters.user([609517172, 2059887769]) & ~filters.service # NOQA +BASIC_FILTER = filters.user([609517172, 2059887769]) & ~filters.service FBAN_REGEX = filters.regex( r"(New FedBan|" @@ -26,16 +25,6 @@ FBAN_REGEX = filters.regex( UNFBAN_REGEX = filters.regex(r"(New un-FedBan|I'll give|Un-FedBan)") -class _User(User): - def __init__(self, id): - super().__init__(id=id) - self.first_name = id - - @cached_property - def mention(self) -> str: - return f"{self.id}" - - @bot.add_cmd(cmd="addf") async def add_fed(bot: BOT, message: Message): """ @@ -96,8 +85,12 @@ async def fed_ban(bot: BOT, message: Message): await progress.edit(user) return if not isinstance(user, User): - user = _User(id=message.text_list[1]) - if user.id in [Config.OWNER_ID, *Config.SUDO_USERS]: + user_id = user + user_mention = f"{user_id}" + else: + user_id = user.id + user_mention = user.mention + if user_id in [Config.OWNER_ID, *Config.SUDO_USERS, *Config.SUDO_USERS]: await progress.edit("Cannot Fban Owner/Sudo users.") return proof_str: str = "" @@ -110,17 +103,20 @@ async def fed_ban(bot: BOT, message: Message): reason = f"{reason}{proof_str}" - if message.replied: - me = await bot.get_chat_member(message.chat.id, "me") + if message.replied and not message.chat.type == ChatType.PRIVATE: + me = await bot.get_chat_member(chat_id=message.chat.id, user_id="me") if me.status in {ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR}: await message.replied.reply( - f"!dban {reason}", disable_web_page_preview=True, del_in=3, block=False + text=f"!dban {reason}", + disable_web_page_preview=True, + del_in=3, + block=False, ) await progress.edit("❯❯") total: int = 0 failed: list[str] = [] - fban_cmd: str = f"/fban {user.id} {reason}" + fban_cmd: str = f"/fban {user_id} {reason}" async for fed in DB.find(): chat_id = int(fed["_id"]) total += 1 @@ -139,8 +135,8 @@ async def fed_ban(bot: BOT, message: Message): await progress.edit("You Don't have any feds connected!") return resp_str = ( - f"❯❯❯ FBanned {user.mention}" - f"\nID: {user.id}" + f"❯❯❯ FBanned {user_mention}" + f"\nID: {user_id}" f"\nReason: {reason}" f"\nInitiated in: {message.chat.title or 'PM'}" ) @@ -168,12 +164,16 @@ async def un_fban(bot: BOT, message: Message): await progress.edit(user) return if not isinstance(user, User): - user = _User(id=message.text_list[1]) + user_id = user + user_mention = f"{user_id}" + else: + user_id = user.id + user_mention = user.mention await progress.edit("❯❯") total: int = 0 failed: list[str] = [] - unfban_cmd: str = f"/unfban {user.id} {reason}" + unfban_cmd: str = f"/unfban {user_id} {reason}" async for fed in DB.find(): chat_id = int(fed["_id"]) total += 1 @@ -189,7 +189,9 @@ async def un_fban(bot: BOT, message: Message): if not total: await progress.edit("You Don't have any feds connected!") return - resp_str = f"❯❯❯ Un-FBanned {user.mention}\nID: {user.id}\nReason: {reason}\n" + resp_str = ( + f"❯❯❯ Un-FBanned {user_mention}" f"\nID: {user_id}" f"\nReason: {reason}\n" + ) if failed: resp_str += f"Failed in: {len(failed)}/{total}\n• " + "\n• ".join(failed) else: diff --git a/app/plugins/admin/promote.py b/app/plugins/admin/promote.py index 126d5ea..b83fed9 100644 --- a/app/plugins/admin/promote.py +++ b/app/plugins/admin/promote.py @@ -67,8 +67,6 @@ async def promote_or_demote(bot: BOT, message: Message) -> None: chat_id=message.chat.id, user_id=user.id, privileges=privileges ) if promote: - # Let server promote admin before setting title - # Bot is too fast moment 😂😂😂 await asyncio.sleep(1) await bot.set_administrator_title( chat_id=message.chat.id, user_id=user.id, title=title or "Admin" diff --git a/app/plugins/dev/exec.py b/app/plugins/dev/exec.py index 7006949..a084ad0 100644 --- a/app/plugins/dev/exec.py +++ b/app/plugins/dev/exec.py @@ -62,6 +62,5 @@ if Config.DEV_MODE: cmd="py", func=executor, path=inspect.stack()[0][1], - doc=executor.__doc__, sudo=False, ) diff --git a/app/plugins/dev/loader.py b/app/plugins/dev/loader.py index bc96350..c06e3e6 100644 --- a/app/plugins/dev/loader.py +++ b/app/plugins/dev/loader.py @@ -12,19 +12,29 @@ async def loader(bot: BOT, message: Message) -> Message | None: 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.") + ) and "-r" not in message.flags: + await message.reply("Reply to a Plugin.") + return + if "-r" in message.flags: + plugin = message.flt_input + cmd_module = Config.CMD_DICT.get(plugin) + if not cmd_module: + await message.reply(text="Invalid cmd.") + return + module = str(cmd_module.func.__module__) + else: + file_name: str = os.path.splitext(message.replied.document.file_name)[0] + module = f"app.temp.{file_name}" + await message.replied.download("app/temp/") reply: Message = await message.reply("Loading....") - file_name: str = os.path.splitext(message.replied.document.file_name)[0] - reload = sys.modules.pop(f"app.temp.{file_name}", None) + reload = sys.modules.pop(module, None) status: str = "Reloaded" if reload else "Loaded" - await message.replied.download("app/temp/") try: - importlib.import_module(f"app.temp.{file_name}") - except BaseException: + importlib.import_module(module) + except Exception as e: await reply.edit(str(traceback.format_exc())) return - await reply.edit(f"{status} {file_name}.py.") + await reply.edit(f"{status} {module}") if Config.DEV_MODE: @@ -32,6 +42,5 @@ if Config.DEV_MODE: cmd="load", func=loader, path=inspect.stack()[0][1], - doc=loader.__doc__, sudo=False, ) diff --git a/app/plugins/dev/shell.py b/app/plugins/dev/shell.py index 0dfc32f..b3deae8 100644 --- a/app/plugins/dev/shell.py +++ b/app/plugins/dev/shell.py @@ -11,7 +11,7 @@ async def run_cmd(bot: BOT, message: Message) -> Message | None: cmd: str = message.input.strip() reply: Message = await message.reply("executing...") try: - proc_stdout: str = await asyncio.Task( + proc_stdout: str = await asyncio.create_task( shell.run_shell_cmd(cmd), name=reply.task_id ) except asyncio.exceptions.CancelledError: @@ -21,7 +21,7 @@ async def run_cmd(bot: BOT, message: Message) -> Message | None: # Shell with Live Output -async def live_shell(bot: BOT, message: Message) -> Message | None: +async def live_shell(bot: BOT, message: Message): cmd: str = message.input.strip() reply: Message = await message.reply("`getting live output....`") sub_process: shell.AsyncShell = await shell.AsyncShell.run_cmd(cmd) @@ -30,25 +30,24 @@ async def live_shell(bot: BOT, message: Message) -> Message | None: try: async for stdout in sub_process.get_output(): if output != stdout: - if len(stdout) <= 4096: - await reply.edit( - f"```shell\n{stdout}```", - disable_web_page_preview=True, - parse_mode=ParseMode.MARKDOWN, - ) + await reply.edit( + text=f"```shell\n{stdout}```", + disable_web_page_preview=True, + parse_mode=ParseMode.MARKDOWN, + ) output = stdout if sleep_for >= 6: - sleep_for = 1 - await asyncio.Task(asyncio.sleep(sleep_for), name=reply.task_id) - sleep_for += 1 - return await reply.edit( - f"
~${cmd}\n\n{sub_process.full_std}
", + sleep_for = 2 + await asyncio.create_task(asyncio.sleep(sleep_for), name=reply.task_id) + sleep_for += 2 + await reply.edit( + text=f"
~${cmd}\n\n{sub_process.full_std}
", name="shell.txt", disable_web_page_preview=True, ) except asyncio.exceptions.CancelledError: sub_process.cancel() - return await reply.edit(f"`Cancelled....`") + await reply.edit(f"`Cancelled....`") if Config.DEV_MODE: @@ -56,13 +55,11 @@ if Config.DEV_MODE: cmd="shell", func=live_shell, path=inspect.stack()[0][1], - doc=live_shell.__doc__, sudo=False, ) Config.CMD_DICT["sh"] = Config.CMD( cmd="sh", func=run_cmd, path=inspect.stack()[0][1], - doc=run_cmd.__doc__, sudo=False, ) diff --git a/app/plugins/sudo/commands.py b/app/plugins/sudo/commands.py index d75143f..349fe63 100644 --- a/app/plugins/sudo/commands.py +++ b/app/plugins/sudo/commands.py @@ -28,7 +28,7 @@ async def add_scmd(bot: BOT, message: Message): return cmd = message.flt_input response = await message.reply(f"Adding {cmd} to sudo....") - func = Config.CMD_DICT.get(cmd, None) + func = Config.CMD_DICT.get(cmd) if not func: await response.edit(text=f"{cmd} not a valid command.", del_in=10) return diff --git a/app/plugins/sudo/users.py b/app/plugins/sudo/users.py index 0a774ca..7684095 100644 --- a/app/plugins/sudo/users.py +++ b/app/plugins/sudo/users.py @@ -3,7 +3,6 @@ import asyncio from pyrogram.types import User from app import BOT, Config, CustomDB, Message, bot -from app.plugins.admin.fbans import _User from app.utils.helpers import extract_user_data, get_name SUDO = CustomDB("COMMON_SETTINGS") @@ -53,23 +52,22 @@ async def add_sudo(bot: BOT, message: Message) -> Message | None: """ response = await message.reply("Extracting User info...") user, _ = await message.extract_user_n_reason() - if isinstance(user, str): - await response.edit(user) - return if not isinstance(user, User): - user: _User = _User(id=message.text_list[1]) - config, text = ( - (Config.SUPERUSERS, "Super Users") - if "-su" in message.flags - else (Config.SUDO_USERS, "Sudo Users.") - ) - if user.id in config: + await response.edit("unable to extract user info.") + return + if "-su" in message.flags: + add_list, remove_list = Config.SUPERUSERS, Config.SUDO_USERS + text = "Super Users" + else: + add_list, remove_list = Config.SUDO_USERS, Config.SUPERUSERS + text = "Sudo Users" + if user.id in add_list: await response.edit( text=f"{get_name(user)} already in Sudo with same privileges!", del_in=5 ) return response_str = f"#SUDO\n{user.mention} added to {text} List." - config.append(user.id) + add_and_remove(user.id, add_list, remove_list) if "-temp" not in message.flags: await SUDO_USERS.add_data( { @@ -102,21 +100,21 @@ async def remove_sudo(bot: BOT, message: Message) -> Message | None: await response.edit(user) return if not isinstance(user, User): - user: _User = _User(id=message.text_list[1]) - config, text = ( - (Config.SUPERUSERS, "Super Users") - if "-su" in message.flags - else (Config.SUDO_USERS, "Sudo Users.") - ) - if user.id not in config: - await response.edit(text=f"{get_name(user)} not in {text}!", del_in=5) + await response.edit("unable to extract user info.") return - config.remove(user.id) - response_str = f"{user.mention} removed from {text} List" + if user.id not in {*Config.SUDO_USERS, *Config.SUPERUSERS}: + await response.edit(text=f"{get_name(user)} not in Sudo!", del_in=5) + return + if "-su" in message.flags: + response_str = f"{user.mention}'s Super User access is revoked to Sudo only." + add_and_remove(user.id, Config.SUDO_USERS, Config.SUPERUSERS) + else: + add_and_remove(user.id, remove_list=Config.SUPERUSERS) + add_and_remove(user.id, remove_list=Config.SUDO_USERS) + response_str = f"{user.mention}'s access to bot has been removed." if "-temp" not in message.flags: if "-su" in message.flags: - response_str += " and added to Sudo." - await SUDO_USERS.add_data({"_id": user.id, "super": "-su" in message.flags}) + await SUDO_USERS.add_data({"_id": user.id, "super": False}) else: await SUDO_USERS.delete_data(id=user.id) else: @@ -125,6 +123,15 @@ async def remove_sudo(bot: BOT, message: Message) -> Message | None: await response.log() +def add_and_remove( + u_id: int, add_list: list | None = None, remove_list: list | None = None +): + if add_list is not None and u_id not in add_list: + add_list.append(u_id) + if remove_list is not None and u_id in remove_list: + remove_list.remove(u_id) + + @bot.add_cmd(cmd="vsudo") async def sudo_list(bot: BOT, message: Message): """ diff --git a/app/plugins/utils/cmdinfo.py b/app/plugins/sys_utils/cmdinfo.py similarity index 100% rename from app/plugins/utils/cmdinfo.py rename to app/plugins/sys_utils/cmdinfo.py diff --git a/app/plugins/utils/help.py b/app/plugins/sys_utils/help.py similarity index 100% rename from app/plugins/utils/help.py rename to app/plugins/sys_utils/help.py diff --git a/app/plugins/utils/logs.py b/app/plugins/sys_utils/logs.py similarity index 100% rename from app/plugins/utils/logs.py rename to app/plugins/sys_utils/logs.py diff --git a/app/plugins/utils/ping.py b/app/plugins/sys_utils/ping.py similarity index 100% rename from app/plugins/utils/ping.py rename to app/plugins/sys_utils/ping.py diff --git a/app/plugins/utils/repo.py b/app/plugins/sys_utils/repo.py similarity index 100% rename from app/plugins/utils/repo.py rename to app/plugins/sys_utils/repo.py diff --git a/app/plugins/utils/restart.py b/app/plugins/sys_utils/restart.py similarity index 100% rename from app/plugins/utils/restart.py rename to app/plugins/sys_utils/restart.py diff --git a/app/plugins/utils/update.py b/app/plugins/sys_utils/update.py similarity index 97% rename from app/plugins/utils/update.py rename to app/plugins/sys_utils/update.py index 47a401d..838b2a9 100644 --- a/app/plugins/utils/update.py +++ b/app/plugins/sys_utils/update.py @@ -3,7 +3,7 @@ import asyncio from git import Repo from app import BOT, Config, Message, bot -from app.plugins.utils.restart import restart +from app.plugins.sys_utils.restart import restart async def get_commits(repo: Repo) -> str | None: diff --git a/app/plugins/tools/cancel.py b/app/plugins/tg_tools/cancel.py similarity index 100% rename from app/plugins/tools/cancel.py rename to app/plugins/tg_tools/cancel.py diff --git a/app/plugins/tools/chat.py b/app/plugins/tg_tools/chat.py similarity index 100% rename from app/plugins/tools/chat.py rename to app/plugins/tg_tools/chat.py diff --git a/app/plugins/tools/click.py b/app/plugins/tg_tools/click.py similarity index 100% rename from app/plugins/tools/click.py rename to app/plugins/tg_tools/click.py diff --git a/app/plugins/tools/delete.py b/app/plugins/tg_tools/delete.py similarity index 94% rename from app/plugins/tools/delete.py rename to app/plugins/tg_tools/delete.py index 736d442..03f1275 100644 --- a/app/plugins/tools/delete.py +++ b/app/plugins/tg_tools/delete.py @@ -1,6 +1,6 @@ from app import BOT, bot from app.core import Message -from app.plugins.tools.get_message import parse_link +from app.plugins.tg_tools.get_message import parse_link @bot.add_cmd(cmd="del") diff --git a/app/plugins/tools/get_message.py b/app/plugins/tg_tools/get_message.py similarity index 100% rename from app/plugins/tools/get_message.py rename to app/plugins/tg_tools/get_message.py diff --git a/app/plugins/tools/kang.py b/app/plugins/tg_tools/kang.py similarity index 95% rename from app/plugins/tools/kang.py rename to app/plugins/tg_tools/kang.py index a3cd2ca..dae9698 100644 --- a/app/plugins/tools/kang.py +++ b/app/plugins/tg_tools/kang.py @@ -42,7 +42,7 @@ async def kang_sticker(bot: BOT, message: Message): text=f"Kanged: here" ) return - async with bot.Convo(chat_id="stickers", timeout=60) as convo: + async with bot.Convo(client=bot, chat_id="stickers", timeout=60) as convo: await convo.send_message(text="/addsticker", get_response=True, timeout=5) await convo.send_message(text=pack_name, get_response=True, timeout=5) if kwargs.get("sticker"): @@ -66,7 +66,7 @@ async def kang_sticker(bot: BOT, message: Message): async def create_n_kang( kwargs: dict, pack_title: str, pack_name: str, message: Message ): - async with bot.Convo(chat_id="stickers", timeout=60) as convo: + async with bot.Convo(client=bot, chat_id="stickers", timeout=60) as convo: await convo.send_message(text=kwargs["cmd"], get_response=True, timeout=5) await convo.send_message(text=pack_title, get_response=True, timeout=5) if kwargs.get("sticker"): @@ -177,11 +177,7 @@ async def video_kang(message: Message, ff=False) -> dict: limit = 50 is_video = True return dict( - cmd=cmd, - limit=limit, - is_video=is_video, - file=output_file, - path=down_dir, + cmd=cmd, limit=limit, is_video=is_video, file=output_file, path=down_dir ) @@ -223,9 +219,5 @@ async def sticker_kang(message: Message) -> dict: input_file: BytesIO = await message.download(in_memory=True) input_file.seek(0) return dict( - emoji=emoji, - file=input_file, - cmd="/newvideo", - is_video=True, - limit=50, + emoji=emoji, file=input_file, cmd="/newvideo", is_video=True, limit=50 ) diff --git a/app/plugins/tools/pm_n_tag_logger.py b/app/plugins/tg_tools/pm_n_tag_logger.py similarity index 73% rename from app/plugins/tools/pm_n_tag_logger.py rename to app/plugins/tg_tools/pm_n_tag_logger.py index b048e90..538e9ff 100644 --- a/app/plugins/tools/pm_n_tag_logger.py +++ b/app/plugins/tg_tools/pm_n_tag_logger.py @@ -2,7 +2,7 @@ import asyncio from collections import defaultdict from pyrogram import filters -from pyrogram.enums import ChatType, MessageEntityType, ParseMode +from pyrogram.enums import ChatType, MessageEntityType from pyrogram.errors import MessageIdInvalid from app import BOT, Config, CustomDB, Message, bot @@ -158,7 +158,15 @@ async def log_pm(message: Message, log_info: bool): try: await message.forward(Config.MESSAGE_LOGGER_CHAT) except MessageIdInvalid: - await log_deleted_message(message) + notice = ( + f"{message.from_user.mention} [{message.from_user.id}] deleted this message." + f"\n\n---\n\n" + f"Message: \n{message.chat.title or message.chat.first_name} ({message.chat.id})" + f"\n\n---\n\n" + f"{notice}Caption:\n{message.caption or 'No Caption in media.'}" + ) + + await message.copy(Config.MESSAGE_LOGGER_CHAT, caption=notice) async def log_chat(message: Message): @@ -166,58 +174,25 @@ async def log_chat(message: Message): mention, u_id = message.sender_chat.title, message.sender_chat.id else: mention, u_id = message.from_user.mention, message.from_user.id + notice = ( + f"{mention} [{u_id}] deleted this message." + f"\n\n---\n\n" + f"Message: \n{message.chat.title or message.chat.first_name} ({message.chat.id})" + f"\n\n---\n\n" + f"{notice}Caption:\n{message.caption or 'No Caption in media.'}" + ) + if message.reply_to_message: try: await message.reply_to_message.forward(Config.MESSAGE_LOGGER_CHAT) except MessageIdInvalid: - await log_deleted_message(message.reply_to_message, data=(mention, u_id)) + await message.reply_to_message.copy( + Config.MESSAGE_LOGGER_CHAT, caption=notice + ) try: logged = await message.forward(Config.MESSAGE_LOGGER_CHAT) await logged.reply( text=f"#TAG\n{mention} [{u_id}]\nMessage: \n{message.chat.title} ({message.chat.id})", ) except MessageIdInvalid: - await log_deleted_message(message, data=(mention, u_id)) - - -async def log_deleted_message(message: Message, data: tuple | None = None): - if data: - mention, u_id = data - else: - mention, u_id = message.from_user.mention, message.from_user.id - notice = f"{mention} [{u_id}] deleted this message.\n\n---\n\nMessage: \n{message.chat.title or message.chat.first_name} ({message.chat.id})\n\n---\n\n" - if not message.media: - await bot.send_message( - chat_id=Config.MESSAGE_LOGGER_CHAT, - text=f"{notice}Text:\n{message.text}", - disable_web_page_preview=True, - parse_mode=ParseMode.HTML, - ) - return - kwargs = dict( - chat_id=Config.MESSAGE_LOGGER_CHAT, - caption=f"{notice}Caption:\n{message.caption or 'No Caption in media.'}", - parse_mode=ParseMode.HTML, - ) - if message.photo: - await bot.send_photo(**kwargs, photo=message.photo.file_id) - elif message.audio: - await bot.send_audio(**kwargs, audio=message.audio.file_id) - elif message.animation: - await bot.send_animation( - **kwargs, animation=message.animation.file_id, unsave=True - ) - elif message.document: - await bot.send_document( - **kwargs, document=message.document.file_id, force_document=True - ) - elif message.video: - await bot.send_video(**kwargs, video=message.video.file_id) - elif message.voice: - await bot.send_voice(**kwargs, voice=message.voice.file_id) - elif message.sticker: - await bot.send_sticker( - chat_id=Config.MESSAGE_LOGGER_CHAT, sticker=message.sticker.file_id - ) - else: - await bot.send_message(chat_id=Config.MESSAGE_LOGGER_CHAT, text=str(message)) + await message.copy(Config.MESSAGE_LOGGER_CHAT, caption=notice) diff --git a/app/plugins/tools/pm_permit.py b/app/plugins/tg_tools/pm_permit.py similarity index 87% rename from app/plugins/tools/pm_permit.py rename to app/plugins/tg_tools/pm_permit.py index 12899b6..f06c3dc 100644 --- a/app/plugins/tools/pm_permit.py +++ b/app/plugins/tg_tools/pm_permit.py @@ -1,4 +1,5 @@ import asyncio +from collections import defaultdict from pyrogram import filters from pyrogram.enums import ChatType @@ -16,15 +17,12 @@ allowed_filter = filters.create(lambda _, __, m: m.chat.id in ALLOWED_USERS) guard_check = filters.create(lambda _, __, ___: Config.PM_GUARD) -RECENT_USERS: dict = {} +RECENT_USERS: dict = defaultdict(int) async def init_task(): guard = await PM_GUARD.find_one({"_id": "guard_switch"}) - if not guard: - return - global ALLOWED_USERS - ALLOWED_USERS = [user_id["_id"] async for user_id in PM_USERS.find()] + [ALLOWED_USERS.append(user_id["_id"]) async for user_id in PM_USERS.find()] Config.PM_GUARD = guard["value"] @@ -36,7 +34,6 @@ async def init_task(): ) async def handle_new_pm(bot: BOT, message: Message): user_id = message.from_user.id - RECENT_USERS[user_id] = RECENT_USERS.get(user_id, 0) if RECENT_USERS[user_id] == 0: await bot.log_text( text=f"#PMGUARD\n{message.from_user.mention} [{user_id}] has messaged you.", @@ -66,9 +63,11 @@ async def handle_new_pm(bot: BOT, message: Message): ) async def auto_approve(bot: BOT, message: Message): message = Message.parse(message=message) - await message.reply("Auto-Approved to PM.", del_in=5) ALLOWED_USERS.append(message.chat.id) - await PM_USERS.insert_one({"_id": message.chat.id}) + await asyncio.gather( + PM_USERS.insert_one({"_id": message.chat.id}), + message.reply("Auto-Approved to PM.", del_in=5), + ) @bot.add_cmd(cmd="pmguard") @@ -91,12 +90,16 @@ async def pmguard(bot: BOT, message: Message): PM_GUARD.add_data({"_id": "guard_switch", "value": value}), message.reply(text=f"PM Guard is enabled: {value}!", del_in=8), ) - await init_task() @bot.add_cmd(cmd=["a", "allow"]) async def allow_pm(bot: BOT, message: Message): - user_id, name = get_user_name(message) + """ + CMD: A | ALLOW + INFO: Approve a User to PM. + USAGE: .a|.allow [reply to a user or in pm] + """ + user_id, name = get_userID_name(message) if not user_id: await message.reply( "Unable to extract User to allow.\nGive user id | Reply to a user | use in PM." @@ -115,7 +118,7 @@ async def allow_pm(bot: BOT, message: Message): @bot.add_cmd(cmd="nopm") async def no_pm(bot: BOT, message: Message): - user_id, name = get_user_name(message) + user_id, name = get_userID_name(message) if not user_id: await message.reply( "Unable to extract User to Dis-allow.\nGive user id | Reply to a user | use in PM." @@ -131,7 +134,7 @@ async def no_pm(bot: BOT, message: Message): ) -def get_user_name(message: Message) -> tuple: +def get_userID_name(message: Message) -> tuple: if message.flt_input and message.flt_input.isdigit(): user_id = int(message.flt_input) return user_id, user_id diff --git a/app/plugins/tools/reply.py b/app/plugins/tg_tools/reply.py similarity index 94% rename from app/plugins/tools/reply.py rename to app/plugins/tg_tools/reply.py index cbfa327..7862fad 100644 --- a/app/plugins/tools/reply.py +++ b/app/plugins/tg_tools/reply.py @@ -1,5 +1,5 @@ from app import BOT, Message, bot -from app.plugins.tools.get_message import parse_link +from app.plugins.tg_tools.get_message import parse_link @bot.add_cmd(cmd="reply") diff --git a/app/plugins/tools/respond.py b/app/plugins/tg_tools/respond.py similarity index 100% rename from app/plugins/tools/respond.py rename to app/plugins/tg_tools/respond.py diff --git a/app/utils/__init__.py b/app/utils/__init__.py new file mode 100644 index 0000000..13e1996 --- /dev/null +++ b/app/utils/__init__.py @@ -0,0 +1,6 @@ +import json + + +class Str: + def __str__(self): + return json.dumps(self.__dict__, indent=4, ensure_ascii=False, default=str) diff --git a/app/utils/downloader.py b/app/utils/downloader.py index c20d6f8..45ae1d1 100644 --- a/app/utils/downloader.py +++ b/app/utils/downloader.py @@ -1,4 +1,3 @@ -import json import os import re import shutil @@ -9,11 +8,12 @@ import aiohttp from pyrogram.types import Message as Msg from app.core.types.message import Message +from app.utils import Str from app.utils.helpers import progress from app.utils.media_helper import bytes_to_mb, get_filename, get_type -class DownloadedFile: +class DownloadedFile(Str): def __init__( self, name: str, @@ -26,15 +26,8 @@ class DownloadedFile: self.size = size self.type = get_type(path=name) - def __str__(self): - return json.dumps( - self.__dict__, - indent=4, - ensure_ascii=False, - default=str) - -class Download: +class Download(Str): """Download a file in async using aiohttp. Attributes: diff --git a/app/utils/shell.py b/app/utils/shell.py index 0543709..eeba110 100644 --- a/app/utils/shell.py +++ b/app/utils/shell.py @@ -1,6 +1,5 @@ import asyncio import os -from typing import AsyncIterable async def run_shell_cmd(cmd: str) -> str: @@ -35,24 +34,28 @@ async def get_duration(file) -> int: class AsyncShell: - def __init__(self, process: asyncio.create_subprocess_shell): + def __init__( + self, + process: asyncio.create_subprocess_shell, + ): self.process: asyncio.create_subprocess_shell = process self.full_std: str = "" + self.last_line: str = "" self.is_done: bool = False self._task: asyncio.Task | None = None async def read_output(self) -> None: - while True: - line: str = (await self.process.stdout.readline()).decode("utf-8") - if not line: - break - self.full_std += line + async for line in self.process.stdout: + decoded_line = line.decode("utf-8") + self.full_std += decoded_line + self.last_line = decoded_line self.is_done = True await self.process.wait() - async def get_output(self) -> AsyncIterable: + async def get_output(self): while not self.is_done: - yield self.full_std + yield self.full_std if len(self.full_std) < 4000 else self.last_line + await asyncio.sleep(0) def cancel(self) -> None: if not self.is_done: