From da8a697acc34a38bd2489f042754bccae4498cc1 Mon Sep 17 00:00:00 2001 From: thedragonsinn <98635854+thedragonsinn@users.noreply.github.com> Date: Fri, 12 Jan 2024 17:19:42 +0530 Subject: [PATCH] PMGuard and AioHttp session, site changes. --- app/config.py | 2 + app/core/client/client.py | 30 +++++--- app/core/db.py | 10 ++- app/core/logger.py | 1 + app/plugins/dev/exec.py | 3 +- app/plugins/misc/debrid.py | 4 +- app/plugins/misc/song.py | 4 +- app/plugins/sudo/users.py | 8 +- app/plugins/tools/pm_permit.py | 135 +++++++++++++++++++++++++++++++++ app/utils/aiohttp_tools.py | 102 +++++++++++++++---------- run | 13 +--- 11 files changed, 241 insertions(+), 71 deletions(-) create mode 100644 app/plugins/tools/pm_permit.py diff --git a/app/config.py b/app/config.py index 42f9f5a..30eb8c5 100644 --- a/app/config.py +++ b/app/config.py @@ -27,6 +27,8 @@ class _Config: self.LOG_CHAT: int = int(os.environ.get("LOG_CHAT")) + self.PM_GUARD: bool = False + self.REPO = Repo(".") self.SUDO: bool = False diff --git a/app/core/client/client.py b/app/core/client/client.py index f2811f0..68e870a 100644 --- a/app/core/client/client.py +++ b/app/core/client/client.py @@ -12,7 +12,7 @@ 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 import aiohttp_tools +from app.utils.aiohttp_tools import aio def import_modules(): @@ -67,7 +67,7 @@ class BOT(Client, AddCmd): await self.log_text(text="Started") LOGGER.info("Idling...") await idle() - await aiohttp_tools.init_task() + await aio.session.close() LOGGER.info("DB Closed.") DB_CLIENT.close() @@ -92,29 +92,39 @@ class BOT(Client, AddCmd): parse_mode=parse_mode, )) # fmt:skip - async def log_message(self, message: Message | Msg): + @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 aiohttp_tools.init_task() + await aio.session.close() await super().stop(block=False) - LOGGER.info("Closing DB....") + LOGGER.info("Closing DB...") DB_CLIENT.close() if hard: os.remove("logs/app_logs.txt") os.execl("/bin/bash", "/bin/bash", "run") - LOGGER.info("Restarting......") + 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", **kwargs + 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: - return Message.parse_message( - (await super().send_message(chat_id=chat_id, text=text, **kwargs)) + 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=message) doc = BytesIO(bytes(text, encoding="utf-8")) doc.name = name - kwargs.pop("disable_web_page_preview", "") + # fmt:skip return await super().send_document(chat_id=chat_id, document=doc, **kwargs) diff --git a/app/core/db.py b/app/core/db.py index 74d18b3..567c22c 100644 --- a/app/core/db.py +++ b/app/core/db.py @@ -17,13 +17,21 @@ class CustomDB(AsyncIOMotorCollection): super().__init__(database=DB, name=collection_name) async def add_data(self, data: dict) -> None: - found = await self.find_one(data) + """ + :param data: {"_id": db_id, rest of the data} + entry is added or updated if exists. + """ + found = await self.find_one({"_id": data["_id"]}) if not found: await self.insert_one(data) else: await self.update_one({"_id": data.pop("_id")}, {"$set": data}) async def delete_data(self, id: int | str) -> bool | None: + """ + :param id: the db id key to delete. + :return: True if entry was deleted. + """ found = await self.find_one({"_id": id}) if found: await self.delete_one({"_id": id}) diff --git a/app/core/logger.py b/app/core/logger.py index 1df145d..006074c 100644 --- a/app/core/logger.py +++ b/app/core/logger.py @@ -24,3 +24,4 @@ basicConfig( getLogger("pyrogram").setLevel(WARNING) getLogger("httpx").setLevel(WARNING) +getLogger("aiohttp.access").setLevel(WARNING) diff --git a/app/plugins/dev/exec.py b/app/plugins/dev/exec.py index a36d711..dfc3733 100644 --- a/app/plugins/dev/exec.py +++ b/app/plugins/dev/exec.py @@ -7,7 +7,8 @@ from io import StringIO from pyrogram.enums import ParseMode from app import Config, bot, BOT, Message, DB, DB_CLIENT, try_ # isort:skip -from app.utils import shell, aiohttp_tools as aio # isort:skip +from app.utils import shell # isort:skip +from app.utils.aiohttp_tools import aio # isort:skip async def executor(bot: BOT, message: Message) -> Message | None: diff --git a/app/plugins/misc/debrid.py b/app/plugins/misc/debrid.py index abe222e..2a3a3a9 100644 --- a/app/plugins/misc/debrid.py +++ b/app/plugins/misc/debrid.py @@ -3,7 +3,7 @@ import os from app import BOT, Message, bot -from app.utils import aiohttp_tools +from app.utils.aiohttp_tools import aio from app.utils.helpers import post_to_telegraph as post_tgh @@ -13,7 +13,7 @@ async def get_json(endpoint: str, query: dict, key=os.environ.get("DEBRID_TOKEN" return "API key not found." api = "https://api.alldebrid.com/v4" + endpoint params = {"agent": "bot", "apikey": key, **query} - async with aiohttp_tools.SESSION.get(url=api, params=params) as ses: + async with aio.session.get(url=api, params=params) as ses: try: json = await ses.json() return json diff --git a/app/plugins/misc/song.py b/app/plugins/misc/song.py index fe88af9..c4df12b 100644 --- a/app/plugins/misc/song.py +++ b/app/plugins/misc/song.py @@ -7,7 +7,7 @@ from urllib.parse import urlparse import yt_dlp from app import Message, bot -from app.utils.aiohttp_tools import in_memory_dl +from app.utils.aiohttp_tools import aio domains = [ "www.youtube.com", @@ -63,7 +63,7 @@ async def song_dl(bot: bot, message: Message) -> None | Message: yt_info: str = yt_info["entries"][0] duration: int = yt_info["duration"] artist: str = yt_info["channel"] - thumb = await in_memory_dl(yt_info["thumbnail"]) + thumb = await aio.in_memory_dl(yt_info["thumbnail"]) down_path: list = glob.glob(dl_path + "*") if not down_path: return await response.edit("Not found") diff --git a/app/plugins/sudo/users.py b/app/plugins/sudo/users.py index 2ef0eba..579bcd1 100644 --- a/app/plugins/sudo/users.py +++ b/app/plugins/sudo/users.py @@ -1,3 +1,5 @@ +import asyncio + from pyrogram.types import User from app import BOT, Config, CustomDB, Message, bot @@ -29,8 +31,10 @@ async def sudo(bot: BOT, message: Message): return value = not Config.SUDO Config.SUDO = value - await SUDO.add_data({"_id": "sudo_switch", "value": value}) - await message.reply(text=f"Sudo is enabled: {value}!", del_in=8) + await asyncio.gather( + SUDO.add_data({"_id": "sudo_switch", "value": value}), + message.reply(text=f"Sudo is enabled: {value}!", del_in=8), + ) @bot.add_cmd(cmd="addsudo") diff --git a/app/plugins/tools/pm_permit.py b/app/plugins/tools/pm_permit.py new file mode 100644 index 0000000..0c5b3ad --- /dev/null +++ b/app/plugins/tools/pm_permit.py @@ -0,0 +1,135 @@ +import asyncio + +from pyrogram import filters +from pyrogram.enums import ChatType + +from app import BOT, Config, CustomDB, Message, bot +from app.utils.helpers import get_name + +PM_USERS = CustomDB("PM_USERS") + +PM_GUARD = CustomDB("PM_GUARD") + +ALLOWED_USERS: list[int] = [] + +allowed_filter = filters.create(lambda _, __, m: m.from_user.id in ALLOWED_USERS) + +guard_check = filters.create(lambda _, __, ___: Config.PM_GUARD) + +RECENT_USERS: dict = {} + + +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()] + Config.PM_GUARD = guard["value"] + + +@bot.on_message( + (guard_check & filters.private & filters.incoming) & ~allowed_filter, group=0 +) +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.", + type="info", + ) + RECENT_USERS[user_id] += 1 + if RECENT_USERS[user_id] >= 5: + await message.reply("You've been blocked for spamming.") + await bot.block_user(user_id) + RECENT_USERS.pop(user_id) + await bot.log_text( + text=f"#PMGUARD\n{message.from_user.mention} [{user_id}] has been blocked for spamming.", + type="info", + ) + return + if RECENT_USERS[user_id] % 2: + await message.reply( + "You are not authorised to PM.\nWait until you get authorised." + ) + + +@bot.on_message(guard_check & filters.private & filters.outgoing, group=2) +async def auto_approve(bot: BOT, message: Message): + if message.chat.id in ALLOWED_USERS: + return + message = Message.parse_message(message=message) + await message.reply("Auto-Approved to PM.", del_in=5) + await PM_USERS.insert_one({"_id": message.chat.id}) + ALLOWED_USERS.append(message.chat.id) + + +@bot.add_cmd(cmd="pmguard") +async def pmguard(bot: BOT, message: Message): + """ + CMD: PMGUARD + INFO: Enable/Disable PM GUARD. + FLAGS: -c to check guard status. + USAGE: + .pmguard | .pmguard -c + """ + if "-c" in message.flags: + await message.reply( + text=f"PM Guard is enabled: {Config.PM_GUARD} .", del_in=8 + ) + return + value = not Config.PM_GUARD + Config.PM_GUARD = value + await asyncio.gather( + PM_GUARD.add_data({"_id": "guard_switch", "value": value}), + message.reply(text=f"PM Guard is enabled: {value}!", del_in=8), + ) + + +@bot.add_cmd(cmd=["a", "allow"]) +async def allow_pm(bot: BOT, message: Message): + user_id, name = get_user_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." + ) + return + if user_id in ALLOWED_USERS: + await message.reply(f"{name} is already approved.") + return + ALLOWED_USERS.append(user_id) + await asyncio.gather( + message.reply(text=f"{name} allowed to PM.", del_in=8), + PM_USERS.insert_one({"_id": user_id}), + ) + + +@bot.add_cmd(cmd="nopm") +async def no_pm(bot: BOT, message: Message): + user_id, name = get_user_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." + ) + return + if user_id not in ALLOWED_USERS: + await message.reply(f"{name} is not approved to PM.") + return + ALLOWED_USERS.remove(user_id) + await asyncio.gather( + message.reply(text=f"{name} Dis-allowed to PM.", del_in=8), + PM_USERS.delete_data(user_id), + ) + + +def get_user_name(message: Message) -> tuple: + if message.flt_input and message.flt_input.isdigit(): + user_id = int(message.flt_input) + return user_id, user_id + elif message.replied: + return message.replied.from_user.id, get_name(message.replied.from_user) + elif message.chat.type == ChatType.PRIVATE: + return message.chat.id, get_name(message.chat) + else: + return 0, 0 diff --git a/app/utils/aiohttp_tools.py b/app/utils/aiohttp_tools.py index 1b7a0d4..ced2e22 100644 --- a/app/utils/aiohttp_tools.py +++ b/app/utils/aiohttp_tools.py @@ -1,49 +1,69 @@ import json +import os from io import BytesIO -import aiohttp +from aiohttp import ClientSession, web +from app import LOGGER, Config from app.utils.media_helper import get_filename -SESSION: aiohttp.ClientSession | None = None + +class Aio: + def __init__(self): + self.session: ClientSession | None = None + self.app = None + self.site = None + self.port = os.environ.get("API_PORT", 0) + self.runner = None + if self.port: + Config.INIT_TASKS.append(self.set_site()) + Config.INIT_TASKS.append(self.set_session()) + + async def set_session(self): + self.session = ClientSession() + + async def set_site(self): + LOGGER.info("Starting Static WebSite.") + self.app = web.Application() + self.app.router.add_get(path="/", handler=self.handle_request) + self.runner = web.AppRunner(self.app) + await self.runner.setup() + self.site = web.TCPSite(self.runner, "0.0.0.0", self.port) + await self.site.start() + + async def handle_request(self, _): + return web.Response(text="Web Server Running...") + + async def get_json( + self, + url: str, + headers: dict = None, + params: dict | str = None, + json_: bool = False, + timeout: int = 10, + ) -> dict | None: + try: + async with self.session.get( + url=url, headers=headers, params=params, timeout=timeout + ) as ses: + if json_: + return await ses.json() + else: + return json.loads(await ses.text()) + except BaseException: + return + + async def in_memory_dl(self, url: str) -> BytesIO: + async with self.session.get(url) as remote_file: + bytes_data = await remote_file.read() + file = BytesIO(bytes_data) + file.name = get_filename(url) + return file + + async def thumb_dl(self, thumb) -> BytesIO | str | None: + if not thumb or not thumb.startswith("http"): + return thumb + return await in_memory_dl(thumb) # NOQA -async def init_task() -> None: - if not SESSION: - globals().update({"SESSION": aiohttp.ClientSession()}) - else: - await SESSION.close() - - -async def get_json( - url: str, - headers: dict = None, - params: dict = None, - json_: bool = False, - timeout: int = 10, -) -> dict | None: - try: - async with SESSION.get( - url=url, headers=headers, params=params, timeout=timeout - ) as ses: - if json_: - ret_json = await ses.json() - else: - ret_json = json.loads(await ses.text()) - return ret_json - except BaseException: - return - - -async def in_memory_dl(url: str) -> BytesIO: - async with SESSION.get(url) as remote_file: - bytes_data = await remote_file.read() - file = BytesIO(bytes_data) - file.name = get_filename(url) - return file - - -async def thumb_dl(thumb) -> BytesIO | str | None: - if not thumb or not thumb.startswith("http"): - return thumb - return await in_memory_dl(thumb) # NOQA +aio = Aio() diff --git a/run b/run index f2e9391..7b16eaf 100755 --- a/run +++ b/run @@ -1,18 +1,7 @@ #!/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 - if ! [ -d ".git" ] ; then git init fi -python3 -m app \ No newline at end of file +python3 -m app