diff --git a/Hellbot/__init__.py b/Hellbot/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2ea14e419335e4f3d1cd29ba3b98caf9f1c30b73 --- /dev/null +++ b/Hellbot/__init__.py @@ -0,0 +1,60 @@ +import os +import time +from platform import python_version + +import heroku3 +from pyrogram import __version__ as pyrogram_version + +from .core import LOGS, Config + +START_TIME = time.time() + + +__version__ = { + "hellbot": "3.0", + "pyrogram": pyrogram_version, + "python": python_version(), +} + + +try: + if Config.HEROKU_APIKEY is not None and Config.HEROKU_APPNAME is not None: + HEROKU_APP = heroku3.from_key(Config.HEROKU_APIKEY).apps()[ + Config.HEROKU_APPNAME + ] + else: + HEROKU_APP = None +except Exception as e: + LOGS.error(f"Heroku Api - {e}") + HEROKU_APP = None + + +if Config.API_HASH is None: + LOGS.error("Please set your API_HASH !") + quit(1) + +if Config.API_ID == 0: + LOGS.error("Please set your API_ID !") + quit(1) + +if Config.BOT_TOKEN is None: + LOGS.error("Please set your BOT_TOKEN !") + quit(1) + +if Config.DATABASE_URL is None: + LOGS.error("Please set your DATABASE_URL !") + quit(1) + +if Config.LOGGER_ID == 0: + LOGS.error("Please set your LOGGER_ID !") + quit(1) + +if Config.OWNER_ID == 0: + LOGS.error("Please set your OWNER_ID !") + quit(1) + +if not os.path.isdir(Config.DWL_DIR): + os.makedirs(Config.DWL_DIR) + +if not os.path.isdir(Config.TEMP_DIR): + os.makedirs(Config.TEMP_DIR) diff --git a/Hellbot/__main__.py b/Hellbot/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..6e2c3ca34e775fc6267041cfb7c8f6a24f8dd89c --- /dev/null +++ b/Hellbot/__main__.py @@ -0,0 +1,33 @@ +from pyrogram import idle + +from Hellbot import __version__ +from Hellbot.core import ( + Config, + ForcesubSetup, + GachaBotsSetup, + TemplateSetup, + UserSetup, + db, + hellbot, +) +from Hellbot.functions.tools import initialize_git +from Hellbot.functions.utility import BList, Flood, TGraph + + +async def main(): + await hellbot.startup() + await db.connect() + await UserSetup() + await ForcesubSetup() + await GachaBotsSetup() + await TemplateSetup() + await Flood.updateFromDB() + await BList.updateBlacklists() + await TGraph.setup() + await initialize_git(Config.PLUGINS_REPO) + await hellbot.start_message(__version__) + await idle() + + +if __name__ == "__main__": + hellbot.run(main()) diff --git a/Hellbot/core/__init__.py b/Hellbot/core/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..caa4b0445d0dd0df78506363b2da664cf16e120f --- /dev/null +++ b/Hellbot/core/__init__.py @@ -0,0 +1,19 @@ +from .clients import hellbot +from .config import ENV, Config, Limits, Symbols +from .database import db +from .initializer import ForcesubSetup, GachaBotsSetup, TemplateSetup, UserSetup +from .logger import LOGS + +__all__ = [ + "hellbot", + "ENV", + "Config", + "Limits", + "Symbols", + "db", + "ForcesubSetup", + "GachaBotsSetup", + "TemplateSetup", + "UserSetup", + "LOGS", +] diff --git a/Hellbot/core/clients.py b/Hellbot/core/clients.py new file mode 100644 index 0000000000000000000000000000000000000000..f9871785ad507f4ff0f676816a28542ed574d707 --- /dev/null +++ b/Hellbot/core/clients.py @@ -0,0 +1,232 @@ +import asyncio +import glob +import importlib +import os +import sys +from pathlib import Path + +import pyroaddon # pylint: disable=unused-import +from pyrogram import Client +from pyrogram.enums import ParseMode +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message + +from .config import ENV, Config, Symbols +from .database import db +from .logger import LOGS + + +class HellClient(Client): + def __init__(self) -> None: + self.users: list[Client] = [] + self.bot: Client = Client( + name="HellBot", + api_id=Config.API_ID, + api_hash=Config.API_HASH, + bot_token=Config.BOT_TOKEN, + plugins=dict(root="Hellbot.plugins.bot"), + ) + + async def start_user(self) -> None: + sessions = await db.get_all_sessions() + for i, session in enumerate(sessions): + try: + client = Client( + name=f"HellUser#{i + 1}", + api_id=Config.API_ID, + api_hash=Config.API_HASH, + session_string=session["session"], + ) + await client.start() + me = await client.get_me() + self.users.append(client) + LOGS.info( + f"{Symbols.arrow_right * 2} Started User {i + 1}: '{me.first_name}' {Symbols.arrow_left * 2}" + ) + is_in_logger = await self.validate_logger(client) + if not is_in_logger: + LOGS.warning( + f"Client #{i+1}: '{me.first_name}' is not in Logger Group! Check and add manually for proper functioning." + ) + try: + await client.join_chat("https://t.me/+wQyUMn4891Q2OTVh") # Channel + except: + pass + # try: + # await client.join_chat("https://t.me/+P4Ekwk7P7Rk3NzA9") # Group + # except: + # pass + except Exception as e: + LOGS.error(f"{i + 1}: {e}") + continue + + async def start_bot(self) -> None: + await self.bot.start() + me = await self.bot.get_me() + LOGS.info( + f"{Symbols.arrow_right * 2} Started HellBot Client: '{me.username}' {Symbols.arrow_left * 2}" + ) + + async def load_plugin(self) -> None: + count = 0 + files = glob.glob("Hellbot/plugins/user/*.py") + unload = await db.get_env(ENV.unload_plugins) or "" + unload = unload.split(" ") + for file in files: + with open(file) as f: + path = Path(f.name) + shortname = path.stem.replace(".py", "") + if shortname in unload: + os.remove(Path(f"Hellbot/plugins/user/{shortname}.py")) + continue + if shortname.startswith("__"): + continue + fpath = Path(f"Hellbot/plugins/user/{shortname}.py") + name = "Hellbot.plugins.user." + shortname + spec = importlib.util.spec_from_file_location(name, fpath) + load = importlib.util.module_from_spec(spec) + spec.loader.exec_module(load) + sys.modules["Hellbot.plugins.user." + shortname] = load + count += 1 + f.close() + LOGS.info( + f"{Symbols.bullet * 3} Loaded User Plugin: '{count}' {Symbols.bullet * 3}" + ) + + async def validate_logger(self, client: Client) -> bool: + try: + await client.get_chat_member(Config.LOGGER_ID, "me") + return True + except Exception: + return await self.join_logger(client) + + async def join_logger(self, client: Client) -> bool: + try: + invite_link = await self.bot.export_chat_invite_link(Config.LOGGER_ID) + await client.join_chat(invite_link) + return True + except Exception: + return False + + async def start_message(self, version: dict) -> None: + await self.bot.send_animation( + Config.LOGGER_ID, + "https://te.legra.ph/file/8deca5343c64d9db9401f.mp4", + f"**{Symbols.check_mark} ๐–ง๐–พ๐—…๐—…๐–ก๐—ˆ๐— ๐—‚๐—Œ ๐—‡๐—ˆ๐— ๐–ฎ๐—‡๐—…๐—‚๐—‡๐–พ!**\n\n" + f"**{Symbols.triangle_right} ๐–ข๐—…๐—‚๐–พ๐—‡๐—๐—Œ:** `{len(self.users)}`\n" + f"**{Symbols.triangle_right} ๐–ฏ๐—…๐—Ž๐—€๐—‚๐—‡๐—Œ:** `{len(Config.CMD_MENU)}`\n" + f"**{Symbols.triangle_right} ๐–ข๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ๐—Œ:** `{len(Config.CMD_INFO)}`\n" + f"**{Symbols.triangle_right} ๐–ฒ๐—๐–บ๐—‡ ๐–ด๐—Œ๐–พ๐—‹๐—Œ:** `{len(Config.STAN_USERS)}`\n" + f"**{Symbols.triangle_right} ๐– ๐—Ž๐—๐— ๐–ด๐—Œ๐–พ๐—‹๐—Œ:** `{len(Config.AUTH_USERS)}`\n\n" + f"**{Symbols.triangle_right} ๐–ง๐–พ๐—…๐—…๐–ก๐—ˆ๐— ๐–ต๐–พ๐—‹๐—Œ๐—‚๐—ˆ๐—‡:** `{version['hellbot']}`\n" + f"**{Symbols.triangle_right} ๐–ฏ๐—’๐—‹๐—ˆ๐—€๐—‹๐–บ๐—† ๐–ต๐–พ๐—‹๐—Œ๐—‚๐—ˆ๐—‡:** `{version['pyrogram']}`\n" + f"**{Symbols.triangle_right} ๐–ฏ๐—’๐—๐—๐—ˆ๐—‡ ๐–ต๐–พ๐—‹๐—Œ๐—‚๐—ˆ๐—‡:** `{version['python']}`\n\n" + f"** @HellBot_Networks**", + parse_mode=ParseMode.MARKDOWN, + disable_notification=True, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton("๐Ÿ’ซ Start Me", url=f"https://t.me/{self.bot.me.username}?start=start"), + InlineKeyboardButton("๐Ÿ’– Repo", url="https://github.com/The-HellBot/HellBot"), + ], + [ + InlineKeyboardButton("๐Ÿ€ HellBot Networks ๐Ÿ€", url="https://t.me/hellbot_networks"), + ], + ] + ), + ) + + async def startup(self) -> None: + LOGS.info( + f"{Symbols.bullet * 3} Starting HellBot Client & User {Symbols.bullet * 3}" + ) + await self.start_bot() + await self.start_user() + await self.load_plugin() + + +class CustomMethods(HellClient): + async def input(self, message: Message) -> str: + """Get the input from the user""" + if len(message.command) < 2: + output = "" + + else: + try: + output = message.text.split(" ", 1)[1].strip() or "" + except IndexError: + output = "" + + return output + + async def edit( + self, + message: Message, + text: str, + parse_mode: ParseMode = ParseMode.DEFAULT, + no_link_preview: bool = True, + ) -> Message: + """Edit or Reply to a message, if possible""" + if message.from_user and message.from_user.id in Config.STAN_USERS: + if message.reply_to_message: + return await message.reply_to_message.reply_text( + text, + parse_mode=parse_mode, + disable_web_page_preview=no_link_preview, + ) + return await message.reply_text( + text, parse_mode=parse_mode, disable_web_page_preview=no_link_preview + ) + return await message.edit_text( + text, parse_mode=parse_mode, disable_web_page_preview=no_link_preview + ) + + async def _delete(self, message: Message, delay: int = 0) -> None: + """Delete a message after a certain period of time""" + await asyncio.sleep(delay) + await message.delete() + + async def delete( + self, message: Message, text: str, delete: int = 10, in_background: bool = True + ) -> None: + """Edit a message and delete it after a certain period of time""" + to_del = await self.edit(message, text) + if in_background: + asyncio.create_task(self._delete(to_del, delete)) + else: + await self._delete(to_del, delete) + + async def error(self, message: Message, text: str, delete: int = 10) -> None: + """Edit an error message and delete it after a certain period of time if mentioned""" + to_del = await self.edit(message, f"{Symbols.cross_mark} **Error:** \n\n{text}") + if delete: + asyncio.create_task(self._delete(to_del, delete)) + + async def _log(self, tag: str, text: str, file: str = None) -> None: + """Log a message to the Logger Group""" + msg = f"**#{tag.upper()}**\n\n{text}" + try: + if file: + try: + await self.bot.send_document(Config.LOGGER_ID, file, caption=msg) + except: + await self.bot.send_message( + Config.LOGGER_ID, msg, disable_web_page_preview=True + ) + else: + await self.bot.send_message( + Config.LOGGER_ID, msg, disable_web_page_preview=True + ) + except Exception as e: + raise Exception(f"{Symbols.cross_mark} LogErr: {e}") + + async def check_and_log(self, tag: str, text: str, file: str = None) -> None: + """Check if : + \n-> the Logger Group is available + \n-> the logging is enabled""" + status = await db.get_env(ENV.is_logger) + if status and status.lower() == "true": + await self._log(tag, text, file) + + +hellbot = CustomMethods() diff --git a/Hellbot/core/config.py b/Hellbot/core/config.py new file mode 100644 index 0000000000000000000000000000000000000000..ee069dfa299f46c18f739ed1ab95ebd6a1e715e7 --- /dev/null +++ b/Hellbot/core/config.py @@ -0,0 +1,155 @@ +from os import getenv + +from dotenv import load_dotenv +from pyrogram import filters + +load_dotenv() + + +class Config: + # editable configs + API_HASH = getenv("API_HASH", None) + API_ID = int(getenv("API_ID", 0)) + BOT_TOKEN = getenv("BOT_TOKEN", None) + DATABASE_URL = getenv("DATABASE_URL", None) + HANDLERS = getenv("HANDLERS", ". ! ?").strip().split() + LOGGER_ID = int(getenv("LOGGER_ID", 0)) + OWNER_ID = int(getenv("OWNER_ID", 0)) + + # heroku related configs + HEROKU_APPNAME = getenv("HEROKU_APPNAME", None) + HEROKU_APIKEY = getenv("HEROKU_APIKEY", None) + + # github related configs + PLUGINS_REPO = getenv("PLUGINS_REPO", "The-HellBot/Plugins") + DEPLOY_REPO = getenv("DEPLOY_REPO", "The-HellBot/Hellbot") + + # storage dir: you may or may not edit + DWL_DIR = "./downloads/" + TEMP_DIR = "./temp/" + CHROME_BIN = getenv("CHROME_BIN", "/app/.chrome-for-testing/chrome-linux64/chrome") + CHROME_DRIVER = getenv( + "CHROME_DRIVER", "/app/.chrome-for-testing/chromedriver-linux64/chromedriver" + ) + FONT_PATH = "./Hellbot/resources/fonts/Montserrat.ttf" + + # users config: do not edit + AUTH_USERS = filters.user() + BANNED_USERS = filters.user() + GACHA_BOTS = filters.user() + MUTED_USERS = filters.user() + DEVS = filters.user([1432756163, 1874070588, 1533682758]) + STAN_USERS = filters.user() + FORCESUBS = filters.chat() + + # Global config: do not edit + AFK_CACHE = {} + BOT_CMD_INFO = {} + BOT_CMD_MENU = {} + BOT_HELP = {} + CMD_INFO = {} + CMD_MENU = {} + HELP_DICT = {} + TEMPLATES = {} + + +class ENV: + """Database ENV Names""" + + airing_template = "AIRING_TEMPLATE" + airpollution_template = "AIRPOLLUTION_TEMPLATE" + alive_pic = "ALIVE_PIC" + alive_template = "ALIVE_TEMPLATE" + anilist_user_template = "ANILIST_USER_TEMPLATE" + anime_template = "ANIME_TEMPLATE" + btn_in_help = "BUTTONS_IN_HELP" + character_template = "CHARACTER_TEMPLATE" + chat_info_template = "CHAT_INFO_TEMPLATE" + climate_api = "CLIMATE_API" + climate_template = "CLIMATE_TEMPLATE" + command_template = "COMMAND_TEMPLATE" + currency_api = "CURRENCY_API" + custom_pmpermit = "CUSTOM_PMPERMIT" + gban_template = "GBAN_TEMPLATE" + github_user_template = "GITHUB_USER_TEMPLATE" + help_emoji = "HELP_EMOJI" + help_template = "HELP_TEMPLATE" + is_logger = "IS_LOGGER" + lyrics_api = "LYRICS_API" + manga_template = "MANGA_TEMPLATE" + ocr_api = "OCR_API" + ping_pic = "PING_PIC" + ping_template = "PING_TEMPLATE" + pm_logger = "PM_LOGGER" + pm_max_spam = "PM_MAX_SPAM" + pmpermit = "PMPERMIT" + pmpermit_pic = "PMPERMIT_PIC" + remove_bg_api = "REMOVE_BG_API" + thumbnail_url = "THUMBNAIL_URL" + statistics_template = "STATISTICS_TEMPLATE" + sticker_packname = "STICKER_PACKNAME" + tag_logger = "TAG_LOGGER" + telegraph_account = "TELEGRAPH_ACCOUNT" + time_zone = "TIME_ZONE" + unload_plugins = "UNLOAD_PLUGINS" + unsplash_api = "UNSPLASH_API" + usage_template = "USAGE_TEMPLATE" + user_info_template = "USER_INFO_TEMPLATE" + + +class Limits: + AdminRoleLength = 16 + AdminsLimit = 50 + BioLength = 70 + BotDescriptionLength = 512 + BotInfoLength = 120 + BotsLimit = 20 + CaptionLength = 1024 + ChannelGroupsLimit = 500 + ChatTitleLength = 128 + FileNameLength = 60 + MessageLength = 4096 + NameLength = 64 + PremiumBioLength = 140 + PremiumCaptionLength = 2048 + PremiumChannelGroupsLimit = 1000 + StickerAniamtedLimit = 50 + StickerPackNameLength = 64 + StickerStaticLimit = 120 + + +class Symbols: + anchor = "โš˜" + arrow_left = "ยซ" + arrow_right = "ยป" + back = "๐Ÿ”™ back" + bullet = "โ€ข" + check_mark = "โœ”" + close = "๐Ÿ—‘๏ธ" + cross_mark = "โœ˜" + diamond_1 = "โ—‡" + diamond_2 = "โ—ˆ" + next = "โคš next" + previous = "prev โค™" + radio_select = "โ—‰" + radio_unselect = "ใ€‡" + triangle_left = "โ—‚" + triangle_right = "โ–ธ" + + +os_configs = [ + "API_HASH", + "API_ID", + "BOT_TOKEN", + "DATABASE_URL", + "DEPLOY_REPO", + "HANDLERS", + "HEROKU_APIKEY", + "HEROKU_APPNAME", + "LOGGER_ID", + "OWNER_ID", + "PLUGINS_REPO", +] +all_env: list[str] = [ + value for key, value in ENV.__dict__.items() if not key.startswith("__") +] diff --git a/Hellbot/core/database.py b/Hellbot/core/database.py new file mode 100644 index 0000000000000000000000000000000000000000..4f133c3e7a1ad5cec4e1bc436733af31a402ada9 --- /dev/null +++ b/Hellbot/core/database.py @@ -0,0 +1,584 @@ +import datetime +import time + +from motor import motor_asyncio +from motor.core import AgnosticClient + +from .config import Config, Symbols +from .logger import LOGS + + +class Database: + def __init__(self, uri: str) -> None: + self.client: AgnosticClient = motor_asyncio.AsyncIOMotorClient(uri) + self.db = self.client["Hellbot"] + + self.afk = self.db["afk"] + self.antiflood = self.db["antiflood"] + self.autopost = self.db["autopost"] + self.blacklist = self.db["blacklist"] + self.echo = self.db["echo"] + self.env = self.db["env"] + self.filter = self.db["filter"] + self.forcesub = self.db["forcesub"] + self.gachabots = self.db["gachabots"] + self.gban = self.db["gban"] + self.gmute = self.db["gmute"] + self.greetings = self.db["greetings"] + self.mute = self.db["mute"] + self.pmpermit = self.db["pmpermit"] + self.session = self.db["session"] + self.snips = self.db["snips"] + self.stan_users = self.db["stan_users"] + + async def connect(self): + try: + await self.client.admin.command("ping") + LOGS.info( + f"{Symbols.bullet * 3} Database Connection Established! {Symbols.bullet * 3}" + ) + except Exception as e: + LOGS.info(f"{Symbols.cross_mark} DatabaseErr: {e} ") + quit(1) + + def get_datetime(self) -> str: + return datetime.datetime.now().strftime("%d/%m/%Y - %H:%M") + + async def set_env(self, name: str, value: str) -> None: + await self.env.update_one( + {"name": name}, {"$set": {"value": value}}, upsert=True + ) + + async def get_env(self, name: str) -> str | None: + if await self.is_env(name): + data = await self.env.find_one({"name": name}) + return data["value"] + return None + + async def rm_env(self, name: str) -> None: + await self.env.delete_one({"name": name}) + + async def is_env(self, name: str) -> bool: + if await self.env.find_one({"name": name}): + return True + return False + + async def get_all_env(self) -> list: + return [i async for i in self.env.find({})] + + async def is_stan(self, client: int, user_id: int) -> bool: + if await self.stan_users.find_one({"client": client, "user_id": user_id}): + return True + return False + + async def add_stan(self, client: int, user_id: int) -> bool: + if await self.is_stan(client, user_id): + return False + await self.stan_users.insert_one( + {"client": client, "user_id": user_id, "date": self.get_datetime()} + ) + return True + + async def rm_stan(self, client: int, user_id: int) -> bool: + if not await self.is_stan(client, user_id): + return False + await self.stan_users.delete_one({"client": client, "user_id": user_id}) + return True + + async def get_stans(self, client: int) -> list: + return [i async for i in self.stan_users.find({"client": client})] + + async def get_all_stans(self) -> list: + return [i async for i in self.stan_users.find({})] + + async def is_session(self, user_id: int) -> bool: + if await self.session.find_one({"user_id": user_id}): + return True + return False + + async def update_session(self, user_id: int, session: str) -> None: + await self.session.update_one( + {"user_id": user_id}, + {"$set": {"session": session, "date": self.get_datetime()}}, + upsert=True, + ) + + async def rm_session(self, user_id: int) -> None: + await self.session.delete_one({"user_id": user_id}) + + async def get_session(self, user_id: int): + if not await self.is_session(user_id): + return False + data = await self.session.find_one({"user_id": user_id}) + return data + + async def get_all_sessions(self) -> list: + return [i async for i in self.session.find({})] + + async def is_gbanned(self, user_id: int) -> bool: + if await self.gban.find_one({"user_id": user_id}): + return True + return False + + async def add_gban(self, user_id: int, reason: str) -> bool: + if await self.is_gbanned(user_id): + return False + await self.gban.insert_one( + {"user_id": user_id, "reason": reason, "date": self.get_datetime()} + ) + return True + + async def rm_gban(self, user_id: int): + if not await self.is_gbanned(user_id): + return None + reason = (await self.gban.find_one({"user_id": user_id}))["reason"] + await self.gban.delete_one({"user_id": user_id}) + return reason + + async def get_gban(self) -> list: + return [i async for i in self.gban.find({})] + + async def get_gban_user(self, user_id: int) -> dict | None: + if not await self.is_gbanned(user_id): + return None + return await self.gban.find_one({"user_id": user_id}) + + async def is_gmuted(self, user_id: int) -> bool: + if await self.gmute.find_one({"user_id": user_id}): + return True + return False + + async def add_gmute(self, user_id: int, reason: str) -> bool: + if await self.is_gmuted(user_id): + return False + await self.gmute.insert_one( + {"user_id": user_id, "reason": reason, "date": self.get_datetime()} + ) + return True + + async def rm_gmute(self, user_id: int): + if not await self.is_gmuted(user_id): + return None + reason = (await self.gmute.find_one({"user_id": user_id}))["reason"] + await self.gmute.delete_one({"user_id": user_id}) + return reason + + async def get_gmute(self) -> list: + return [i async for i in self.gmute.find({})] + + async def add_mute(self, client: int, user_id: int, chat_id: int, reason: str): + await self.mute.update_one( + {"client": client, "user_id": user_id, "chat_id": chat_id}, + {"$set": {"reason": reason, "date": self.get_datetime()}}, + upsert=True, + ) + + async def rm_mute(self, client: int, user_id: int, chat_id: int) -> str: + reason = (await self.get_mute(client, user_id, chat_id))["reason"] + await self.mute.delete_one({"client": client, "user_id": user_id, "chat_id": chat_id}) + return reason + + async def is_muted(self, client: int, user_id: int, chat_id: int) -> bool: + if await self.get_mute(client, user_id, chat_id): + return True + return False + + async def get_mute(self, client: int, user_id: int, chat_id: int): + data = await self.mute.find_one({"client": client, "user_id": user_id, "chat_id": chat_id}) + return data + + async def set_afk( + self, user_id: int, reason: str, media: int, media_type: str + ) -> None: + await self.afk.update_one( + {"user_id": user_id}, + { + "$set": { + "reason": reason, + "time": time.time(), + "media": media, + "media_type": media_type, + } + }, + upsert=True, + ) + + async def get_afk(self, user_id: int): + data = await self.afk.find_one({"user_id": user_id}) + return data + + async def is_afk(self, user_id: int) -> bool: + if await self.afk.find_one({"user_id": user_id}): + return True + return False + + async def rm_afk(self, user_id: int) -> None: + await self.afk.delete_one({"user_id": user_id}) + + async def set_flood(self, client_chat: tuple[int, int], settings: dict): + await self.antiflood.update_one( + {"client": client_chat[0], "chat": client_chat[1]}, + {"$set": settings}, + upsert=True, + ) + + async def get_flood(self, client_chat: tuple[int, int]): + data = await self.antiflood.find_one( + {"client": client_chat[0], "chat": client_chat[1]} + ) + return data or {} + + async def is_flood(self, client_chat: tuple[int, int]) -> bool: + data = await self.get_flood(client_chat) + + if not data: + return False + + if data["limit"] == 0: + return False + + return True + + async def get_all_floods(self) -> list: + return [i async for i in self.antiflood.find({})] + + async def set_autopost(self, client: int, from_channel: int, to_channel: int): + await self.autopost.update_one( + {"client": client}, + { + "$push": { + "autopost": { + "from_channel": from_channel, + "to_channel": to_channel, + "date": self.get_datetime(), + } + } + }, + upsert=True, + ) + + async def get_autopost(self, client: int, from_channel: int): + data = await self.autopost.find_one( + { + "client": client, + "autopost": {"$elemMatch": {"from_channel": from_channel}}, + } + ) + return data + + async def is_autopost( + self, client: int, from_channel: int, to_channel: int = None + ) -> bool: + if to_channel: + data = await self.autopost.find_one( + { + "client": client, + "autopost": { + "$elemMatch": { + "from_channel": from_channel, + "to_channel": to_channel, + } + }, + } + ) + else: + data = await self.autopost.find_one( + { + "client": client, + "autopost": {"$elemMatch": {"from_channel": from_channel}}, + } + ) + return True if data else False + + async def rm_autopost(self, client: int, from_channel: int, to_channel: int): + await self.autopost.update_one( + {"client": client}, + { + "$pull": { + "autopost": { + "from_channel": from_channel, + "to_channel": to_channel, + } + } + }, + ) + + async def get_all_autoposts(self, client: int) -> list: + return [i async for i in self.autopost.find({"client": client})] + + async def add_blacklist(self, client: int, chat: int, blacklist: str): + await self.blacklist.update_one( + {"client": client, "chat": chat}, + {"$push": {"blacklist": blacklist}}, + upsert=True, + ) + + async def rm_blacklist(self, client: int, chat: int, blacklist: str): + await self.blacklist.update_one( + {"client": client, "chat": chat}, + {"$pull": {"blacklist": blacklist}}, + ) + + async def is_blacklist(self, client: int, chat: int, blacklist: str) -> bool: + blacklists = await self.get_all_blacklists(client, chat) + if blacklist in blacklists: + return True + return False + + async def get_all_blacklists(self, client: int, chat: int) -> list: + data = await self.blacklist.find_one({"client": client, "chat": chat}) + + if not data: + return [] + + return data["blacklist"] + + async def get_blacklist_clients(self) -> list: + return [i async for i in self.blacklist.find({})] + + async def set_echo(self, client: int, chat: int, user: int): + await self.echo.update_one( + {"client": client, "chat": chat}, + {"$push": {"echo": user}}, + upsert=True, + ) + + async def rm_echo(self, client: int, chat: int, user: int): + await self.echo.update_one( + {"client": client, "chat": chat}, + {"$pull": {"echo": user}}, + ) + + async def is_echo(self, client: int, chat: int, user: int) -> bool: + data = await self.get_all_echo(client, chat) + if user in data: + return True + return False + + async def get_all_echo(self, client: int, chat: int) -> list: + data = await self.echo.find_one({"client": client, "chat": chat}) + + if not data: + return [] + + return data["echo"] + + async def set_filter(self, client: int, chat: int, keyword: str, msgid: int): + await self.filter.update_one( + {"client": client, "chat": chat}, + {"$push": {"filter": {"keyword": keyword, "msgid": msgid}}}, + upsert=True, + ) + + async def rm_filter(self, client: int, chat: int, keyword: str): + await self.filter.update_one( + {"client": client, "chat": chat}, + {"$pull": {"filter": {"keyword": keyword}}}, + ) + + async def rm_all_filters(self, client: int, chat: int): + await self.filter.delete_one({"client": client, "chat": chat}) + + async def is_filter(self, client: int, chat: int, keyword: str) -> bool: + data = await self.get_filter(client, chat, keyword) + return True if data else False + + async def get_filter(self, client: int, chat: int, keyword: str): + data = await self.filter.find_one( + { + "client": client, + "chat": chat, + "filter": {"$elemMatch": {"keyword": keyword}}, + } + ) + return data + + async def get_all_filters(self, client: int, chat: int) -> list: + data = await self.filter.find_one({"client": client, "chat": chat}) + + if not data: + return [] + + return data["filter"] + + async def set_snip(self, client: int, chat: int, keyword: str, msgid: int): + await self.snips.update_one( + {"client": client, "chat": chat}, + {"$push": {"snips": {"keyword": keyword, "msgid": msgid}}}, + upsert=True, + ) + + async def rm_snip(self, client: int, chat: int, keyword: str): + await self.snips.update_one( + {"client": client, "chat": chat}, + {"$pull": {"snips": {"keyword": keyword}}}, + ) + + async def rm_all_snips(self, client: int, chat: int): + await self.snips.delete_one({"client": client, "chat": chat}) + + async def is_snip(self, client: int, chat: int, keyword: str) -> bool: + data = await self.get_snip(client, chat, keyword) + return True if data else False + + async def get_snip(self, client: int, chat: int, keyword: str): + data = await self.snips.find_one( + { + "client": client, + "chat": chat, + "snips": {"$elemMatch": {"keyword": keyword}}, + } + ) + return data + + async def get_all_snips(self, client: int, chat: int) -> list: + data = await self.snips.find_one({"client": client, "chat": chat}) + + if not data: + return [] + + return data["snips"] + + async def add_pmpermit(self, client: int, user: int): + await self.pmpermit.update_one( + {"client": client, "user": user}, + {"$set": {"date": self.get_datetime()}}, + upsert=True, + ) + + async def rm_pmpermit(self, client: int, user: int): + await self.pmpermit.delete_one({"client": client, "user": user}) + + async def is_pmpermit(self, client: int, user: int) -> bool: + data = await self.get_pmpermit(client, user) + return True if data else False + + async def get_pmpermit(self, client: int, user: int): + data = await self.pmpermit.find_one({"client": client, "user": user}) + return data + + async def get_all_pmpermits(self, client: int) -> list: + return [i async for i in self.pmpermit.find({"client": client})] + + async def set_welcome(self, client: int, chat: int, message: int): + await self.greetings.update_one( + {"client": client, "chat": chat, "welcome": True}, + {"$set": {"message": message}}, + upsert=True, + ) + + async def rm_welcome(self, client: int, chat: int): + await self.greetings.delete_one( + {"client": client, "chat": chat, "welcome": True} + ) + + async def is_welcome(self, client: int, chat: int) -> bool: + data = await self.get_welcome(client, chat) + return True if data else False + + async def get_welcome(self, client: int, chat: int): + data = await self.greetings.find_one( + {"client": client, "chat": chat, "welcome": True} + ) + return data + + async def set_goodbye(self, client: int, chat: int, message: int): + await self.greetings.update_one( + {"client": client, "chat": chat, "welcome": False}, + {"$set": {"message": message}}, + upsert=True, + ) + + async def rm_goodbye(self, client: int, chat: int): + await self.greetings.delete_one( + {"client": client, "chat": chat, "welcome": False} + ) + + async def is_goodbye(self, client: int, chat: int) -> bool: + data = await self.get_goodbye(client, chat) + return True if data else False + + async def get_goodbye(self, client: int, chat: int): + data = await self.greetings.find_one( + {"client": client, "chat": chat, "welcome": False} + ) + return data + + async def get_all_greetings(self, client: int) -> list: + return [i async for i in self.greetings.find({"client": client})] + + async def add_forcesub(self, chat: int, must_join: int): + await self.forcesub.update_one( + {"chat": chat}, + {"$push": {"must_join": must_join}}, + upsert=True, + ) + + async def rm_forcesub(self, chat: int, must_join: int) -> int: + await self.forcesub.update_one( + {"chat": chat}, + {"$pull": {"must_join": must_join}}, + ) + data = await self.forcesub.find_one({"chat": chat}) + return len(data["must_join"]) + + async def rm_all_forcesub(self, in_chat: int): + await self.forcesub.delete_one({"chat": in_chat}) + + async def is_forcesub(self, chat: int, must_join: int) -> bool: + data = await self.get_forcesub(chat) + if must_join in data["must_join"]: + return True + return False + + async def get_forcesub(self, in_chat: int): + data = await self.forcesub.find_one({"chat": in_chat}) + return data + + async def get_all_forcesubs(self) -> list: + return [i async for i in self.forcesub.find({})] + + async def add_gachabot( + self, client: int, bot: tuple[int, str], catch_command: str, chat_id: int + ): + await self.gachabots.update_one( + {"client": client, "bot": bot[0]}, + { + "$set": { + "username": bot[1], + "catch_command": catch_command, + "chat_id": chat_id, + "date": self.get_datetime(), + } + }, + upsert=True, + ) + + async def rm_gachabot(self, client: int, bot: int, chat_id: int = None): + if chat_id: + await self.gachabots.delete_one( + {"client": client, "bot": bot, "chat_id": chat_id} + ) + else: + await self.gachabots.delete_one({"client": client, "bot": bot}) + + async def is_gachabot(self, client: int, bot: int, chat_id: int) -> bool: + data = await self.get_gachabot(client, bot, chat_id) + return True if data else False + + async def get_gachabot(self, client: int, bot: int, chat_id: int): + data = await self.gachabots.find_one( + {"client": client, "bot": bot, "chat_id": chat_id} + ) + + return data + + async def get_all_gachabots(self, client: int) -> list: + return [i async for i in self.gachabots.find({"client": client})] + + async def get_all_gachabots_id(self) -> list: + data = await self.gachabots.distinct("bot") + return data + + +db = Database(Config.DATABASE_URL) diff --git a/Hellbot/core/initializer.py b/Hellbot/core/initializer.py new file mode 100644 index 0000000000000000000000000000000000000000..d4fba9642c9cd415537c088f3abd8ebb46ae6fc5 --- /dev/null +++ b/Hellbot/core/initializer.py @@ -0,0 +1,94 @@ +import sys +from .clients import hellbot +from .config import Config, Symbols +from .database import db +from .logger import LOGS + + +async def _AuthUsers() -> None: + temp_list = [] + temp_list.append(Config.OWNER_ID) + temp_list.extend([(await client.get_me()).id for client in hellbot.users]) + + stan_users = await db.get_all_stans() + for user in stan_users: + temp_list.append(user["user_id"]) + + users = list(set(temp_list)) + for user in users: + Config.AUTH_USERS.add(user) + + temp_list = None + LOGS.info( + f"{Symbols.arrow_right * 2} Added Authorized Users {Symbols.arrow_left * 2}" + ) + + +async def _StanUsers() -> None: + users = await db.get_all_stans() + for user in users: + Config.STAN_USERS.add(user["user_id"]) + + LOGS.info(f"{Symbols.arrow_right * 2} Added Stan Users {Symbols.arrow_left * 2}") + + +async def _GbanUsers() -> None: + users = await db.get_gban() + for user in users: + Config.BANNED_USERS.add(user["user_id"]) + + LOGS.info( + f"{Symbols.arrow_right * 2} Added {len(users)} Gbanned Users {Symbols.arrow_left * 2}" + ) + + musers = await db.get_gmute() + for user in musers: + Config.MUTED_USERS.add(user["user_id"]) + + LOGS.info( + f"{Symbols.arrow_right * 2} Added {len(musers)} Gmuted Users {Symbols.arrow_left * 2}" + ) + + +async def UserSetup() -> None: + """Initialize Users Config""" + LOGS.info(f"{Symbols.bullet * 3} Setting Up Users {Symbols.bullet * 3}") + await _AuthUsers() + await _StanUsers() + await _GbanUsers() + + +async def ForcesubSetup() -> None: + """Initialize Forcesub Config""" + chats = await db.get_all_forcesubs() + for chat in chats: + if chat not in Config.FORCESUBS: + Config.FORCESUBS.add(chat["chat"]) + + +async def GachaBotsSetup() -> None: + """Initialize GachaBots Config""" + bots = await db.get_all_gachabots_id() + for bot in bots: + Config.GACHA_BOTS.add(bot) + + +async def TemplateSetup() -> None: + """Initialize Templates Config""" + module_name = "temp_module" + module = sys.modules.get(module_name) + if module is None: + module = type(sys)(module_name) + + with open("Hellbot/functions/templates.py", "r", encoding="utf-8") as file: + exec(file.read(), module.__dict__) + + global_vars = module.__dict__ + + var_n_value: dict[str, str] = { + var_name: global_vars[var_name][0] + for var_name in global_vars + if var_name.isupper() and not callable(global_vars[var_name]) + } + + Config.TEMPLATES = var_n_value diff --git a/Hellbot/core/logger.py b/Hellbot/core/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..c0cfdc03af6abb4f95fc0db8007d2ef84dbf6ba4 --- /dev/null +++ b/Hellbot/core/logger.py @@ -0,0 +1,19 @@ +import logging +from logging.handlers import RotatingFileHandler + +logging.basicConfig( + format="[%(asctime)s]:[%(name)s]:[%(levelname)s] - %(message)s", + level=logging.INFO, + datefmt="%H:%M:%S", + handlers=[ + RotatingFileHandler( + "HellBot.log", maxBytes=(1024 * 1024 * 5), backupCount=10, encoding="utf-8" + ), + logging.StreamHandler(), + ], +) + + +logging.getLogger("pyrogram").setLevel(logging.ERROR) + +LOGS = logging.getLogger("HellBot") diff --git a/Hellbot/functions/__init__.py b/Hellbot/functions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Hellbot/functions/admins.py b/Hellbot/functions/admins.py new file mode 100644 index 0000000000000000000000000000000000000000..934dfecc8e375bb85f85276af690dd73a4256076 --- /dev/null +++ b/Hellbot/functions/admins.py @@ -0,0 +1,24 @@ +from pyrogram.enums import ChatMembersFilter, ChatMemberStatus, ChatType +from pyrogram.types import Chat + +from Hellbot.core import hellbot + + +async def get_admins(chat_id: int) -> list: + admins = [] + async for x in hellbot.bot.get_chat_members( + chat_id, filter=ChatMembersFilter.ADMINISTRATORS + ): + admins.append(x.user.id) + return admins + + +async def is_user_admin(chat: Chat, user_id: int) -> bool: + if chat.type in [ChatType.PRIVATE, ChatType.BOT]: + return True + + status = (await chat.get_member(user_id)).status + if status in [ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR]: + return True + + return False diff --git a/Hellbot/functions/convert.py b/Hellbot/functions/convert.py new file mode 100644 index 0000000000000000000000000000000000000000..74b8832b531efdf03ee5a8c8b248008e52d2cc7f --- /dev/null +++ b/Hellbot/functions/convert.py @@ -0,0 +1,98 @@ +import os +import time + +from pyrogram.types import Message +from PIL import Image +from Hellbot.core import Config + +from .tools import runcmd + + +async def convert_to_gif(file: str, is_video: bool = False) -> str: + resultFileName = f"gif_{round(time.time())}.mp4" + + if is_video: + cmd = f"ffmpeg -i '{file}' -c copy '{resultFileName}'" + else: + cmd = f"lottie_convert.py '{file}' '{resultFileName}'" + + await runcmd(cmd) + + return resultFileName + + +async def tgs_to_png(file: str) -> str: + resultFileName = f"png_{round(time.time())}.png" + + cmd = f"lottie_convert.py '{file}' '{resultFileName}'" + + await runcmd(cmd) + + return resultFileName + + +async def image_to_sticker(file: str, max_size: tuple = (512, 512)) -> tuple[bool, str]: + try: + with Image.open(file) as img: + original_width, original_height = img.size + + new_width = min(original_width, max_size[0]) + new_height = min(original_height, max_size[1]) + + if original_width > max_size[0] or original_height > max_size[1]: + img = img.resize((new_width, new_height), Image.LANCZOS) + + file_name = f"sticker_{int(time.time())}.png" + img.save(file_name, "PNG") + + return True, file_name + + except Exception as e: + return False, str(e) + + +async def video_to_png( + file: str, duration: float, output: str = None +) -> tuple[str, bool]: + resultFileName = output or f"{os.path.basename(file)}.png" + cut_at = duration // 2 + + cmd = f"ffmpeg -ss {cut_at} -i '{file}' -vframes 1 '{resultFileName}'" + + _, err, _, _ = await runcmd(cmd) + if err: + return err, False + + return resultFileName, True + + +async def video_to_sticker(file: Message) -> tuple[str, bool]: + try: + if file.animation: + width, height = file.animation.width, file.animation.height + elif file.video: + width, height = file.video.width, file.video.height + else: + return "Unsupported media type.", False + + file_path = await file.download(Config.TEMP_DIR) + output_path = os.path.join(Config.TEMP_DIR, "videoSticker.webm") + + if height > width: + scale_params = f"scale=-1:512" + else: + scale_params = f"scale=512:-1" + + cmd = ( + f"ffmpeg -i {file_path} " + f"-vf fps=30,{scale_params} -t 3 -c:v libvpx-vp9 -b:v 256k -an -pix_fmt yuv420p -auto-alt-ref 0 -loop 0 " + f"-f webm {output_path}" + ) + + await runcmd(cmd) + os.remove(file_path) + + return output_path, True + + except Exception as e: + return f"Error during conversion: {e}", False diff --git a/Hellbot/functions/driver.py b/Hellbot/functions/driver.py new file mode 100644 index 0000000000000000000000000000000000000000..f461154258888621b0931b8327cd4afd84c493b8 --- /dev/null +++ b/Hellbot/functions/driver.py @@ -0,0 +1,318 @@ +import datetime +import json +import random +import re +import time +import urllib.parse +from urllib.parse import quote_plus + +import httpx +import requests +from pytz import country_names, country_timezones, timezone +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.common.by import By + +from Hellbot.core import ENV, Config, db + +from .formatter import format_text + + +class ChromeDriver: + def __init__(self) -> None: + self.carbon_theme = [ + "3024-night", + "a11y-dark", + "blackboard", + "base16-dark", + "base16-light", + "cobalt", + "duotone-dark", + "hopscotch", + "lucario", + "material", + "monokai", + "night-owl", + "nord", + "oceanic-next", + "one-light", + "one-dark", + "panda-syntax", + "paraiso-dark", + "seti", + "shades-of-purple", + "solarized+dark", + "solarized+light", + "synthwave-84", + "twilight", + "verminal", + "vscode", + "yeti", + "zenburn", + ] + + def get(self): + if not Config.CHROME_BIN: + return ( + None, + "ChromeBinaryErr: No binary path found! Install Chromium or Google Chrome.", + ) + + try: + options = Options() + options.binary_location = Config.CHROME_BIN + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--ignore-certificate-errors") + options.add_argument("--disable-gpu") + options.add_argument("--headless=new") + options.add_argument("--test-type") + options.add_argument("--no-sandbox") + options.add_argument("--window-size=1920x1080") + options.add_experimental_option( + "prefs", {"download.default_directory": "./"} + ) + service = Service(Config.CHROME_DRIVER) + driver = webdriver.Chrome(options, service) + return driver, None + except Exception as e: + return None, f"ChromeDriverErr: {e}" + + def close(self, driver: webdriver.Chrome): + driver.close() + driver.quit() + + @property + def get_random_carbon(self) -> str: + url = "https://carbon.now.sh/?l=auto" + url += f"&t={random.choice(self.carbon_theme)}" + url += f"&bg=rgba%28{random.randint(1, 255)}%2C{random.randint(1, 255)}%2C{random.randint(1, 255)}%2C1%29" + url += "&code=" + return url + + async def generate_carbon( + self, driver: webdriver.Chrome, code: str, is_random: bool = False + ) -> str: + filename = f"{round(time.time())}" + BASE_URL = ( + self.get_random_carbon + if is_random + else "https://carbon.now.sh/?l=auto&code=" + ) + + driver.get(BASE_URL + format_text(quote_plus(code))) + driver.command_executor._commands["send_command"] = ( + "POST", + "/session/$sessionId/chromium/send_command", + ) + params = { + "cmd": "Page.setDownloadBehavior", + "params": {"behavior": "allow", "downloadPath": Config.DWL_DIR}, + } + driver.execute("send_command", params) + + driver.find_element(By.XPATH, "//button[@id='export-menu']").click() + driver.find_element(By.XPATH, "//input[@title='filename']").send_keys(filename) + driver.find_element(By.XPATH, "//button[@id='export-png']").click() + + return f"{Config.DWL_DIR}/{filename}.png" + + +class ClimateDriver: + def __init__(self) -> None: + self.weather_api = "https://api.openweathermap.org/data/2.5/weather?lat={0}&lon={1}&appid={2}&units=metric" + self.location_api = ( + "https://api.openweathermap.org/geo/1.0/direct?q={0}&limit=1&appid={1}" + ) + self.pollution_api = "http://api.openweathermap.org/data/2.5/air_pollution?lat={0}&lon={1}&appid={2}" + self.AQI_DICT = { + 1: "Good", + 2: "Fair", + 3: "Moderate", + 4: "Poor", + 5: "Very Poor", + } + + async def fetchLocation(self, city: str, apiKey: str): + response = httpx.get(self.location_api.format(city, apiKey)) + if response.status_code == 200: + data = response.json() + if data: + return data[0]["lat"], data[0]["lon"] + return None, None + + async def fetchWeather(self, city: str, apiKey: str): + lattitude, longitude = await self.fetchLocation(city, apiKey) + if not lattitude and not longitude: + return None + + response = httpx.get(self.weather_api.format(lattitude, longitude, apiKey)) + if response.status_code == 200: + return response.json() + return None + + async def fetchAirPollution(self, city: str, apiKey: str): + lattitude, longitude = await self.fetchLocation(city, apiKey) + if not lattitude and not longitude: + return None + + response = httpx.get(self.pollution_api.format(lattitude, longitude, apiKey)) + if response.status_code == 200: + return response.json() + return None + + async def getTime(self, timestamp: int) -> str: + tz = await db.get_env(ENV.time_zone) or "Asia/Kolkata" + tz = timezone(tz) + return datetime.datetime.fromtimestamp(timestamp, tz=tz).strftime("%I:%M %p") + + def getCountry(self, country_code: str) -> str: + return country_names.get(country_code, "Unknown") + + def getCountryTimezone(self, country_code: str) -> str: + timezones = country_timezones.get(country_code, []) + if timezones: + return ", ".join(timezones) + return "Unknown" + + def getWindData(self, windSpeed: str, windDegree: str) -> str: + dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] + ix = round(windDegree / (360.00 / len(dirs))) + kmph = str(float(windSpeed) * 3.6) + " km/h" + return f"[{dirs[ix % len(dirs)]}] {kmph}" + + +class YoutubeDriver: + def __init__(self, search_terms: str, max_results: int = 5): + self.base_url = "https://youtube.com/results?search_query={0}" + self.search_terms = search_terms + self.max_results = max_results + self.videos = self._search() + + def _search(self): + encoded_search = urllib.parse.quote_plus(self.search_terms) + response = requests.get(self.base_url.format(encoded_search)).text + + while "ytInitialData" not in response: + response = requests.get(self.base_url.format(encoded_search)).text + + results = self._parse_html(response) + + if self.max_results is not None and len(results) > self.max_results: + return results[: self.max_results] + + return results + + def _parse_html(self, response: str): + results = [] + start = response.index("ytInitialData") + len("ytInitialData") + 3 + end = response.index("};", start) + 1 + json_str = response[start:end] + data = json.loads(json_str) + + videos = data["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"][ + "sectionListRenderer" + ]["contents"][0]["itemSectionRenderer"]["contents"] + + for video in videos: + res = {} + if "videoRenderer" in video.keys(): + video_data = video.get("videoRenderer", {}) + _id = video_data.get("videoId", None) + + res["id"] = _id + res["thumbnail"] = f"https://i.ytimg.com/vi/{_id}/hqdefault.jpg" + res["title"] = ( + video_data.get("title", {}).get("runs", [[{}]])[0].get("text", None) + ) + res["channel"] = ( + video_data.get("longBylineText", {}) + .get("runs", [[{}]])[0] + .get("text", None) + ) + res["duration"] = video_data.get("lengthText", {}).get("simpleText", 0) + res["views"] = video_data.get("viewCountText", {}).get( + "simpleText", "Unknown" + ) + res["publish_time"] = video_data.get("publishedTimeText", {}).get( + "simpleText", "Unknown" + ) + res["url_suffix"] = ( + video_data.get("navigationEndpoint", {}) + .get("commandMetadata", {}) + .get("webCommandMetadata", {}) + .get("url", None) + ) + + results.append(res) + return results + + def to_dict(self, clear_cache=True) -> list[dict]: + result = self.videos + if clear_cache: + self.videos = [] + return result + + @staticmethod + def check_url(url: str) -> tuple[bool, str]: + if "&" in url: + url = url[: url.index("&")] + + if "?si=" in url: + url = url[: url.index("?si=")] + + youtube_regex = ( + r"(https?://)?(www\.)?" + r"(youtube|youtu|youtube-nocookie)\.(com|be)/" + r'(video|embed|shorts/|watch\?v=|v/|e/|u/\\w+/|\\w+/)?([^"&?\\s]{11})' + ) + match = re.match(youtube_regex, url) + if match: + return True, match.group(6) + else: + return False, "Invalid YouTube URL!" + + @staticmethod + def song_options() -> dict: + return { + "format": "bestaudio", + "addmetadata": True, + "key": "FFmpegMetadata", + "prefer_ffmpeg": True, + "geo_bypass": True, + "nocheckcertificate": True, + "postprocessors": [ + { + "key": "FFmpegExtractAudio", + "preferredcodec": "mp3", + "preferredquality": "480", + } + ], + "outtmpl": "%(id)s", + "quiet": True, + "logtostderr": False, + } + + @staticmethod + def video_options() -> dict: + return { + "format": "best", + "addmetadata": True, + "key": "FFmpegMetadata", + "prefer_ffmpeg": True, + "geo_bypass": True, + "nocheckcertificate": True, + "postprocessors": [ + { + "key": "FFmpegVideoConvertor", + "preferedformat": "mp4", + } + ], + "outtmpl": "%(id)s.mp4", + "quiet": True, + "logtostderr": False, + } + + +Driver = ChromeDriver() +Climate = ClimateDriver() diff --git a/Hellbot/functions/formatter.py b/Hellbot/functions/formatter.py new file mode 100644 index 0000000000000000000000000000000000000000..1b6ca654c87e07b085d34c096be9cd723457c82f --- /dev/null +++ b/Hellbot/functions/formatter.py @@ -0,0 +1,94 @@ +import math +import re + + +def format_text(text: str) -> str: + emoji_pattern = re.compile( + "[" + "\U0001F600-\U0001F64F" # emoticons + "\U0001F300-\U0001F5FF" # symbols & pictographs + "\U0001F680-\U0001F6FF" # transport & map symbols + "\U0001F700-\U0001F77F" # alchemical symbols + "\U0001F780-\U0001F7FF" # Geometric Shapes Extended + "\U0001F800-\U0001F8FF" # Supplemental Arrows-C + "\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs + "\U0001FA00-\U0001FA6F" # Chess Symbols + "\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A + "\U00002702-\U000027B0" # Dingbats + "\U000024C2-\U0001F251" # enclosed characters + "]+", + flags=re.UNICODE, + ) + + return re.sub(emoji_pattern, "", text) + + +def superscript(text: str) -> str: + superscript_digits = str.maketrans("0123456789", "โฐยนยฒยณโดโตโถโทโธโน") + return text.translate(superscript_digits) + + +def subscript(text: str) -> str: + subscript_digits = str.maketrans("0123456789", "โ‚€โ‚โ‚‚โ‚ƒโ‚„โ‚…โ‚†โ‚‡โ‚ˆโ‚‰") + return text.translate(subscript_digits) + + +def readable_time(seconds: int) -> str: + count = 0 + out_time = "" + time_list = [] + time_suffix_list = ["secs", "mins", "hrs", "days"] + + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + + if len(time_list) == 4: + out_time += time_list.pop() + ", " + + time_list.reverse() + out_time += " ".join(time_list) + + return out_time or "0 secs" + + +def humanbytes(size: int): + if not size: + return "" + power = 2**10 + number = 0 + dict_power_n = {0: " ", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"} + while size > power: + size /= power + number += 1 + return str(round(size, 2)) + " " + dict_power_n[number] + "B" + + +def add_to_dict(data: dict, keys: list, value: str | int | bool = None) -> None: + current_level = data + for key in keys[:-1]: + current_level = current_level.setdefault(key, {}) + current_level[keys[-1]] = value + + +def get_from_dict(data: dict, key: list): + current_level = data + for k in key: + current_level = current_level[k] + return current_level + + +def limit_per_page(limit: int) -> int: + return math.ceil(limit / 10) + + +def secs_to_mins(secs: int) -> str: + mins, secs = divmod(secs, 60) + return f"{mins}:{secs}" diff --git a/Hellbot/functions/images.py b/Hellbot/functions/images.py new file mode 100644 index 0000000000000000000000000000000000000000..017b60bb9ae79774596e99f3e8b24e0cf215c78a --- /dev/null +++ b/Hellbot/functions/images.py @@ -0,0 +1,389 @@ +import calendar +import logging +import os +import random +import textwrap +import time + +import httpx +from icrawler.builtin import BingImageCrawler +from PIL import Image, ImageDraw, ImageEnhance, ImageFont, ImageOps +from unidecode import unidecode + +from .formatter import format_text, limit_per_page + + +def convert_to_png(image: str) -> str: + output_img = f"png_{round(time.time())}.png" + + img = Image.open(image) + img.save(output_img, "PNG") + img.close() + + os.remove(image) + return output_img + + +def add_rounded_corners(img: Image.Image, radius: int = 80): + circle = Image.new("L", (radius * 2, radius * 2), 0) + + draw = ImageDraw.Draw(circle) + draw.ellipse((0, 0, radius * 2, radius * 2), fill=255) + + alpha = Image.new("L", img.size, 255) + w, h = img.size + + alpha.paste(circle.crop((0, 0, radius, radius)), (0, 0)) + alpha.paste(circle.crop((radius, 0, radius * 2, radius)), (w - radius, 0)) + alpha.paste(circle.crop((0, radius, radius, radius * 2)), (0, h - radius)) + alpha.paste( + circle.crop((radius, radius, radius * 2, radius * 2)), (w - radius, h - radius) + ) + + img.putalpha(alpha) + + return img + + +def generate_alive_image( + username: str, profile_pic: str, del_img: bool, font_path: str +) -> str: + if not profile_pic.endswith(".png"): + profile_pic = convert_to_png(profile_pic) + + img = Image.open(profile_pic).convert("RGBA") + img_rotated = img.rotate(45, expand=True) + + width, height = img_rotated.size + left = width / 2 - 480 / 2 + top = height / 2 - 480 / 2 + right = width / 2 + 480 / 2 + bottom = height / 2 + 480 / 2 + + cropped_img = img_rotated.crop((left, top, right, bottom)) + + img_rotated = ImageOps.fit( + cropped_img, (480, 480), method=0, bleed=0.0, centering=(0.5, 0.5) + ) + + img_rounded = add_rounded_corners(img_rotated) + + img = img_rounded.rotate(-45, expand=True) + + background = Image.open("./Hellbot/resources/images/hellbot_alive.png").convert( + "RGBA" + ) + + background.paste(img, (383, 445), img) + draw = ImageDraw.Draw(background) + + text = format_text(username[:25] + ("..." if len(username) > 25 else "")) + + font_size = width // 15 + font = ImageFont.truetype(font_path, font_size, encoding="utf-8") + + text_length = draw.textlength(text, font) + position = ((background.width - text_length) / 2, background.height - 145) + draw.text( + position, + unidecode(text), + (255, 255, 255), + font, + ) + + output_img = f"alive_{int(time.time())}.png" + background.save(output_img, "PNG") + background.close() + + if del_img: + os.remove(profile_pic) + + return output_img + + +async def get_wallpapers( + access: str, + limit: int, + query: str = "", + isRandom: bool = False, +) -> list[str]: + headers = {"Authorization": f"Client-ID {access}"} + + if isRandom: + api = f"https://api.unsplash.com/photos/random?count={limit}" + response = httpx.get(api, headers=headers) + results = response.json() + urls = [i["urls"]["raw"] for i in results] + else: + api = f"https://api.unsplash.com/search/photos?query={query}&page={limit_per_page(limit)}" + response = httpx.get(api, headers=headers) + result = response.json() + urls = [i["urls"]["raw"] for i in result["results"]] + + random.shuffle(urls) + + return urls[:limit] + + +async def deep_fry(img: Image.Image) -> Image.Image: + colours = ( + (random.randint(50, 200), random.randint(40, 170), random.randint(40, 190)), + (random.randint(190, 255), random.randint(170, 240), random.randint(180, 250)), + ) + + img = img.copy().convert("RGB") + img = img.convert("RGB") + + width, height = img.width, img.height + + img = img.resize( + ( + int(width ** random.uniform(0.8, 0.9)), + int(height ** random.uniform(0.8, 0.9)), + ), + resample=Image.LANCZOS, + ) + + img = img.resize( + ( + int(width ** random.uniform(0.85, 0.95)), + int(height ** random.uniform(0.85, 0.95)), + ), + resample=Image.BILINEAR, + ) + + img = img.resize( + ( + int(width ** random.uniform(0.89, 0.98)), + int(height ** random.uniform(0.89, 0.98)), + ), + resample=Image.BICUBIC, + ) + + img = img.resize((width, height), resample=Image.BICUBIC) + img = ImageOps.posterize(img, random.randint(3, 7)) + + overlay = img.split()[0] + overlay = ImageEnhance.Contrast(overlay).enhance(random.uniform(1.0, 2.0)) + overlay = ImageEnhance.Brightness(overlay).enhance(random.uniform(1.0, 2.0)) + overlay = ImageOps.colorize(overlay, colours[0], colours[1]) + + img = Image.blend(img, overlay, random.uniform(0.1, 0.4)) + img = ImageEnhance.Sharpness(img).enhance(random.randint(5, 300)) + + return img + + +async def make_logo(background: str, text: str, font_path: str) -> str: + if not background.endswith(".png"): + background = convert_to_png(background) + + bg = Image.open(background).convert("RGBA") + bgWidth, bgHeight = bg.size + + text = format_text(text) + font_size = bgWidth // len(text) + font = ImageFont.truetype(font_path, font_size, encoding="utf-8") + + draw = ImageDraw.Draw(bg) + text_length = draw.textlength(text, font) + + x = (bgWidth - text_length) // 2 + y = (bgHeight - font_size) // 2 + + draw.text( + (x, y), + unidecode(text), + (255, 255, 255), + font, + stroke_fill=(0, 0, 0), + stroke_width=2, + ) + + output_img = f"logo_{int(time.time())}.png" + bg.save(output_img, "PNG") + bg.close() + + os.remove(background) + + return output_img + + +async def draw_meme( + image_path: str, upper_text: str = "", lower_text: str = "" +) -> list[str]: + image = Image.open(image_path) + width, height = image.size + + draw = ImageDraw.Draw(image) + font_size = int((30 / 500) * width) + font = ImageFont.truetype("./Hellbot/resources/fonts/Montserrat.ttf", font_size) + + curr_height, padding = 20, 5 + for utext in textwrap.wrap(upper_text, 25): + upper_width = draw.textlength(utext, font=font) + draw.text( + ((width - upper_width) / 2, curr_height), + unidecode(utext), + (255, 255, 255), + font, + stroke_width=3, + stroke_fill=(0, 0, 0), + ) + curr_height += font_size + padding + + curr_height = height - font_size + for ltext in reversed(textwrap.wrap(lower_text, 25)): + lower_width = draw.textlength(ltext, font=font) + draw.text( + ((width - lower_width) / 2, curr_height - font_size), + ltext, + (255, 255, 255), + font, + stroke_width=3, + stroke_fill=(0, 0, 0), + ) + curr_height -= font_size + padding + + filename = f"meme_{int(time.time())}" + image.save(f"{filename}.png", "PNG", optimize=True) + image.save(f"{filename}.webp", "WEBP", optimize=True) + image.close() + + return [f"{filename}.png", f"{filename}.webp"] + + +async def remove_bg(api_key: str, image: str) -> str: + response = httpx.post( + "https://api.remove.bg/v1.0/removebg", + files={"image_file": open(image, "rb")}, + data={"size": "auto"}, + headers={"X-Api-Key": api_key}, + ) + filename = f"removedbg_{int(time.time())}.png" + + if response.is_success: + with open(filename, "wb") as f: + f.write(response.content) + else: + raise Exception( + f"RemoveBGError: [{response.status_code}] {response.content.decode('utf-8')}" + ) + + return filename + + +def create_gradient( + size: tuple[int, int], + color_start: tuple[int, int, int], + color_end: tuple[int, int, int], +) -> Image.Image: + gradient = Image.new("RGB", (size)) + draw = ImageDraw.Draw(gradient) + + for x in range(size[0]): + r = int(color_start[0] + (color_end[0] - color_start[0]) * (x / size[0])) + g = int(color_start[1] + (color_end[1] - color_start[1]) * (x / size[0])) + b = int(color_start[2] + (color_end[2] - color_start[2]) * (x / size[0])) + + draw.line([(x, 0), (x, size[1])], fill=(r, g, b)) + + return gradient + + +async def create_calendar(year: int, month: int) -> str: + cal = calendar.monthcalendar(year, month) + month_name = calendar.month_name[month] + + calendar_image = create_gradient((500, 500), (140, 200, 250), (0, 150, 200)) + draw = ImageDraw.Draw(calendar_image) + + month_font = ImageFont.truetype("./Hellbot/resources/fonts/Montserrat.ttf", 40) + month_x = ( + calendar_image.width - draw.textlength(f"{month_name} {year}", month_font) + ) // 2 + month_y = 30 + draw.text( + (month_x, month_y), + f"{month_name} {year}", + (43, 255, 136), + month_font, + stroke_width=2, + stroke_fill=(255, 40, 40), + ) + + week_font = ImageFont.truetype("./Hellbot/resources/fonts/Montserrat.ttf", 23) + weekdays_text = " ".join([day[:3] for day in calendar.day_name]) + textsize = draw.textlength(weekdays_text, week_font) + draw.text( + ((calendar_image.width - textsize) // 2, month_y + 80), + weekdays_text, + (150, 190, 200), + week_font, + stroke_width=2, + stroke_fill=(200, 150, 250), + ) + + scale_factor = 1.5 + cell_size = 30 + padding = 15 + + font = ImageFont.truetype("./Hellbot/resources/fonts/Montserrat.ttf", 30) + + for week_num, week in enumerate(cal): + for day_num, day in enumerate(week): + x = int(day_num * (cell_size + padding) * scale_factor) + y = int((week_num + 3) * (cell_size + padding) * scale_factor) + + cell_width = int(cell_size * scale_factor) + cell_height = int(cell_size * scale_factor) + + text_x = ( + int(x + (cell_width - draw.textlength(str(day), font=font)) // 2) + + cell_size + ) + text_y = ( + int(y + (cell_height - draw.textlength(str(day), font=font)) // 2) - 55 + ) + + if day != 0: + draw.text( + (text_x, text_y), + str(day), + (240, 200, 100), + font, + stroke_width=1, + stroke_fill=(0, 0, 0), + ) + + filename = f"calendar_{int(time.time())}.png" + calendar_image.save(filename, "PNG") + calendar_image.close() + + return filename + + +async def create_thumbnail(photo: str, xy: tuple[int, int], file_size: int): + img = Image.open(photo) + img.thumbnail(xy) + + size_in_bytes = file_size * 1024 + quality = 90 + + while True: + img.save(photo, "JPEG", quality=quality, optimize=True) + if os.path.getsize(photo) <= size_in_bytes: + break + + quality -= 5 + + return photo + + +async def download_images(query: str, limit: int) -> list[str]: + offset = random.randint(0, 20) + + crawler = BingImageCrawler(log_level=logging.ERROR) + crawler.crawl(query, offset=offset, max_num=limit) + + return [os.path.join("images", image) for image in os.listdir("images")] diff --git a/Hellbot/functions/media.py b/Hellbot/functions/media.py new file mode 100644 index 0000000000000000000000000000000000000000..fb7f604d621befd0afaa3b597c7fd50af4597fc4 --- /dev/null +++ b/Hellbot/functions/media.py @@ -0,0 +1,192 @@ +import os +from typing import Union + +import requests +from pyrogram import Client +from pyrogram.file_id import FileId +from pyrogram.raw.functions.messages import UploadMedia +from pyrogram.raw.types import ( + DocumentAttributeFilename, + InputDocument, + InputMediaUploadedDocument, +) +from pyrogram.types import Animation, Audio, Document, Message, Photo, Sticker, Video + +from Hellbot.core import Symbols + + +async def get_metedata(media: Union[Animation, Audio, Document, Photo, Sticker, Video]): + output = "๐Ÿ“„ MetaData:\n\n" + if isinstance(media, Animation): + output += f"{Symbols.diamond_2} File ID: {media.file_id}\n" + output += f"{Symbols.diamond_2} Width: {media.width}\n" + output += f"{Symbols.diamond_2} Height: {media.height}\n" + output += ( + f"{Symbols.diamond_2} Duration: {media.duration}\n" + ) + output += ( + f"{Symbols.diamond_2} File Name: {media.file_name}\n" + ) + output += ( + f"{Symbols.diamond_2} Mime Type: {media.mime_type}\n" + ) + output += ( + f"{Symbols.diamond_2} File Size: {media.file_size}\n" + ) + output += f"{Symbols.diamond_2} Date: {media.date}\n" + output += f"{Symbols.diamond_2} File Type: Animation\n" + elif isinstance(media, Audio): + output += f"{Symbols.diamond_2} File ID: {media.file_id}\n" + output += ( + f"{Symbols.diamond_2} Duration: {media.duration}\n" + ) + output += ( + f"{Symbols.diamond_2} Performer: {media.performer}\n" + ) + output += f"{Symbols.diamond_2} Title: {media.title}\n" + output += ( + f"{Symbols.diamond_2} File Name: {media.file_name}\n" + ) + output += ( + f"{Symbols.diamond_2} Mime Type: {media.mime_type}\n" + ) + output += ( + f"{Symbols.diamond_2} File Size: {media.file_size}\n" + ) + output += f"{Symbols.diamond_2} Date: {media.date}\n" + output += f"{Symbols.diamond_2} File Type: Audio\n" + elif isinstance(media, Document): + output += f"{Symbols.diamond_2} File ID: {media.file_id}\n" + output += ( + f"{Symbols.diamond_2} File Name: {media.file_name}\n" + ) + output += ( + f"{Symbols.diamond_2} Mime Type: {media.mime_type}\n" + ) + output += ( + f"{Symbols.diamond_2} File Size: {media.file_size}\n" + ) + output += f"{Symbols.diamond_2} Date: {media.date}\n" + output += f"{Symbols.diamond_2} File Type: Document\n" + elif isinstance(media, Photo): + output += f"{Symbols.diamond_2} File ID: {media.file_id}\n" + output += f"{Symbols.diamond_2} Width: {media.width}\n" + output += f"{Symbols.diamond_2} Height: {media.height}\n" + output += f"{Symbols.diamond_2} File Name: photo.jpg\n" + output += f"{Symbols.diamond_2} Mime Type: image/jpeg\n" + output += ( + f"{Symbols.diamond_2} File Size: {media.file_size}\n" + ) + output += f"{Symbols.diamond_2} Date: {media.date}\n" + output += f"{Symbols.diamond_2} File Type: Photo\n" + elif isinstance(media, Sticker): + output += f"{Symbols.diamond_2} File ID: {media.file_id}\n" + output += f"{Symbols.diamond_2} Width: {media.width}\n" + output += f"{Symbols.diamond_2} Height: {media.height}\n" + output += ( + f"{Symbols.diamond_2} File Name: {media.file_name}\n" + ) + output += ( + f"{Symbols.diamond_2} Mime Type: {media.mime_type}\n" + ) + output += ( + f"{Symbols.diamond_2} File Size: {media.file_size}\n" + ) + output += f"{Symbols.diamond_2} Date: {media.date}\n" + output += f"{Symbols.diamond_2} Emoji: {media.emoji}\n" + output += ( + f"{Symbols.diamond_2} Set Name: {media.set_name}\n" + ) + output += f"{Symbols.diamond_2} File Type: Sticker\n" + elif isinstance(media, Video): + output += f"{Symbols.diamond_2} File ID: {media.file_id}\n" + output += f"{Symbols.diamond_2} Width: {media.width}\n" + output += f"{Symbols.diamond_2} Height: {media.height}\n" + output += ( + f"{Symbols.diamond_2} Duration: {media.duration}\n" + ) + output += ( + f"{Symbols.diamond_2} File Name: {media.file_name}\n" + ) + output += ( + f"{Symbols.diamond_2} Mime Type: {media.mime_type}\n" + ) + output += ( + f"{Symbols.diamond_2} File Size: {media.file_size}\n" + ) + output += f"{Symbols.diamond_2} Date: {media.date}\n" + output += f"{Symbols.diamond_2} File Type: Video\n" + else: + return None + + return output + + +def get_media_text_ocr(filename: str, api_key: str, language: str = "eng") -> dict: + payload = { + "isOverlayRequired": False, + "apikey": api_key, + "language": language, + } + + with open(filename, "rb") as f: + r = requests.post( + "https://api.ocr.space/parse/image", + files={filename: f}, + data=payload, + ) + + return r.json() + + +async def upload_media(client: Client, chat_id: int, file: str) -> InputDocument: + media = await client.invoke( + UploadMedia( + peer=(await client.resolve_peer(chat_id)), + media=InputMediaUploadedDocument( + file=(await client.save_file(file)), + mime_type=client.guess_mime_type(file) or "application/zip", + attributes=[ + DocumentAttributeFilename(file_name=os.path.basename(file)) + ], + force_file=True, + ), + ), + ) + + return InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference, + ) + + +async def get_media_from_id(file_id: str) -> InputDocument: + file = FileId.decode(file_id) + + return InputDocument( + id=file.media_id, + access_hash=file.access_hash, + file_reference=file.file_reference, + ) + + +async def get_media_fileid(message: Message) -> str | None: + file_id = None + if message.photo: + file_id = message.photo.file_id + elif message.animation: + file_id = message.animation.file_id + elif message.audio: + file_id = message.audio.file_id + elif message.document: + file_id = message.document.file_id + elif message.video: + file_id = message.video.file_id + elif message.sticker: + file_id = message.sticker.file_id + elif message.video_note: + file_id = message.video_note.file_id + elif message.voice: + file_id = message.voice.file_id + return file_id diff --git a/Hellbot/functions/paste.py b/Hellbot/functions/paste.py new file mode 100644 index 0000000000000000000000000000000000000000..1aec968c654c27746a2d454f982d3428abe7fa89 --- /dev/null +++ b/Hellbot/functions/paste.py @@ -0,0 +1,49 @@ +import uuid + +import requests + +from .utility import TGraph + + +def post_to_telegraph( + title: str, + content: str, + author: str = "[ ๐–ง๐–พ๐—…๐—…๐–ก๐—ˆ๐— ]", + url: str = "https://t.me/Its_HellBot", +) -> str: + content = content.replace("\n", "
") + try: + response = TGraph.telegraph.create_page( + title=title, + html_content=content, + author_name=author, + author_url=url, + ) + except Exception: + rnd_key = uuid.uuid4().hex[:8] + title = f"{title}_{rnd_key}" + response = TGraph.telegraph.create_page( + title=title, + html_content=content, + author_name=author, + author_url=url, + ) + + return f"https://te.legra.ph/{response['path']}" + + +def spaceBin(data: str, extension: str = "none") -> str: + data = { + "content": data, + "extension": extension, + } + + resp = requests.post("https://spaceb.in/api/v1/documents/", data) + + try: + result = resp.json() + url = f"https://spaceb.in/{result['payload']['id']}" + except Exception: + url = "" + + return url diff --git a/Hellbot/functions/scraping.py b/Hellbot/functions/scraping.py new file mode 100644 index 0000000000000000000000000000000000000000..aa366a904e508654f451ad2dd634105bbe26eaa6 --- /dev/null +++ b/Hellbot/functions/scraping.py @@ -0,0 +1,528 @@ +import calendar +import time + +import httpx +from bs4 import BeautifulSoup +from urllib.parse import quote, urlparse +from Hellbot.core import Symbols + +from .paste import post_to_telegraph +from .templates import ( + airing_templates, + anilist_user_templates, + anime_template, + character_templates, + manga_templates, +) + +anime_query = """query ($id: Int,$search: String) { + Page (perPage: 10) { + media (id: $id, type: ANIME,search: $search) { + id + title { + romaji + english + native + } + type + format + status + description (asHtml: false) + episodes + duration + countryOfOrigin + source + trailer{ + id + site + } + genres + tags { + name + } + isAdult + averageScore + studios (isMain: true){ + nodes{ + name + } + } + nextAiringEpisode{ + episode + } + siteUrl + } + } +}""" + + +manga_query = """query ($id: Int,$search: String) { + Page (perPage: 10) { + media (id: $id, type: MANGA,search: $search) { + id + title { + romaji + english + native + } + type + format + status + description (asHtml: false) + chapters + volumes + countryOfOrigin + source + genres + isAdult + averageScore + siteUrl + } + } +}""" + + +character_query = """query ($id: Int, $search: String) { + Page { + characters (id: $id, search: $search) { + id + name { + full + native + } + image { + large + } + description + gender + dateOfBirth { + year + month + day + } + age + bloodType + siteUrl + favourites + media { + nodes { + title { + romaji + english + native + } + type + format + siteUrl + } + } + } + } +}""" + + +airing_query = """query ($id: Int, $idMal:Int, $search: String) { + Media (id: $id, idMal: $idMal, search: $search, type: ANIME) { + id + title { + romaji + english + native + } + status + episodes + countryOfOrigin + nextAiringEpisode { + airingAt + timeUntilAiring + episode + } + } +}""" + + +anilist_user_query = """query($id: Int, $search: String) { + User(id: $id, name: $search) { + id + name + siteUrl + statistics { + anime { + count + meanScore + minutesWatched + episodesWatched + } + manga { + count + meanScore + chaptersRead + volumesRead + } + } + } +}""" + + +def is_valid_url(text: str) -> bool: + try: + result = urlparse(text) + return all([result.scheme, result.netloc]) + except ValueError: + return False + + +def post_request(query: str, search_term: str): + url = "https://graphql.anilist.co" + variables = {"search": search_term} + r = httpx.post(url, json={"query": query, "variables": variables}) + if r.status_code != 200: + return None + return r.json() + + +def get_country_flag(country: str) -> str: + base = ord('๐Ÿ‡ฆ') + emoji_flag = "".join(chr(base + ord(letter) - ord("A")) for letter in country) + return emoji_flag + + +def get_date(data: dict) -> str: + try: + year = data["year"] or "" + if not year and not data["month"]: + return "N/A" + day = data["day"] + if 10 <= day % 100 <= 20: + day = f"{day}th" + else: + day_dict = {1: "st", 2: "nd", 3: "rd"} + day = f"{day}{day_dict.get(day % 10, 'th')}" + month_name = calendar.month_name[int(data["month"])] + return f"{day} {month_name} {year}" + except: + return "N/A" + + +def search_anime_filler(search_term: str): + BASE = "https://www.animefillerlist.com/shows/" + + response = httpx.get(BASE).text + soup = BeautifulSoup(response, "html.parser") + div = soup.findAll("div", {"class": "Group"}) + + index = {} + for i in div: + li = i.findAll("li") + for j in li: + index[j.text] = j.a["href"].split("/")[-1] + + results = {} + keys = list(index.keys()) + for i in range(len(keys)): + if search_term.lower() in keys[i].lower(): + results[keys[i]] = index[keys[i]] + + for result in results.keys(): + data = [] + response = httpx.get(BASE + results[result]).text + soup = BeautifulSoup(response, "html.parser") + base_div = soup.find("div", {"id": "Condensed"}) + + if not base_div: + continue + + divs = base_div.findAll("div") + for div in divs: + heading = div.find("span", {"class": "Label"}).text + episodes = div.find("span", {"class": "Episodes"}).text + data.append((heading, episodes)) + + yield result, data + + +async def get_filler_info(search_term: str) -> str: + animes = search_anime_filler(search_term) + message = "" + + for anime in animes: + html_message = f"{Symbols.check_mark} {anime[0]} Filler Guide:\n\n" + + for data in anime[1]: + html_message += ( + f"

{Symbols.bullet} {data[0]}:

\n{data[1]}\n\n" + ) + + paste = post_to_telegraph(anime[0] + " Filler Guide", html_message) + message += f"{Symbols.anchor} [{anime[0]}]({paste})\n" + + return message + + +def search_watch_order(anime: str): + response = httpx.get(f"https://www.animechrono.com/search?q={quote(anime)}") + soup = BeautifulSoup(response.text, "html.parser") + + item_div = soup.find("div", {"class": "search-result-items"}) + animes = item_div.find_all("a", {"class": "list-item search w-inline-block"}) + + for anime in animes: + name = anime.find("h1", "h1 search").text + yield name, anime["href"] + + +async def get_watch_order(search_term: str) -> str: + animes = search_watch_order(search_term) + message = "" + + for anime in animes: + message += f"**{Symbols.anchor} {anime[0]}:** \n" + response = httpx.get("https://www.animechrono.com" + anime[1]).text + soup = BeautifulSoup(response, "html.parser") + + elements = soup.find_all("h2", {"class": "heading-5"}) + for element in elements: + message += f" {Symbols.bullet} `{element.text}`\n" + message += "\n" + + return message + + +async def get_anime_info(search_term: str) -> tuple[str, str]: + data = post_request(anime_query, search_term) + if not data: + return "", "" + + data = data["data"]["Page"]["media"][0] + english_title = data["title"]["english"] + native_title = data["title"]["native"] + if not english_title: + english_title = data["title"]["romaji"] + flag = get_country_flag(data["countryOfOrigin"]) + name = f"**[{flag}] {english_title} ({native_title})**" + + anime_id = data["id"] + score = data["averageScore"] if data["averageScore"] else "N/A" + source = str(data["source"]).title() if data["source"] else "N/A" + mtype = str(data["type"]).title() if data["type"] else "N/A" + synopsis = data["description"] + + episodes = data["episodes"] + if not episodes: + try: + episodes = data["nextAiringEpisode"]["episode"] - 1 + except: + episodes = "N/A" + + duration = data["duration"] if data["duration"] else "N/A" + status = str(data["status"]).title() if data["status"] else "N/A" + format = str(data["format"]).title() if data["format"] else "N/A" + genre = ", ".join(data["genres"]) if data["genres"] else "N/A" + tags = ", ".join([i["name"] for i in data["tags"][:5]]) if data["tags"] else "N/A" + studio = data["studios"]["nodes"][0]["name"] if data["studios"]["nodes"] else "N/A" + siteurl = f"[Anilist Website]({data['siteUrl']})" if data["siteUrl"] else "N/A" + isAdult = data["isAdult"] + + trailer = "N/A" + if data["trailer"] and data["trailer"]["site"] == "youtube": + trailer = f"[Youtube](https://youtu.be/{data['trailer']['id']})" + + response = httpx.get(f"https://img.anili.st/media/{anime_id}").content + banner = f"anime_{anime_id}.jpg" + with open(banner, "wb") as f: + f.write(response) + + description = post_to_telegraph(name, synopsis) + + message = await anime_template( + name=name, + score=score, + source=source, + mtype=mtype, + episodes=episodes, + duration=duration, + status=status, + format=format, + genre=genre, + studio=studio, + trailer=trailer, + siteurl=siteurl, + description=description, + tags=tags, + isAdult=isAdult, + ) + + return message, banner + + +async def get_manga_info(search_term: str) -> tuple[str, str]: + data = post_request(manga_query, search_term) + if not data: + return "", "" + + data = data["data"]["Page"]["media"][0] + english_title = data["title"]["english"] + native_title = data["title"]["native"] + if not english_title: + english_title = data["title"]["romaji"] + flag = get_country_flag(data["countryOfOrigin"]) + name = f"**[{flag}] {english_title} ({native_title})**" + + manga_id = data["id"] + score = data["averageScore"] if data["averageScore"] else "N/A" + source = str(data["source"]).title() if data["source"] else "N/A" + mtype = str(data["type"]).title() if data["type"] else "N/A" + synopsis = data["description"] + + chapters = data["chapters"] if data["chapters"] else "N/A" + volumes = data["volumes"] if data["volumes"] else "N/A" + status = str(data["status"]).title() if data["status"] else "N/A" + format = str(data["format"]).title() if data["format"] else "N/A" + genre = ", ".join(data["genres"]) if data["genres"] else "N/A" + siteurl = f"[Anilist Website]({data['siteUrl']})" if data["siteUrl"] else "N/A" + isAdult = data["isAdult"] + + response = httpx.get(f"https://img.anili.st/media/{manga_id}").content + banner = f"manga_{manga_id}.jpg" + with open(banner, "wb") as f: + f.write(response) + + description = post_to_telegraph(name, synopsis) + + message = await manga_templates( + name=name, + score=score, + source=source, + mtype=mtype, + chapters=chapters, + volumes=volumes, + status=status, + format=format, + genre=genre, + siteurl=siteurl, + description=description, + isAdult=isAdult, + ) + + return message, banner + + +async def get_character_info(search_term: str) -> tuple[str, str]: + data = post_request(character_query, search_term) + if not data: + return "", "" + + data = data["data"]["Page"]["characters"][0] + name = f"**{data['name']['full']} ({data['name']['native']})**" + char_id = data["id"] + + description = data["description"].split("\n\n", 1)[0] + gender = data["gender"] if data["gender"] else "N/A" + date_of_birth = get_date(data["dateOfBirth"]) + age = data["age"] if data["age"] else "N/A" + blood_type = data["bloodType"] if data["bloodType"] else "N/A" + siteurl = f"[Anilist Website]({data['siteUrl']})" if data["siteUrl"] else "N/A" + favorites = data["favourites"] if data["favourites"] else "N/A" + + cameo = data["media"]["nodes"][0] if data["media"]["nodes"] else {} + if cameo: + role_in = f"\nโ•ฐโžข **๐–ฑ๐—ˆ๐—…๐–พ ๐–จ๐—‡:** [{cameo['title']['romaji']}]({cameo['siteUrl']})" + else: + role_in = "" + + response = httpx.get(data["image"]["large"]).content + banner = f"character_{char_id}.jpg" + with open(banner, "wb") as f: + f.write(response) + + message = await character_templates( + name=name, + gender=gender, + date_of_birth=date_of_birth, + age=age, + blood_type=blood_type, + favorites=favorites, + siteurl=siteurl, + role_in=role_in, + description=description, + ) + + return message, banner + + +async def get_airing_info(search_term: str) -> tuple[str, str]: + data = post_request(airing_query, search_term) + if not data: + return "", "" + + data = data["data"]["Media"] + english_title = data["title"]["english"] + native_title = data["title"]["native"] + if not english_title: + english_title = data["title"]["romaji"] + flag = get_country_flag(data["countryOfOrigin"]) + name = f"**[{flag}] {english_title} ({native_title})**" + + episode = data["episodes"] + if not episode: + try: + episode = data["nextAiringEpisode"]["episode"] + except: + episode = "N/A" + + response = httpx.get(f"https://img.anili.st/media/{data['id']}").content + banner = f"airing_{data['id']}.jpg" + with open(banner, "wb") as f: + f.write(response) + + status = str(data["status"]).title() if data["status"] else "N/A" + next_date = data["nextAiringEpisode"]["airingAt"] if data["nextAiringEpisode"] else "" + + airing_info = "" + if next_date: + airing_info = f"\n**๐Ÿ—“๏ธ {time.ctime(next_date)}**" + + message = await airing_templates( + name=name, + status=status, + episode=episode, + airing_info=airing_info, + ) + + return message, banner + + +async def get_anilist_user_info(search_term: str) -> tuple[str, str]: + data = post_request(anilist_user_query, search_term) + if not data: + return "", "" + + data = data["data"]["User"] + user_id = data["id"] + name = data['name'] + siteurl = f"[Anilist Website]({data['siteUrl']})" if data["siteUrl"] else "N/A" + + response = httpx.get(f"https://img.anili.st/user/{user_id}").content + banner = f"aniuser_{user_id}.jpg" + with open(banner, "wb") as f: + f.write(response) + + anime_stats = data["statistics"]["anime"] + manga_stats = data["statistics"]["manga"] + + anime = ( + anime_stats["count"] or 0, + anime_stats["meanScore"] or 0, + anime_stats["minutesWatched"] or 0, + anime_stats["episodesWatched"] or 0, + ) + manga = ( + manga_stats["count"] or 0, + manga_stats["meanScore"] or 0, + manga_stats["chaptersRead"] or 0, + manga_stats["volumesRead"] or 0, + ) + + message = await anilist_user_templates(name, anime, manga, siteurl) + + return message, banner diff --git a/Hellbot/functions/sticker.py b/Hellbot/functions/sticker.py new file mode 100644 index 0000000000000000000000000000000000000000..3e37e531eef76cf994b1748c319cdbc19d1868d8 --- /dev/null +++ b/Hellbot/functions/sticker.py @@ -0,0 +1,135 @@ +from typing import Tuple + +from emoji import EMOJI_DATA +from pyrogram import Client +from pyrogram.raw.functions.messages import GetStickerSet +from pyrogram.raw.functions.stickers import AddStickerToSet, CreateStickerSet, RemoveStickerFromSet +from pyrogram.raw import base, types +from pyrogram.types import Message + +from .media import get_media_from_id, upload_media + + +def is_emoji(text: str) -> bool: + return any(c in EMOJI_DATA for c in text) + + +def get_emoji_and_id(message: Message) -> Tuple[int, str]: + pack_id = None + pack_emoji = None + + for command in message.command: + if command.isdigit(): + pack_id = int(command) + elif is_emoji(command): + pack_emoji = command + + if pack_id is None: + pack_id = 1 + + if pack_emoji is None: + sticker = message.reply_to_message.sticker + try: + pack_emoji = sticker.emoji if sticker and sticker.emoji else "๐Ÿ€" + except: + pack_emoji = "๐Ÿ€" + + return pack_id, pack_emoji + + +def check_sticker_data(replied: Message) -> Tuple[str | None, bool, bool, bool, int]: + pack_type = None + is_animated = False + is_video = False + is_static = False + pack_limit = 50 + + if replied.sticker: + if replied.sticker.is_animated: + pack_type, is_animated = "animated", True + elif replied.sticker.is_video: + pack_type, is_video = "video", True + else: + pack_type, is_static, pack_limit = "static", True, 120 + + elif replied.photo: + pack_type, is_static, pack_limit = "static", True, 120 + + elif replied.video or replied.animation: + pack_type, is_video = "video", True + + elif replied.document: + mime_type = replied.document.mime_type.lower() + if mime_type.startswith("video/"): + pack_type, is_video = "video", True + elif mime_type.startswith("image/"): + pack_type, is_static, pack_limit = "static", True, 120 + elif mime_type in ["application/x-tgsticker", "application/x-bad-tgsticker"]: + pack_type, is_animated = "animated", True + + return pack_type, is_animated, is_video, is_static, pack_limit + + +async def create_sticker( + client: Client, + chat_id: int, + file: str, + emoji: str, +) -> types.InputStickerSetItem: + sticker = await upload_media(client, chat_id, file) + + return types.InputStickerSetItem( + document=sticker, + emoji=emoji, + ) + + +async def remove_sticker(client: Client, stickerid: str) -> base.messages.StickerSet: + sticker = await get_media_from_id(stickerid) + return await client.invoke(RemoveStickerFromSet(sticker=sticker)) + + +async def get_sticker_set(client: Client, name: str) -> base.messages.StickerSet | None: + try: + return await client.invoke( + GetStickerSet( + stickerset=types.InputStickerSetShortName(short_name=name), + hash=0, + ) + ) + except: + return None + + +async def add_sticker( + client: Client, + stickerset: base.messages.StickerSet, + sticker: base.InputStickerSetItem, +) -> base.messages.StickerSet: + return await client.invoke( + AddStickerToSet( + stickerset=types.InputStickerSetShortName(short_name=stickerset.set.short_name), + sticker=sticker, + ) + ) + + +async def new_sticker_set( + client: Client, + user_id: int, + title: str, + short_name: str, + stickers: list[base.InputStickerSetItem], + animated: bool, + video: bool, +) -> base.messages.StickerSet: + return await client.invoke( + CreateStickerSet( + user_id=(await client.resolve_peer(user_id)), + title=title, + short_name=short_name, + stickers=stickers, + animated=animated, + videos=video, + ) + ) diff --git a/Hellbot/functions/templates.py b/Hellbot/functions/templates.py new file mode 100644 index 0000000000000000000000000000000000000000..e9e89aa060a1c7c0592ac9fd9695281e2c942a40 --- /dev/null +++ b/Hellbot/functions/templates.py @@ -0,0 +1,466 @@ +import random + +from Hellbot import __version__ +from Hellbot.core import ENV, db + +ALIVE_TEMPLATES = [ + ( + "โ€ขโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข\n" + "โ€ข ๐‡แด‡สŸสŸ๐แดแด› ๐ˆs ๐€สŸษชแด แด‡ โ€ข\n" + "โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข\n" + "โ•ฐโžข แดแดกษดแด‡ส€ ยป {owner}\n" + "โ•ฐโžข แด˜สส€แดษขส€แด€แด ยป {pyrogram}\n" + "โ•ฐโžข สœแด‡สŸสŸส™แดแด› ยป {hellbot}\n" + "โ•ฐโžข แด˜สแด›สœแดษด ยป {python}\n" + "โ•ฐโžข แดœแด˜แด›ษชแดแด‡ ยป {uptime}\n" + "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข\n" + "๐–ก๐—’ ยฉ @HellBot_Networks\n" + "โ€ขโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข\n" + ), +] + +PING_TEMPLATES = [ + """**๐Ÿ€ ๐–ฏ๐—‚๐—‡๐—€!** + + โš˜ **ั•ฯั”ั”โˆ‚:** {speed} m/s + โš˜ **ฯ…ฯั‚ฮนะผั”:** {uptime} + โš˜ **ฯƒฯ‰ฮทั”ั:** {owner}""", +] + +HELP_MENU_TEMPLATES = [ + """**๐Ÿ€ ๐–ง๐–พ๐—…๐—‰ ๐–ฌ๐–พ๐—‡๐—Ž ๐–ฟ๐—ˆ๐—‹:** {owner} + +__๐Ÿ“ƒ ๐–ซ๐—ˆ๐–บ๐–ฝ๐–พ๐–ฝ__ **{plugins} ๐—‰๐—…๐—Ž๐—€๐—‚๐—‡๐—Œ** __๐—๐—‚๐—๐— ๐–บ ๐—๐—ˆ๐—๐–บ๐—… ๐—ˆ๐–ฟ__ **{commands} ๐–ผ๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ๐—Œ.** + +**๐Ÿ“‘ Page:** __{current}/{last}__""", +] + +COMMAND_MENU_TEMPLATES = [ + """**๐–ฏ๐—…๐—Ž๐—€๐—‚๐—‡ ๐–ฅ๐—‚๐—…๐–พ:** `{file}` +**๐–ฏ๐—…๐—Ž๐—€๐—‚๐—‡ ๐–จ๐—‡๐–ฟ๐—ˆ:** __{info} ๐Ÿ€__ + +**๐Ÿ“ƒ ๐–ซ๐—ˆ๐–บ๐–ฝ๐–พ๐–ฝ ๐–ข๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ๐—Œ:** `{commands}`""", +] + +ANIME_TEMPLATES = [ + """ +{name} + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ฒ๐–ผ๐—ˆ๐—‹๐–พ:** `{score}` +โ•ฐโžข **๐–ฒ๐—ˆ๐—Ž๐—‹๐–ผ๐–พ:** `{source}` +โ•ฐโžข **๐–ณ๐—’๐—‰๐–พ:** `{mtype}` +โ•ฐโžข **๐–ค๐—‰๐—‚๐—Œ๐—ˆ๐–ฝ๐–พ๐—Œ:** `{episodes}` +โ•ฐโžข **๐–ฃ๐—Ž๐—‹๐–บ๐—๐—‚๐—ˆ๐—‡:** `{duration} minutes` +โ•ฐโžข **๐–ฒ๐—๐–บ๐—๐—Ž๐—Œ:** `{status}` +โ•ฐโžข **๐–ฅ๐—ˆ๐—‹๐—†๐–บ๐—:** `{format}` +โ•ฐโžข **๐–ฆ๐–พ๐—‡๐—‹๐–พ:** `{genre}` +โ•ฐโžข **๐–ณ๐–บ๐—€๐—Œ:** `{tags}` +โ•ฐโžข **๐– ๐–ฝ๐—Ž๐—…๐— ๐–ฑ๐–บ๐—๐–พ๐–ฝ:** `{isAdult}` +โ•ฐโžข **๐–ฒ๐—๐—Ž๐–ฝ๐—‚๐—ˆ:** `{studio}` +โ•ฐโžข **๐–ณ๐—‹๐–บ๐—‚๐—…๐–พ๐—‹:** {trailer} +โ•ฐโžข **๐–ถ๐–พ๐–ป๐—Œ๐—‚๐—๐–พ:** {siteurl} +โ•ฐโžข **๐–ฒ๐—’๐—‡๐—ˆ๐—‰๐—Œ๐—‚๐—Œ:** [๐–ข๐—…๐—‚๐–ผ๐—„ ๐–ง๐–พ๐—‹๐–พ]({description}) +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +""" +] + +MANGA_TEMPLATES = [ + """ +{name} + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ฒ๐–ผ๐—ˆ๐—‹๐–พ:** `{score}` +โ•ฐโžข **๐–ฒ๐—ˆ๐—Ž๐—‹๐–ผ๐–พ:** `{source}` +โ•ฐโžข **๐–ณ๐—’๐—‰๐–พ:** `{mtype}` +โ•ฐโžข **๐–ข๐—๐–บ๐—‰๐—๐–พ๐—‹๐—Œ:** `{chapters}` +โ•ฐโžข **๐–ต๐—ˆ๐—…๐—Ž๐—†๐–พ๐—Œ:** `{volumes}` +โ•ฐโžข **๐–ฒ๐—๐–บ๐—๐—Ž๐—Œ:** `{status}` +โ•ฐโžข **๐–ฅ๐—ˆ๐—‹๐—†๐–บ๐—:** `{format}` +โ•ฐโžข **๐–ฆ๐–พ๐—‡๐—‹๐–พ:** `{genre}` +โ•ฐโžข **๐– ๐–ฝ๐—Ž๐—…๐— ๐–ฑ๐–บ๐—๐–พ๐–ฝ:** `{isAdult}` +โ•ฐโžข **๐–ถ๐–พ๐–ป๐—Œ๐—‚๐—๐–พ:** {siteurl} +โ•ฐโžข **๐–ฒ๐—’๐—‡๐—ˆ๐—‰๐—Œ๐—‚๐—Œ:** [๐–ข๐—…๐—‚๐–ผ๐—„ ๐–ง๐–พ๐—‹๐–พ]({description}) +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +""" +] + +CHARACTER_TEMPLATES = [ + """ +{name} + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ฆ๐–พ๐—‡๐–ฝ๐–พ๐—‹:** `{gender}` +โ•ฐโžข **๐–ฃ๐–บ๐—๐–พ ๐—ˆ๐–ฟ ๐–ก๐—‚๐—‹๐—๐—:** `{date_of_birth}` +โ•ฐโžข **๐– ๐—€๐–พ:** `{age}` +โ•ฐโžข **๐–ก๐—…๐—ˆ๐—ˆ๐–ฝ ๐–ณ๐—’๐—‰๐–พ:** `{blood_type}` +โ•ฐโžข **๐–ฅ๐–บ๐—๐—ˆ๐—Ž๐—‹๐—‚๐—๐–พ๐—Œ:** `{favorites}` +โ•ฐโžข **๐–ถ๐–พ๐–ป๐—Œ๐—‚๐—๐–พ:** {siteurl}{role_in} +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +{description} +""" +] + +AIRING_TEMPLATES = [ + """ +{name} + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ฒ๐—๐–บ๐—๐—Ž๐—Œ:** `{status}` +โ•ฐโžข **๐–ค๐—‰๐—‚๐—Œ๐—ˆ๐–ฝ๐–พ:** `{episode}` +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข{airing_info} +""" +] + +ANILIST_USER_TEMPLATES = [ + """ +**๐Ÿ’ซ {name}** + +โ•ญโ”€โ”€โ”€โ”€ ๐– ๐—‡๐—‚๐—†๐–พ โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ข๐—ˆ๐—Ž๐—‡๐—:** `{anime_count}` +โ•ฐโžข **๐–ฒ๐–ผ๐—ˆ๐—‹๐–พ:** `{anime_score}` +โ•ฐโžข **๐–ฌ๐—‚๐—‡๐—Ž๐—๐–พ๐—Œ ๐–ฒ๐—‰๐–พ๐—‡๐—:** `{minutes}` +โ•ฐโžข **๐–ค๐—‰๐—‚๐—Œ๐—ˆ๐–ฝ๐–พ๐—Œ ๐–ถ๐–บ๐—๐–ผ๐—๐–พ๐–ฝ:** `{episodes}` +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ญโ”€โ”€โ”€โ”€ ๐–ฌ๐–บ๐—‡๐—€๐–บ โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ข๐—ˆ๐—Ž๐—‡๐—:** `{manga_count}` +โ•ฐโžข **๐–ฒ๐–ผ๐—ˆ๐—‹๐–พ:** `{manga_score}` +โ•ฐโžข **๐–ข๐—๐–บ๐—‰๐—๐–พ๐—‹๐—Œ:** `{chapters}` +โ•ฐโžข **๐–ต๐—ˆ๐—…๐—Ž๐—†๐–พ๐—Œ:** `{volumes}` +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข + +๐–ถ๐–พ๐–ป๐—Œ๐—‚๐—๐–พ: {siteurl} +""" +] + +CLIMATE_TEMPLATES = [ + """ +๐ŸŒ† {city_name}, {country} + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ถ๐–พ๐–บ๐—๐—๐–พ๐—‹:** {weather} +โ•ฐโžข **๐–ณ๐—‚๐—†๐–พ๐—“๐—ˆ๐—‡๐–พ:** {timezone} +โ•ฐโžข **๐–ฒ๐—Ž๐—‡๐—‹๐—‚๐—Œ๐–พ:** {sunrise} +โ•ฐโžข **๐–ฒ๐—Ž๐—‡๐—Œ๐–พ๐—:** {sunset} +โ•ฐโžข **๐–ถ๐—‚๐—‡๐–ฝ:** {wind} +โ•ฐโžข **๐–ณ๐–พ๐—†๐—‰๐–พ๐—‹๐–บ๐—๐—Ž๐—‹๐–พ:** {temperature}ยฐC +โ•ฐโžข **๐–ฅ๐–พ๐–พ๐—…๐—Œ ๐—…๐—‚๐—„๐–พ:** {feels_like}ยฐC +โ•ฐโžข **๐–ฌ๐—‚๐—‡๐—‚๐—†๐—Ž๐—†:** {temp_min}ยฐC +โ•ฐโžข **๐–ฌ๐–บ๐—‘๐—‚๐—†๐—Ž๐—†:** {temp_max}ยฐC +โ•ฐโžข **๐–ฏ๐—‹๐–พ๐—Œ๐—Œ๐—Ž๐—‹๐–พ:** {pressure} hPa +โ•ฐโžข **๐–ง๐—Ž๐—†๐—‚๐–ฝ๐—‚๐—๐—’:** {humidity}% +โ•ฐโžข **๐–ต๐—‚๐—Œ๐—‚๐–ป๐—‚๐—…๐—‚๐—๐—’:** {visibility} m +โ•ฐโžข **๐–ข๐—…๐—ˆ๐—Ž๐–ฝ๐—Œ:** {clouds}% +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +""" +] + +AIR_POLLUTION_TEMPLATES = [ + """ +๐ŸŒ† {city_name} + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐– ๐–ฐ๐–จ:** {aqi} +โ•ฐโžข **๐–ข๐–บ๐—‹๐–ป๐—ˆ๐—‡ ๐–ฌ๐—ˆ๐—‡๐—ˆ๐—‘๐—‚๐–ฝ๐–พ:** {co} +โ•ฐโžข **๐–ญ๐—ˆ๐—‚๐—๐—‹๐—ˆ๐—€๐–พ๐—‡ ๐–ฌ๐—ˆ๐—‡๐—ˆ๐—‘๐—‚๐–ฝ๐–พ:** {no} +โ•ฐโžข **๐–ญ๐—‚๐—๐—‹๐—ˆ๐—€๐–พ๐—‡ ๐–ฃ๐—‚๐—ˆ๐—‘๐—‚๐–ฝ๐–พ:** {no2} +โ•ฐโžข **๐–ฎ๐—“๐—ˆ๐—‡๐–พ:** {o3} +โ•ฐโžข **๐–ฒ๐—Ž๐—…๐—‰๐—๐—Ž๐—‹ ๐–ฃ๐—‚๐—ˆ๐—‘๐—‚๐–ฝ๐–พ:** {so2} +โ•ฐโžข **๐– ๐—†๐—†๐—ˆ๐—‡๐—‚๐–บ:** {nh3} +โ•ฐโžข **๐–ฅ๐—‚๐—‡๐–พ ๐–ฏ๐–บ๐—‹๐—๐—‚๐–ผ๐—…๐–พ๐—Œ (PM{sub2_5}):** {pm2_5} +โ•ฐโžข **๐–ข๐—ˆ๐–บ๐—‹๐—Œ๐–พ ๐–ฏ๐–บ๐—‹๐—๐—‚๐–ผ๐—…๐–พ๐—Œ (PM{sub10}):** {pm10} +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +""" +] + +GITHUB_USER_TEMPLATES = [ + """ +๐Ÿ€ {username} ({git_id}) + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ {id_type} โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ญ๐–บ๐—†๐–พ:** [{name}]({profile_url}) +โ•ฐโžข **๐–ก๐—…๐—ˆ๐—€:** {blog} +โ•ฐโžข **๐–ข๐—ˆ๐—†๐—‰๐–บ๐—‡๐—’:** {company} +โ•ฐโžข **๐–ค๐—†๐–บ๐—‚๐—…:** {email} +โ•ฐโžข **๐–ซ๐—ˆ๐–ผ๐–บ๐—๐—‚๐—ˆ๐—‡:** {location} +โ•ฐโžข **๐–ฑ๐–พ๐—‰๐—ˆ:** {public_repos} +โ•ฐโžข **๐–ฆ๐—‚๐—Œ๐—๐—Œ:** {public_gists} +โ•ฐโžข **๐–ฅ๐—ˆ๐—…๐—…๐—ˆ๐—๐–พ๐—‹๐—Œ:** {followers} +โ•ฐโžข **๐–ฅ๐—ˆ๐—…๐—…๐—ˆ๐—๐—‚๐—‡๐—€:** {following} +โ•ฐโžข **๐– ๐–ผ๐–ผ๐—ˆ๐—Ž๐—‡๐— ๐–ผ๐—‹๐–พ๐–บ๐—๐–พ๐–ฝ:** {created_at} +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข + +**๐Ÿ’ซ ๐–ก๐—‚๐—ˆ:** {bio} +""" +] + +STATISTICS_TEMPLATES = [ + """ +๐Ÿ€ {name} + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๐–ข๐—๐–บ๐—‡๐—‡๐–พ๐—…๐—Œ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ณ๐—ˆ๐—๐–บ๐—…:** `{channels}` +โ•ฐโžข **๐– ๐–ฝ๐—†๐—‚๐—‡:** `{ch_admin}` +โ•ฐโžข **๐–ฎ๐—๐—‡๐–พ๐—‹:** `{ch_owner}` + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๐–ฆ๐—‹๐—ˆ๐—Ž๐—‰๐—Œ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ณ๐—ˆ๐—๐–บ๐—…:** `{groups}` +โ•ฐโžข **๐– ๐–ฝ๐—†๐—‚๐—‡:** `{gc_admin}` +โ•ฐโžข **๐–ฎ๐—๐—‡๐–พ๐—‹:** `{gc_owner}` + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๐–ฎ๐—๐—๐–พ๐—‹๐—Œ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ฏ๐—‹๐—‚๐—๐–บ๐—๐–พ:** `{users}` +โ•ฐโžข **๐–ก๐—ˆ๐—๐—Œ:** `{bots}` +โ•ฐโžข **๐–ด๐—‡๐—‹๐–พ๐–บ๐–ฝ ๐–ฌ๐–พ๐—Œ๐—Œ๐–บ๐—€๐–พ๐—Œ:** `{unread_msg}` +โ•ฐโžข **๐–ด๐—‡๐—‹๐–พ๐–บ๐–ฝ ๐–ฌ๐–พ๐—‡๐—๐—‚๐—ˆ๐—‡๐—Œ:** `{unread_mention}` + +โŒ› **๐–ณ๐—‚๐—†๐–พ ๐–ณ๐–บ๐—„๐–พ๐—‡:** `{time_taken}` +""" +] + +GBAN_TEMPLATES = [ + """ +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ {gtype} โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +โ•ฐโžข **๐–ต๐—‚๐–ผ๐—๐—‚๐—†:** {name} +โ•ฐโžข **๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ:** {success} +โ•ฐโžข **๐–ฅ๐–บ๐—‚๐—…๐–พ๐–ฝ:** {failed} +โ•ฐโžข **๐–ฑ๐–พ๐–บ๐—Œ๐—ˆ๐—‡:** {reason} +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ€ข +""" +] + +USAGE_TEMPLATES = [ + """ +**๐Ÿ“ ๐–ฃ๐—‚๐—Œ๐—„ & ๐–ฃ๐—’๐—‡๐—ˆ ๐–ด๐—Œ๐–บ๐—€๐–พ:** + +**โžข ๐–ฃ๐—’๐—‡๐—ˆ ๐–ด๐—Œ๐–บ๐—€๐–พ ๐–ฟ๐—ˆ๐—‹** `{appName}` + โ—ˆ __{appHours}hrs {appMinutes}mins__ | __{appPercentage}%__ + +**โžข ๐–ฃ๐—’๐—‡๐—ˆ ๐—‹๐–พ๐—†๐–บ๐—‚๐—‡๐—‚๐—‡๐—€ ๐—๐—๐—‚๐—Œ ๐—†๐—ˆ๐—‡๐—๐—:** + โ—ˆ __{hours}hrs {minutes}mins__ | __{percentage}%__ + +**โžข ๐–ฃ๐—‚๐—Œ๐—„ ๐–ด๐—Œ๐–บ๐—€๐–พ:** + โ—ˆ __{diskUsed}GB__ / __{diskTotal}GB__ | __{diskPercent}%__ + +**โžข ๐–ฌ๐–พ๐—†๐—ˆ๐—‹๐—’ ๐–ด๐—Œ๐–บ๐—€๐–พ:** + โ—ˆ __{memoryUsed}GB__ / __{memoryTotal}GB__ | __{memoryPercent}%__ +""" +] + +USER_INFO_TEMPLATES = [ + """ +**๐Ÿ€ ๐–ด๐—Œ๐–พ๐—‹ ๐–จ๐—‡๐–ฟ๐—ˆ ๐—ˆ๐–ฟ {mention}:** + +**โžข ๐–ฅ๐—‚๐—‹๐—Œ๐— ๐–ญ๐–บ๐—†๐–พ:** `{firstName}` +**โžข ๐–ซ๐–บ๐—Œ๐— ๐–ญ๐–บ๐—†๐–พ:** `{lastName}` +**โžข ๐–ด๐—Œ๐–พ๐—‹๐–จ๐–ฃ:** `{userId}` + +**โžข ๐–ข๐—ˆ๐—†๐—†๐—ˆ๐—‡ ๐–ฆ๐—‹๐—ˆ๐—Ž๐—‰๐—Œ:** `{commonGroups}` +**โžข ๐–ฃ๐–ข-๐–จ๐–ฃ:** `{dcId}` +**โžข ๐–ฏ๐—‚๐–ผ๐—๐—Ž๐—‹๐–พ๐—Œ:** `{totalPictures}` +**โžข ๐–ฑ๐–พ๐—Œ๐—๐—‹๐—‚๐–ผ๐—๐–พ๐–ฝ:** `{isRestricted}` +**โžข ๐–ต๐–พ๐—‹๐—‚๐–ฟ๐—‚๐–พ๐–ฝ:** `{isVerified}` +**โžข ๐–ก๐—ˆ๐—:** `{isBot}` +**โžข ๐–ก๐—‚๐—ˆ:** `{bio}` + +** @HellBot_Networks** +""" +] + +CHAT_INFO_TEMPLATES = [ + """ +**๐Ÿ€ ๐–ข๐—๐–บ๐— ๐–จ๐—‡๐–ฟ๐—ˆ:** + +**โžข ๐–ข๐—๐–บ๐— ๐–ญ๐–บ๐—†๐–พ:** `{chatName}` +**โžข ๐–ข๐—๐–บ๐— ๐–จ๐–ฃ:** `{chatId}` +**โžข ๐–ข๐—๐–บ๐— ๐–ซ๐—‚๐—‡๐—„:** {chatLink} +**โžข ๐–ฎ๐—๐—‡๐–พ๐—‹:** {chatOwner} +**โžข ๐–ฃ๐–ข-๐–จ๐–ฃ:** `{dcId}` +**โžข ๐–ฌ๐–พ๐—†๐–ป๐–พ๐—‹๐—Œ:** `{membersCount}` +**โžข ๐– ๐–ฝ๐—†๐—‚๐—‡๐—Œ:** `{adminsCount}` +**โžข ๐–ก๐—ˆ๐—๐—Œ:** `{botsCount}` +**โžข ๐–ฃ๐–พ๐—Œ๐–ผ๐—‹๐—‚๐—‰๐—๐—‚๐—ˆ๐—‡:** `{description}` + +** @HellBot_Networks** +""" +] + + +async def alive_template(owner: str, uptime: str) -> str: + template = await db.get_env(ENV.alive_template) + if template: + message = template + else: + message = random.choice(ALIVE_TEMPLATES) + return message.format( + owner=owner, + pyrogram=__version__["pyrogram"], + hellbot=__version__["hellbot"], + python=__version__["python"], + uptime=uptime, + ) + + +async def ping_template(speed: float, uptime: str, owner: str) -> str: + template = await db.get_env(ENV.ping_template) + if template: + message = template + else: + message = random.choice(PING_TEMPLATES) + return message.format(speed=speed, uptime=uptime, owner=owner) + + +async def help_template( + owner: str, cmd_n_plgn: tuple[int, int], page: tuple[int, int] +) -> str: + template = await db.get_env(ENV.help_template) + if template: + message = template + else: + message = random.choice(HELP_MENU_TEMPLATES) + return message.format( + owner=owner, + commands=cmd_n_plgn[0], + plugins=cmd_n_plgn[1], + current=page[0], + last=page[1], + ) + + +async def command_template(file: str, info: str, commands: str) -> str: + template = await db.get_env(ENV.command_template) + if template: + message = template + else: + message = random.choice(COMMAND_MENU_TEMPLATES) + return message.format(file=file, info=info, commands=commands) + + +async def anime_template(**kwargs) -> str: + template = await db.get_env(ENV.anime_template) + if template: + message = template + else: + message = random.choice(ANIME_TEMPLATES) + return message.format(**kwargs) + + +async def manga_templates(**kwargs) -> str: + template = await db.get_env(ENV.manga_template) + if template: + message = template + else: + message = random.choice(MANGA_TEMPLATES) + return message.format(**kwargs) + + +async def character_templates(**kwargs) -> str: + template = await db.get_env(ENV.character_template) + if template: + message = template + else: + message = random.choice(CHARACTER_TEMPLATES) + return message.format(**kwargs) + + +async def airing_templates(**kwargs) -> str: + template = await db.get_env(ENV.airing_template) + if template: + message = template + else: + message = random.choice(AIRING_TEMPLATES) + return message.format(**kwargs) + + +async def anilist_user_templates( + name: str, anime: tuple, manga: tuple, siteurl: str +) -> str: + template = await db.get_env(ENV.anilist_user_template) + if template: + message = template + else: + message = random.choice(ANILIST_USER_TEMPLATES) + return message.format( + name=name, + anime_count=anime[0], + anime_score=anime[1], + minutes=anime[2], + episodes=anime[3], + manga_count=manga[0], + manga_score=manga[1], + chapters=manga[2], + volumes=manga[3], + siteurl=siteurl, + ) + + +async def climate_templates(**kwargs) -> str: + template = await db.get_env(ENV.climate_template) + if template: + message = template + else: + message = random.choice(CLIMATE_TEMPLATES) + return message.format(**kwargs) + + +async def airpollution_templates(**kwargs) -> str: + template = await db.get_env(ENV.airpollution_template) + if template: + message = template + else: + message = random.choice(AIR_POLLUTION_TEMPLATES) + return message.format(**kwargs) + + +async def statistics_templates(**kwargs) -> str: + template = await db.get_env(ENV.statistics_template) + if template: + message = template + else: + message = random.choice(STATISTICS_TEMPLATES) + return message.format(**kwargs) + + +async def github_user_templates(**kwargs) -> str: + template = await db.get_env(ENV.github_user_template) + if template: + message = template + else: + message = random.choice(GITHUB_USER_TEMPLATES) + return message.format(**kwargs) + + +async def gban_templates(**kwargs) -> str: + template = await db.get_env(ENV.gban_template) + if template: + message = template + else: + message = random.choice(GBAN_TEMPLATES) + return message.format(**kwargs) + + +async def usage_templates(**kwargs) -> str: + template = await db.get_env(ENV.usage_template) + if template: + message = template + else: + message = random.choice(USAGE_TEMPLATES) + return message.format(**kwargs) + + +async def user_info_templates(**kwargs) -> str: + template = await db.get_env(ENV.user_info_template) + if template: + message = template + else: + message = random.choice(USER_INFO_TEMPLATES) + return message.format(**kwargs) + + +async def chat_info_templates(**kwargs) -> str: + template = await db.get_env(ENV.chat_info_template) + if template: + message = template + else: + message = random.choice(CHAT_INFO_TEMPLATES) + return message.format(**kwargs) diff --git a/Hellbot/functions/tools.py b/Hellbot/functions/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..9c6ee11ff647797da7ab2e05ccd788a3a5f3f8ff --- /dev/null +++ b/Hellbot/functions/tools.py @@ -0,0 +1,139 @@ +import asyncio +import contextlib +import math +import os +import shlex +import shutil +import time + +from git import Repo +from git.exc import GitCommandError, InvalidGitRepositoryError, NoSuchPathError +from pyrogram.types import Message + +from Hellbot.core import Config, Symbols + +from .formatter import humanbytes, readable_time + + +async def progress( + current: int, total: int, message: Message, start: float, process: str +): + now = time.time() + diff = now - start + if round(diff % 10.00) == 0 or current == total: + percentage = current * 100 / total + speed = current / diff + elapsed_time = round(diff) * 1000 + complete_time = round((total - current) / speed) * 1000 + estimated_total_time = elapsed_time + complete_time + progress_str = "**[{0}{1}] : {2}%\n**".format( + "".join(["โ—" for i in range(math.floor(percentage / 10))]), + "".join(["โ—‹" for i in range(10 - math.floor(percentage / 10))]), + round(percentage, 2), + ) + msg = ( + progress_str + + "__{0}__ **๐—ˆ๐–ฟ** __{1}__\n**๐–ฒ๐—‰๐–พ๐–พ๐–ฝ:** __{2}/s__\n**๐–ค๐–ณ๐– :** __{3}__".format( + humanbytes(current), + humanbytes(total), + humanbytes(speed), + readable_time(estimated_total_time / 1000), + ) + ) + await message.edit_text(f"**{process} ...**\n\n{msg}") + + +async def get_files_from_directory(directory: str) -> list: + all_files = [] + for path, _, files in os.walk(directory): + for file in files: + all_files.append(os.path.join(path, file)) + return all_files + + +async def runcmd(cmd: str) -> tuple[str, str, int, int]: + args = shlex.split(cmd) + process = await asyncio.create_subprocess_exec( + *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + return ( + stdout.decode("utf-8", "replace").strip(), + stderr.decode("utf-8", "replace").strip(), + process.returncode, + process.pid, + ) + + +async def update_dotenv(key: str, value: str) -> None: + with open(".env", "r") as file: + data = file.readlines() + + for index, line in enumerate(data): + if line.startswith(f"{key}="): + data[index] = f"{key}={value}\n" + break + + with open(".env", "w") as file: + file.writelines(data) + + +async def restart( + update: bool = False, + clean_up: bool = False, + shutdown: bool = False, +): + try: + shutil.rmtree(Config.DWL_DIR) + shutil.rmtree(Config.TEMP_DIR) + except BaseException: + pass + + if clean_up: + os.system(f"mkdir {Config.DWL_DIR}") + os.system(f"mkdir {Config.TEMP_DIR}") + return + + if shutdown: + return os.system(f"kill -9 {os.getpid()}") + + cmd = ( + "git pull && pip3 install -U -r requirements.txt && bash start.sh" + if update + else "bash start.sh" + ) + + os.system(f"kill -9 {os.getpid()} && {cmd}") + + +async def gen_changelogs(repo: Repo, branch: str) -> str: + changelogs = "" + commits = list(repo.iter_commits(branch))[:5] + for index, commit in enumerate(commits): + changelogs += f"**{Symbols.triangle_right} {index + 1}.** `{commit.summary}`\n" + + return changelogs + + +async def initialize_git(git_repo: str): + force = False + try: + repo = Repo() + except NoSuchPathError as pathErr: + repo.__del__() + return False, pathErr, force + except GitCommandError as gitErr: + repo.__del__() + return False, gitErr, force + except InvalidGitRepositoryError: + repo = Repo.init() + origin = repo.create_remote("upstream", f"https://github.com/{git_repo}") + origin.fetch() + repo.create_head("master", origin.refs.master) + repo.heads.master.set_tracking_branch(origin.refs.master) + repo.heads.master.checkout(True) + force = True + with contextlib.suppress(BaseException): + repo.create_remote("upstream", f"https://github.com/{git_repo}") + + return True, repo, force diff --git a/Hellbot/functions/utility.py b/Hellbot/functions/utility.py new file mode 100644 index 0000000000000000000000000000000000000000..792ee116363e2c1aca578e00fa92383bb314fa3a --- /dev/null +++ b/Hellbot/functions/utility.py @@ -0,0 +1,241 @@ +import asyncio +import os +import time + +from pyrogram import Client +from pyrogram.enums import ChatType +from pyrogram.errors import FloodWait +from pyrogram.types import Message +from telegraph import Telegraph + +from Hellbot.core import ENV, LOGS, db + +from .formatter import readable_time + + +class TelegraphAPI: + def __init__(self) -> None: + self.shortname: str = "TheHellbot" + self.telegraph: Telegraph = None + + async def setup(self): + shortname = await db.get_env(ENV.telegraph_account) or self.shortname + + try: + self.telegraph = Telegraph(domain="telegra.ph") + self.telegraph.create_account(shortname) + except: + LOGS.warning("Failed to setup Telegraph API") + + +class Gcast: + def __init__(self) -> None: + self.file_name = "gcast_{0}.txt" + self.complete_msg = "**๐Ÿ€ ๐–ฆ๐–ผ๐–บ๐—Œ๐— ๐–ข๐—ˆ๐—†๐—‰๐—…๐–พ๐—๐–พ๐–ฝ!** \n\n**๐–ฌ๐–พ๐—Œ๐—Œ๐–บ๐—€๐–พ:** [click here]({0})\n**๐–ข๐—ˆ๐—Ž๐—‡๐—:** `{1} {2}`\n**๐–ฅ๐—ˆ๐—‹๐—๐–บ๐—‹๐–ฝ ๐—๐–บ๐—€:** `{3}`\n**๐–ณ๐—‚๐—†๐–พ ๐—๐–บ๐—„๐–พ๐—‡:** `{4}`" + + async def _send_msg(self, chat_id: int, msg: Message, tag: bool): + await msg.forward(chat_id) if tag else await msg.copy(chat_id) + + async def start(self, message: Message, client: Client, mode: str, tag: bool): + link = message.link + status = "Enabled" if tag else "Removed" + start = time.time() + + if mode == "all": + uCount, uFileName = await self.users(message, client, tag) + gCount, gFileName = await self.groups(message, client, tag) + count = uCount + gCount + with open(uFileName, "a", encoding="utf-8") as file1, open( + gFileName, "r", encoding="utf-8" + ) as file2: + file1.write(file2.read()) + file2.close() + file1.close() + os.remove(gFileName) + fileName = uFileName + elif mode == "groups": + count, fileName = await self.groups(message, client, tag) + elif mode == "users": + count, fileName = await self.users(message, client, tag) + else: + return None + + end = time.time() + outStr = self.complete_msg.format( + link, count, mode, status, readable_time(int(end - start)) + ) + + return fileName, outStr + + async def groups(self, message: Message, client: Client, tag: bool): + filename = self.file_name.format(round(time.time())) + count = 0 + + with open(filename, "w", encoding="utf-8") as f: + f.write("Group ID | Error\n\n") + async for dialog in client.get_dialogs(): + if dialog.chat.type == ChatType.SUPERGROUP: + try: + await self._send_msg(dialog.chat.id, message, tag) + count += 1 + except FloodWait as fw: + await asyncio.sleep(fw.value) + await self._send_msg(dialog.chat.id, message, tag) + count += 1 + except Exception as e: + f.write(f"{dialog.chat.id} | {e}\n") + + f.close() + + return count, filename + + async def users(self, message: Message, client: Client, tag: bool): + filename = self.file_name.format(round(time.time())) + count = 0 + + with open(filename, "w", encoding="utf-8") as f: + f.write("User ID | Error\n\n") + async for dialog in client.get_dialogs(): + if dialog.chat.type == ChatType.PRIVATE: + try: + await self._send_msg(dialog.chat.id, message, tag) + count += 1 + except FloodWait as fw: + await asyncio.sleep(fw.value) + await self._send_msg(dialog.chat.id, message, tag) + count += 1 + except Exception as e: + f.write(f"{dialog.chat.id} | {e}\n") + + f.close() + + return count, filename + + +class AntiFlood: + def __init__(self) -> None: + self.FloodCount = {} + self.settings = {} + self.client_chats = {} + + def updateSettings(self, client: int, chat: int, data: dict): + mode = data.get("mode", "mute") + mtime = data.get("time", 0) + limit = data.get("limit", 5) + + self.settings[client] = {chat: {"mode": mode, "time": mtime, "limit": limit}} + + def getSettings(self, client: int, chat: int) -> tuple[str, int, int]: + mode = "mute" + mtime = 0 + limit = 5 + + cli_settings: dict = self.settings.get(client, None) + if cli_settings: + chat_settings: dict = cli_settings.get(chat, None) + if chat_settings: + mode = chat_settings.get("mode", "mute") + mtime = chat_settings.get("time", 0) + limit = chat_settings.get("limit", 5) + + return mode, int(mtime), limit + + def updateFlood(self, client: int, chat: int, user: int, count: int): + self.FloodCount[client] = {chat: {"last_user": user, "count": count}} + + def getLastUser(self, client: int, chat: int) -> tuple[int, int]: + try: + cli_dict: dict = self.FloodCount[client] + except KeyError: + self.FloodCount[client] = {} + cli_dict: dict = self.FloodCount[client] + + try: + chat_dict: dict = cli_dict[chat] + except KeyError: + cli_dict[chat] = {} + chat_dict: dict = cli_dict[chat] + + last_user: int = chat_dict.get("last_user", 0) + count: int = chat_dict.get("count", 0) + + return last_user, count + + async def updateFromDB(self): + floods = await db.get_all_floods() + for flood in floods: + client = flood["client"] + chat = flood["chat"] + mode = flood.get("mode", "mute") + mtime = flood.get("time", 0) + limit = flood.get("limit", 5) + settings = {"mode": mode, "time": mtime, "limit": limit} + + self.updateSettings(client, chat, settings) + try: + self.client_chats[client].append(chat) + except KeyError: + self.client_chats[client] = [chat] + + def check_client_chat(self, client: int, chat: int) -> bool: + try: + chats = self.client_chats[client] + except KeyError: + return False + + if chat in chats: + return True + + return False + + +class Blacklists: + def __init__(self) -> None: + self.blacklists = {} + + async def updateBlacklists(self): + datas = await db.get_blacklist_clients() + for data in datas: + client = data["client"] + chats = data.get("chats", []) + for chat in chats: + blacklists = data["blacklist"] + self.blacklists[client] = {chat: blacklists} + + async def addBlacklist(self, client: int, chat: int, text: str): + try: + self.blacklists[client][chat].append(text) + except KeyError: + self.blacklists[client] = {chat: [text]} + + await db.add_blacklist(client, chat, text) + + async def rmBlacklist(self, client: int, chat: int, text: str): + try: + self.blacklists[client][chat].remove(text) + except KeyError: + return + + await db.rm_blacklist(client, chat, text) + + def getBlacklists(self, client: int, chat: int) -> list: + try: + return self.blacklists[client][chat] + except KeyError: + return [] + + def check_client_chat(self, client: int, chat: int) -> bool: + try: + chats = self.blacklists[client] + except KeyError: + return False + + if chat in chats: + return True + + return False + + +Flood = AntiFlood() +BList = Blacklists() +TGraph = TelegraphAPI() diff --git a/Hellbot/plugins/__init__.py b/Hellbot/plugins/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Hellbot/plugins/bot/__init__.py b/Hellbot/plugins/bot/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d67965dd753207baec52c25d32835f8442c25c56 --- /dev/null +++ b/Hellbot/plugins/bot/__init__.py @@ -0,0 +1,29 @@ +from Hellbot.core.clients import hellbot +from Hellbot.core.config import Config, Symbols +from Hellbot.core.database import db +from Hellbot.plugins.help import BotHelp + + +START_MSG = """ +๐Ÿ‘‹ **๐–ฆ๐—‹๐–พ๐–พ๐—๐—‚๐—‡๐—€๐—Œ, {0} - ๐—๐–บ๐—‹๐—‹๐—‚๐—ˆ๐—‹๐—Œ ๐—ˆ๐–ฟ ๐–ง๐–พ๐—…๐—…๐–ป๐—ˆ๐—!** ๐Ÿ‘น ๐–จ ๐–บ๐—† ๐—’๐—ˆ๐—Ž๐—‹ ๐—๐—‹๐—Ž๐—Œ๐—๐—’ ๐–ผ๐—ˆ๐—†๐—‰๐–บ๐—‡๐—‚๐—ˆ๐—‡, ๐—๐—๐–พ **๐–ง๐–พ๐—…๐—…๐–ป๐—ˆ๐— ๐– ๐—Œ๐—Œ๐—‚๐—Œ๐—๐–บ๐—‡๐—!** ๐Ÿš€ + +๐–ง๐–พ๐—‹๐–พ ๐—๐—ˆ ๐—Œ๐–พ๐—‹๐—๐–พ, ๐—€๐—Ž๐—‚๐–ฝ๐–พ, ๐–บ๐—‡๐–ฝ ๐Ÿ’ช ๐—Ž๐—‡๐—…๐–พ๐–บ๐—Œ๐— ๐—๐—๐–พ ๐—‰๐—ˆ๐—๐–พ๐—‹ ๐—ˆ๐–ฟ ๐–ง๐–พ๐—…๐—…๐–ก๐—ˆ๐— ๐–บ๐— ๐—’๐—ˆ๐—Ž๐—‹ ๐–ผ๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ! ๐ŸŽ‰ +๐–ถ๐—๐–พ๐—๐—๐–พ๐—‹ ๐—‚๐—'๐—Œ ๐–ผ๐—‹๐–พ๐–บ๐—๐—‚๐—‡๐—€, ๐–ฝ๐–พ๐—…๐–พ๐—๐—‚๐—‡๐—€, ๐—ˆ๐—‹ ๐—Ž๐—‰๐–ฝ๐–บ๐—๐—‚๐—‡๐—€ ๐—’๐—ˆ๐—Ž๐—‹ ๐—Ž๐—Œ๐–พ๐—‹๐–ป๐—ˆ๐—, ๐–จ'๐—๐–พ ๐—€๐—ˆ๐— ๐—’๐—ˆ๐—Ž๐—‹ ๐–ป๐–บ๐–ผ๐—„. +๐–ข๐—ˆ๐—‡๐—Œ๐—‚๐–ฝ๐–พ๐—‹ ๐—†๐–พ ๐—’๐—ˆ๐—Ž๐—‹ ๐—‰๐–พ๐—‹๐—Œ๐—ˆ๐—‡๐–บ๐—… ๐—Œ๐—‚๐–ฝ๐–พ๐—„๐—‚๐–ผ๐—„ ๐Ÿคญ ๐—‚๐—‡ ๐—๐—๐–พ ๐—‹๐–พ๐–บ๐—…๐—† ๐—ˆ๐–ฟ ๐—Ž๐—…๐—๐—‚๐—†๐–บ๐—๐–พ ๐—Ž๐—Œ๐–พ๐—‹๐–ป๐—ˆ๐— ๐—†๐–บ๐—Œ๐—๐–พ๐—‹๐—’. + +๐Ÿ€ ๐–ซ๐–พ๐—'๐—Œ ๐–พ๐—†๐–ป๐–บ๐—‹๐—„ ๐—ˆ๐—‡ ๐—๐—๐—‚๐—Œ ๐–พ๐—‰๐—‚๐–ผ ๐—ƒ๐—ˆ๐—Ž๐—‹๐—‡๐–พ๐—’ ๐—๐—ˆ๐—€๐–พ๐—๐—๐–พ๐—‹! +๐–จ๐–ฟ ๐—’๐—ˆ๐—Ž ๐–พ๐—๐–พ๐—‹ ๐—‡๐–พ๐–พ๐–ฝ ๐–บ๐—Œ๐—Œ๐—‚๐—Œ๐—๐–บ๐—‡๐–ผ๐–พ ๐—ˆ๐—‹ ๐–ผ๐—‹๐–บ๐—๐–พ โœจ ๐—๐—๐–พ ๐—๐—๐—‹๐—‚๐—…๐—… ๐—ˆ๐–ฟ ๐—Ž๐—‡๐—…๐–พ๐–บ๐—Œ๐—๐—‚๐—‡๐—€ ๐–ง๐–พ๐—…๐—…๐–ป๐—ˆ๐—'๐—Œ ๐—†๐—‚๐—€๐—๐—, ๐—ƒ๐—Ž๐—Œ๐— ๐—Œ๐—Ž๐—†๐—†๐—ˆ๐—‡ ๐—†๐–พ. +๐–ถ๐–พ'๐—‹๐–พ ๐–บ๐–ป๐—ˆ๐—Ž๐— ๐—๐—ˆ ๐–ผ๐—ˆ๐—‡๐—Š๐—Ž๐–พ๐—‹ ๐—‡๐–พ๐— ๐—๐–พ๐—‚๐—€๐—๐—๐—Œ ๐Ÿš€ ๐—‚๐—‡ ๐—๐—๐–พ ๐—Ž๐—Œ๐–พ๐—‹๐–ป๐—ˆ๐— ๐—Ž๐—‡๐—‚๐—๐–พ๐—‹๐—Œ๐–พ! + +๐Ÿ’ซ ๐–ฌ๐–บ๐—’ ๐—’๐—ˆ๐—Ž๐—‹ ๐–ผ๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ๐—Œ ๐–ป๐–พ ๐—Œ๐—๐—‚๐–ฟ๐— ๐–บ๐—‡๐–ฝ ๐—’๐—ˆ๐—Ž๐—‹ ๐—Œ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡๐—Œ ๐—…๐–พ๐—€๐–พ๐—‡๐–ฝ๐–บ๐—‹๐—’. +**๐–ถ๐–พ๐—…๐–ผ๐—ˆ๐—†๐–พ ๐—๐—ˆ ๐–ง๐–พ๐—…๐—…๐–ป๐—ˆ๐— ๐– ๐—Œ๐—Œ๐—‚๐—Œ๐—๐–บ๐—‡๐— โ€“ ๐—๐—๐–พ๐—‹๐–พ ๐–ง๐–พ๐—…๐—…๐–ป๐—ˆ๐—'๐—Œ ๐—…๐–พ๐—€๐–บ๐–ผ๐—’ ๐—…๐—‚๐—๐–พ๐—Œ ๐—ˆ๐—‡ ๐Ÿค–!** +""" + +HELP_MSG = """ +**โš™๏ธ ๐–ง๐–พ๐—…๐—‰:** + +__ยป All commands are categorized and you can use these buttons below to navigate each category and get respective commands.__ +__ยป Feel free to contact us if you need any help regarding the bot.__ + +**โค๏ธ @HellBot_Networks ๐Ÿ‡ฎ๐Ÿ‡ณ** +""" diff --git a/Hellbot/plugins/bot/bot.py b/Hellbot/plugins/bot/bot.py new file mode 100644 index 0000000000000000000000000000000000000000..43bb5fef68d923ef4ba479247d16e15b36fd6998 --- /dev/null +++ b/Hellbot/plugins/bot/bot.py @@ -0,0 +1,60 @@ +import heroku3 +from pyrogram import filters +from pyrogram.types import InlineKeyboardMarkup, Message + +from Hellbot import HEROKU_APP +from Hellbot.core import LOGS +from Hellbot.functions.tools import restart + +from ..btnsG import gen_bot_help_buttons, start_button +from . import HELP_MSG, START_MSG, BotHelp, Config, hellbot + + +@hellbot.bot.on_message(filters.command("start") & Config.AUTH_USERS) +async def start_pm(_, message: Message): + btns = start_button() + + await message.reply_text( + START_MSG.format(message.from_user.mention), + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup(btns), + ) + + +@hellbot.bot.on_message(filters.command("help") & Config.AUTH_USERS) +async def help_pm(_, message: Message): + btns = await gen_bot_help_buttons() + + await message.reply_text( + HELP_MSG, + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup(btns), + ) + + +@hellbot.bot.on_message(filters.command("restart") & Config.AUTH_USERS) +async def restart_clients(_, message: Message): + await message.reply_text("Restarted Bot Successfully โœ…") + try: + if HEROKU_APP: + try: + heroku = heroku3.from_key(Config.HEROKU_APIKEY) + app = heroku.apps()[Config.HEROKU_APPNAME] + app.restart() + except: + await restart() + else: + await restart() + except Exception as e: + LOGS.error(e) + + +BotHelp("Others").add( + "start", "To start the bot and get the main menu." +).add( + "help", "To get the help menu with all the command for this assistant bot." +).add( + "restart", "To restart the bot." +).info( + "Some basic commands of the bot." +).done() diff --git a/Hellbot/plugins/bot/callbacks.py b/Hellbot/plugins/bot/callbacks.py new file mode 100644 index 0000000000000000000000000000000000000000..6479fdb4847b64e73402f3ba7ea90cb9132d5f54 --- /dev/null +++ b/Hellbot/plugins/bot/callbacks.py @@ -0,0 +1,279 @@ +from pyrogram import filters +from pyrogram.enums import ParseMode +from pyrogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup + +from Hellbot.functions.templates import command_template, help_template + +from ..btnsG import gen_bot_help_buttons, gen_inline_help_buttons, start_button +from . import HELP_MSG, START_MSG, Config, Symbols, hellbot + + +async def check_auth_click(cb: CallbackQuery) -> bool: + if cb.from_user.id not in Config.AUTH_USERS: + await cb.answer( + "You are not authorized to use this bot. \n\n @Its_HellBot", + show_alert=True, + ) + return False + return True + + +@hellbot.bot.on_callback_query(filters.regex(r"auth_close")) +async def auth_close_cb(_, cb: CallbackQuery): + if await check_auth_click(cb): + await cb.message.delete() + + +@hellbot.bot.on_callback_query(filters.regex(r"close")) +async def close_cb(_, cb: CallbackQuery): + await cb.message.delete() + + +@hellbot.bot.on_callback_query(filters.regex(r"bot_help_menu")) +async def bot_help_menu_cb(_, cb: CallbackQuery): + if not await check_auth_click(cb): + return + + plugin = str(cb.data.split(":")[1]) + + try: + buttons = [ + InlineKeyboardButton(f"{Symbols.bullet} {i}", f"bot_help_cmd:{plugin}:{i}") + for i in sorted(Config.BOT_HELP[plugin]["commands"]) + ] + except KeyError: + await cb.answer("No description provided for this plugin!", show_alert=True) + return + + buttons = [buttons[i : i + 2] for i in range(0, len(buttons), 2)] + buttons.append([InlineKeyboardButton(Symbols.back, "help_data:bothelp")]) + + caption = ( + f"**๐–ฏ๐—…๐—Ž๐—€๐—‚๐—‡ ๐–ฅ๐—‚๐—…๐–พ:** `{plugin}`\n" + f"**๐–ฏ๐—…๐—Ž๐—€๐—‚๐—‡ ๐–จ๐—‡๐–ฟ๐—ˆ:** __{Config.BOT_HELP[plugin]['info']} ๐Ÿ€__\n\n" + f"**๐Ÿ“ƒ ๐–ซ๐—ˆ๐–บ๐–ฝ๐–พ๐–ฝ ๐–ข๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ๐—Œ:** `{len(sorted(Config.BOT_HELP[plugin]['commands']))}`" + ) + + try: + await cb.edit_message_text( + caption, + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup(buttons), + ) + except Exception: + # handles MessageNotModified error + pass + + +@hellbot.bot.on_callback_query(filters.regex(r"bot_help_cmd")) +async def bot_help_cmd_cb(_, cb: CallbackQuery): + if not await check_auth_click(cb): + return + + result = "" + plugin = str(cb.data.split(":")[1]) + command = str(cb.data.split(":")[2]) + cmd_dict = Config.BOT_HELP[plugin]["commands"][command] + + result += f"**{Symbols.radio_select} ๐–ข๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ:** `/{cmd_dict['command']}`" + result += ( + f"\n\n**{Symbols.arrow_right} ๐–ฃ๐–พ๐—Œ๐–ผ๐—‹๐—‚๐—‰๐—๐—‚๐—ˆ๐—‡:** __{cmd_dict['description']}__" + ) + result += f"\n\n**<\\> @Its_HellBot ๐Ÿ€**" + + buttons = [ + [ + InlineKeyboardButton(Symbols.back, f"bot_help_menu:{plugin}"), + InlineKeyboardButton(Symbols.close, "help_data:botclose"), + ] + ] + + try: + await cb.edit_message_text( + result, + ParseMode.MARKDOWN, + True, + InlineKeyboardMarkup(buttons), + ) + except Exception: + # handles MessageNotModified error + pass + + +@hellbot.bot.on_callback_query(filters.regex(r"help_page")) +async def help_page_cb(_, cb: CallbackQuery): + if not await check_auth_click(cb): + return + + page = int(cb.data.split(":")[1]) + buttons, max_page = await gen_inline_help_buttons(page, sorted(Config.CMD_MENU)) + + caption = await help_template( + cb.from_user.mention, + (len(Config.CMD_INFO), len(Config.CMD_MENU)), + (page + 1, max_page), + ) + + try: + await cb.edit_message_text( + caption, + reply_markup=InlineKeyboardMarkup(buttons), + ) + except Exception: + # handles MessageNotModified error + pass + + +@hellbot.bot.on_callback_query(filters.regex(r"help_menu")) +async def help_menu_cb(_, cb: CallbackQuery): + if not await check_auth_click(cb): + return + + page = int(cb.data.split(":")[1]) + plugin = str(cb.data.split(":")[2]) + + try: + buttons = [ + InlineKeyboardButton( + f"{Symbols.bullet} {i}", f"help_cmd:{page}:{plugin}:{i}" + ) + for i in sorted(Config.HELP_DICT[plugin]["commands"]) + ] + except KeyError: + await cb.answer("No description provided for this plugin!", show_alert=True) + return + + buttons = [buttons[i : i + 2] for i in range(0, len(buttons), 2)] + buttons.append([InlineKeyboardButton(Symbols.back, f"help_page:{page}")]) + + caption = await command_template( + plugin, + Config.HELP_DICT[plugin]["info"], + len(sorted(Config.HELP_DICT[plugin]["commands"])), + ) + + try: + await cb.edit_message_text( + caption, + reply_markup=InlineKeyboardMarkup(buttons), + ) + except Exception: + # handles MessageNotModified error + pass + + +@hellbot.bot.on_callback_query(filters.regex(r"help_cmd")) +async def help_cmd_cb(_, cb: CallbackQuery): + if not await check_auth_click(cb): + return + + page = int(cb.data.split(":")[1]) + plugin = str(cb.data.split(":")[2]) + command = str(cb.data.split(":")[3]) + result = "" + cmd_dict = Config.HELP_DICT[plugin]["commands"][command] + + if cmd_dict["parameters"] is None: + result += f"**{Symbols.radio_select} ๐–ข๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ:** `{Config.HANDLERS[0]}{cmd_dict['command']}`" + else: + result += f"**{Symbols.radio_select} ๐–ข๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ:** `{Config.HANDLERS[0]}{cmd_dict['command']} {cmd_dict['parameters']}`" + + if cmd_dict["description"]: + result += ( + f"\n\n**{Symbols.arrow_right} ๐–ฃ๐–พ๐—Œ๐–ผ๐—‹๐—‚๐—‰๐—๐—‚๐—ˆ๐—‡:** __{cmd_dict['description']}__" + ) + + if cmd_dict["example"]: + result += f"\n\n**{Symbols.arrow_right} ๐–ค๐—‘๐–บ๐—†๐—‰๐—…๐–พ:** `{Config.HANDLERS[0]}{cmd_dict['example']}`" + + if cmd_dict["note"]: + result += f"\n\n**{Symbols.arrow_right} ๐–ญ๐—ˆ๐—๐–พ:** __{cmd_dict['note']}__" + + result += f"\n\n**<\\> @Its_HellBot ๐Ÿ€**" + + buttons = [ + [ + InlineKeyboardButton(Symbols.back, f"help_menu:{page}:{plugin}"), + InlineKeyboardButton(Symbols.close, "help_data:c"), + ] + ] + + try: + await cb.edit_message_text( + result, + ParseMode.MARKDOWN, + reply_markup=InlineKeyboardMarkup(buttons), + ) + except Exception: + # handles MessageNotModified error + pass + + +@hellbot.bot.on_callback_query(filters.regex(r"help_data")) +async def help_close_cb(_, cb: CallbackQuery): + if not await check_auth_click(cb): + return + + action = str(cb.data.split(":")[1]) + if action == "c": + await cb.edit_message_text( + "**๐–ง๐–พ๐—…๐—‰ ๐–ฌ๐–พ๐—‡๐—Ž ๐–ข๐—…๐—ˆ๐—Œ๐–พ๐–ฝ!**", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton("Reopen", "help_data:reopen")]] + ), + ) + elif action == "reopen": + buttons, pages = await gen_inline_help_buttons(0, sorted(Config.CMD_MENU)) + caption = await help_template( + cb.from_user.mention, + (len(Config.CMD_INFO), len(Config.CMD_MENU)), + (1, pages), + ) + await cb.edit_message_text( + caption, + reply_markup=InlineKeyboardMarkup(buttons), + ) + elif action == "botclose": + await cb.message.delete() + elif action == "bothelp": + buttons = await gen_bot_help_buttons() + await cb.edit_message_text( + HELP_MSG, + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup(buttons), + ) + elif action == "source": + buttons = [ + [ + InlineKeyboardButton("๐Ÿš€ Deploy", url="https://github.com/The-HellBot/HellBot"), + InlineKeyboardButton("Plugins ๐Ÿ“‚", url="https://github.com/The-HellBot/Plugins"), + ], + [ + InlineKeyboardButton("ะฝั”โ„“โ„“ะฒฯƒั‚ ฮทั”ั‚ฯ‰ฯƒัะบ ๐Ÿ‡ฎ๐Ÿ‡ณ", url="https://t.me/HellBot_Networks"), + ], + [ + InlineKeyboardButton("๐ŸŽ™๏ธ Support", url="https://t.me/HellBot_Chats"), + InlineKeyboardButton("Updates ๐Ÿ“ฃ", url="https://t.me/Its_HellBot"), + ], + [ + InlineKeyboardButton("๐Ÿ”™", "help_data:start"), + InlineKeyboardButton(Symbols.close, "help_data:botclose"), + ], + ] + await cb.edit_message_text( + "__ยป The source code is available on GitHub. You can find the link below.__\n" + "__ยป Every project available under The-HellBot are open-source and free to use and modify to your needs.__\n" + "__ยป Anyone pretending to be the developer of this bot and selling the code, is a scammer.__\n\n" + "__ยป Please consider giving a star to the repository if you liked the project.__\n" + "__ยป Feel free to contact us if you need any help regarding the source code.__\n\n" + "**โค๏ธ @HellBot_Networks ๐Ÿ‡ฎ๐Ÿ‡ณ**", + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup(buttons), + ) + elif action == "start": + buttons = start_button() + await cb.edit_message_text( + START_MSG.format(cb.from_user.mention), + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup(buttons), + ) diff --git a/Hellbot/plugins/bot/forcesub.py b/Hellbot/plugins/bot/forcesub.py new file mode 100644 index 0000000000000000000000000000000000000000..ea4a60fd25ab0119a4277abee89ca3a0a5419d28 --- /dev/null +++ b/Hellbot/plugins/bot/forcesub.py @@ -0,0 +1,226 @@ +from pyrogram import Client, filters +from pyrogram.errors import ChatAdminRequired, UserNotParticipant +from pyrogram.types import ( + CallbackQuery, + ChatPermissions, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, +) + +from Hellbot.core import LOGS +from Hellbot.functions.admins import is_user_admin + +from ..btnsG import gen_inline_keyboard +from . import BotHelp, Config, Symbols, db, hellbot + + +@hellbot.bot.on_message(filters.command("forcesub") & Config.AUTH_USERS & filters.group) +async def force_sub(client: Client, message: Message): + if len(message.command) < 2: + return await message.reply_text("Give a channel username with command!") + + try: + is_admin = await is_user_admin(message.chat, client.me.id) + if not is_admin: + return await message.reply_text(f"To use forcesub i must be an admin in {must_join}!") + except UserNotParticipant: + return await message.reply_text(f"To use forcesub i must be an admin in {must_join}!") + + must_join = message.command[1] + try: + chat = await client.get_chat(must_join) + except Exception as e: + return await message.reply_text(f"**Error:**\n`{e}`") + + if not await is_user_admin(chat, client.me.id): + return await message.reply_text("Make me admin in that channel first!") + + await db.add_forcesub(message.chat.id, chat.id) + await message.reply_text( + f"**๐Ÿ“Œ ๐–ข๐—๐–บ๐— ๐–ฅ๐—ˆ๐—‹๐–ผ๐–พ๐—Œ๐—Ž๐–ป ๐–ค๐—‡๐–บ๐–ป๐—…๐–พ๐–ฝ!** \n\n" + f"__Users must join__ {chat.title} (`{chat.id}`) __to chat here!__" + ) + + if message.chat.id not in Config.FORCESUBS: + Config.FORCESUBS.add(message.chat.id) + + +@hellbot.bot.on_message(filters.command("unforcesub") & Config.AUTH_USERS) +async def unforce_sub(client: Client, message: Message): + if len(message.command) < 2: + return await message.reply_text( + "Give a channel username with command or give 'all' to remove all forcesubs from this chat!" + ) + + if not await is_user_admin(message.chat, client.me.id): + return await message.reply_text("To use forcesub i must be an admin!") + + if "all" == message.command[1].lower(): + await db.rm_all_forcesub(message.chat.id) + Config.FORCESUBS.remove(message.chat.id) + return await message.reply_text(f"**๐Ÿ“Œ Forcesub disabled!**") + + try: + if await db.is_forcesub(message.chat.id, int(message.command[1])): + remaining = await db.rm_forcesub(message.chat.id, int(message.command[1])) + if remaining: + return await message.reply_text( + f"**๐Ÿ“Œ Removed Forcesub `{message.command[1]}`!**\n\n**Remaining Forcesub(s) in this chat:** `{remaining}`" + ) + else: + Config.FORCESUBS.remove(message.chat.id) + return await message.reply_text( + f"**๐Ÿ“Œ Removed Forcesub `{message.command[1]}`!**" + ) + else: + return await message.reply_text(f"**๐Ÿ“Œ This chat is not forcesub enabled!**") + except Exception as e: + return await message.reply_text(f"**Error:**\n`{e}`") + + +@hellbot.bot.on_message(filters.command("listforcesub") & Config.AUTH_USERS) +async def list_force_subs(client: Client, message: Message): + if not await is_user_admin(message.chat, client.me.id): + return await message.reply_text("To use forcesub i must be an admin!") + + all_forcesubs = Config.FORCESUBS + + text = "" + if len(all_forcesubs) > 0: + for forcesub in all_forcesubs: + try: + chat = await client.get_chat(forcesub["chat"]) + text += f"**๐Ÿ“Œ {chat.title}** (`{chat.id}`)\n" + except: + text += f"**๐Ÿ“Œ {forcesub['chat']}** - `Invalid Chat!`\n" + else: + text = "**๐Ÿ“Œ No Forcesub Enabled in Bot!**" + + await message.reply_text(text) + + +@hellbot.bot.on_message(filters.command("getforcesub") & Config.AUTH_USERS) +async def getforcesub(client: Client, message: Message): + if len(message.command) < 2: + chat = message.chat + else: + try: + chat = await client.get_chat(message.command[1]) + except: + return await message.reply_text(f"**Invalid Channel Username/ID!**") + + mustjoins = await db.get_forcesub(chat.id) + if mustjoins: + text = f"**This chat has {len(mustjoins['must_join'])} forcesub(s):**\n" + for must_join in mustjoins["must_join"]: + try: + chat = await client.get_chat(must_join) + text += f"**๐Ÿ“Œ {chat.title}** (`{chat.id}`)\n" + except: + text += f"**๐Ÿ“Œ {must_join}** - `Invalid Chat!`\n" + else: + text = "**๐Ÿ“Œ No Forcesub Enabled in This Chat!**" + + await message.reply_text(text) + + +@hellbot.bot.on_message( + filters.group + & filters.incoming + & filters.new_chat_members + & ~filters.bot + & ~filters.service + & ~Config.AUTH_USERS + & ~filters.me +) +async def handle_force_sub(client: Client, message: Message): + if message.chat.id not in Config.FORCESUBS: + return + + if not is_user_admin(message.chat, client.me.id): + return + + btns_list = [] + mustjoins = await db.get_forcesub(message.chat.id) + + for i, must_join in enumerate(mustjoins["must_join"]): + try: + await client.get_chat_member(must_join, message.from_user.id) + except UserNotParticipant: + invite_link = await client.export_chat_invite_link(must_join) + btns_list.append((f"Join {i}", invite_link, "url")) + continue + except ChatAdminRequired: + continue + except Exception as e: + LOGS.warning(e) + continue + + if len(btns_list) == 0: + return + + join_btns = gen_inline_keyboard(btns_list, 2) + join_btns.append( + [ + InlineKeyboardButton("Unmute ๐Ÿ—ฃ๏ธ", f"forcesub:unmute:{message.from_user.id}:{message.chat.id}") + ] + ) + await message.reply_text( + f"**๐Ÿ‘‹ Welcome to {message.chat.title}!**\n\n" + f"To be able to chat here, you must follow the instructions below:\n" + f" {Symbols.anchor} __Click the buttons below to join our important channels.__" + f" {Symbols.anchor} __After joining all channels, press the unmute button below.__" + f" {Symbols.anchor} __Then you can chat here.__", + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup(join_btns), + ) + + +@hellbot.bot.on_callback_query(filters.regex(r"forcesub")) +async def forcesub_cb(client: Client, cb: CallbackQuery): + data = cb.data.split(":") + if data[1] == "unmute": + try: + if not int(data[3]) == cb.message.chat.id: + return await cb.answer( + "**This is not for this chat!**", show_alert=True + ) + + must_join = await db.get_forcesub(cb.message.chat.id) + for chat in must_join["must_join"]: + try: + await client.get_chat_member(int(chat), cb.from_user.id) + except UserNotParticipant: + return await cb.answer( + "**You must join all channels first!**", show_alert=True + ) + except ChatAdminRequired: + return await cb.answer( + "I'm not admin in some of the channels! Ask owner to make me admin.", + show_alert=True, + ) + except Exception as e: + return await cb.answer(f"**Error:**\n`{e}`") + + permissions = ChatPermissions(can_send_messages=True) + await cb.message.chat.restrict_member(int(data[2]), permissions) + except Exception as e: + return await cb.answer(f"**Error:**\n`{e}`") + + await cb.answer("**๐Ÿ“Œ Unmuted!**", show_alert=True) + return await cb.message.delete() + + +BotHelp("ForceSub").add( + "forcesub", + "This command is used to force users to join some channels to chat in group.", +).add( + "unforcesub", "This command is used to remove channels from forcesub in group." +).add( + "listforcesub", "This command is used to list all forcesub in bot." +).add( + "getforcesub", "This command is used to get forcesub in group." +).info( + "ForceSub ๐Ÿš€" +).done() diff --git a/Hellbot/plugins/bot/inline.py b/Hellbot/plugins/bot/inline.py new file mode 100644 index 0000000000000000000000000000000000000000..1871f235c396627e675283101d5ff658f4ab5c0b --- /dev/null +++ b/Hellbot/plugins/bot/inline.py @@ -0,0 +1,39 @@ +from pyrogram import filters +from pyrogram.types import ( + InlineKeyboardMarkup, + InlineQuery, + InlineQueryResultArticle, + InputTextMessageContent, +) + +from Hellbot.functions.templates import help_template + +from ..btnsG import gen_inline_help_buttons +from . import Config, hellbot + + +@hellbot.bot.on_inline_query(filters.regex(r"help_menu")) +async def help_inline(_, query: InlineQuery): + if not query.from_user.id in Config.AUTH_USERS: + return + no_of_plugins = len(Config.CMD_MENU) + no_of_commands = len(Config.CMD_INFO) + buttons, pages = await gen_inline_help_buttons(0, sorted(Config.CMD_MENU)) + caption = await help_template( + query.from_user.mention, (no_of_commands, no_of_plugins), (1, pages) + ) + await query.answer( + results=[ + ( + InlineQueryResultArticle( + "HellBot Help Menu ๐Ÿ€", + InputTextMessageContent( + caption, + disable_web_page_preview=True, + ), + description="Inline Query for Help Menu of HellBot", + reply_markup=InlineKeyboardMarkup(buttons), + ) + ) + ], + ) diff --git a/Hellbot/plugins/bot/sessions.py b/Hellbot/plugins/bot/sessions.py new file mode 100644 index 0000000000000000000000000000000000000000..3d93b2d7ebc62a459a9acf0c5a7be6f330ba5dbe --- /dev/null +++ b/Hellbot/plugins/bot/sessions.py @@ -0,0 +1,183 @@ +from pyrogram import Client, filters +from pyrogram.errors import SessionPasswordNeeded +from pyrogram.types import ( + CallbackQuery, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + ReplyKeyboardRemove, +) + +from ..btnsG import gen_inline_keyboard, start_button +from ..btnsK import session_keyboard +from . import START_MSG, BotHelp, Config, Symbols, db, hellbot + + +@hellbot.bot.on_message( + filters.command("session") & Config.AUTH_USERS & filters.private +) +async def session_menu(_, message: Message): + await message.reply_text( + "**๐Ÿ€ ๐–ฏ๐—…๐–พ๐–บ๐—Œ๐–พ ๐–ผ๐—๐—ˆ๐—ˆ๐—Œ๐–พ ๐–บ๐—‡ ๐—ˆ๐—‰๐—๐—‚๐—ˆ๐—‡ ๐–ฟ๐—‹๐—ˆ๐—† ๐–ป๐–พ๐—…๐—ˆ๐—:**", + reply_markup=session_keyboard(), + ) + + +@hellbot.bot.on_message(filters.regex(r"New ๐Ÿ’ซ") & Config.AUTH_USERS & filters.private) +async def new_session(_, message: Message): + await message.reply_text( + "**๐–ฎ๐—„๐–บ๐—’!** ๐–ซ๐–พ๐—'๐—Œ ๐—Œ๐–พ๐—๐—Ž๐—‰ ๐–บ ๐—‡๐–พ๐— ๐—Œ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡", + reply_markup=ReplyKeyboardRemove(), + ) + + phone_number = await hellbot.bot.ask( + message.chat.id, + "**1.** ๐–ค๐—‡๐—๐–พ๐—‹ ๐—’๐—ˆ๐—Ž๐—‹ ๐—๐–พ๐—…๐–พ๐—€๐—‹๐–บ๐—† ๐–บ๐–ผ๐–ผ๐—ˆ๐—Ž๐—‡๐— ๐—‰๐—๐—ˆ๐—‡๐–พ ๐—‡๐—Ž๐—†๐–ป๐–พ๐—‹ ๐—๐—ˆ ๐–บ๐–ฝ๐–ฝ ๐—๐—๐–พ ๐—Œ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡: \n\n__๐–ฒ๐–พ๐—‡๐–ฝ /cancel ๐—๐—ˆ ๐–ผ๐–บ๐—‡๐–ผ๐–พ๐—… ๐—๐—๐–พ ๐—ˆ๐—‰๐–พ๐—‹๐–บ๐—๐—‚๐—ˆ๐—‡.__", + filters=filters.text, + timeout=120, + ) + + if phone_number.text == "/cancel": + return await message.reply_text("**๐–ข๐–บ๐—‡๐–ผ๐–พ๐—…๐—…๐–พ๐–ฝ!**") + elif not phone_number.text.startswith("+") and not phone_number.text[1:].isdigit(): + return await message.reply_text( + "**๐–ค๐—‹๐—‹๐—ˆ๐—‹!** ๐–ฏ๐—๐—ˆ๐—‡๐–พ ๐—‡๐—Ž๐—†๐–ป๐–พ๐—‹ ๐—†๐—Ž๐—Œ๐— ๐–ป๐–พ ๐—‚๐—‡ ๐–ฝ๐—‚๐—€๐—‚๐—๐—Œ ๐–บ๐—‡๐–ฝ ๐—Œ๐—๐—ˆ๐—Ž๐—…๐–ฝ ๐–ผ๐—ˆ๐—‡๐—๐–บ๐—‚๐—‡ ๐–ผ๐—ˆ๐—Ž๐—‡๐—๐—‹๐—’ ๐–ผ๐—ˆ๐–ฝ๐–พ." + ) + + try: + client = Client( + name="Hellbot", + api_id=Config.API_ID, + api_hash=Config.API_HASH, + in_memory=True, + ) + await client.connect() + + code = await client.send_code(phone_number.text) + ask_otp = await hellbot.bot.ask( + message.chat.id, + "**2.** ๐–ค๐—‡๐—๐–พ๐—‹ ๐—๐—๐–พ ๐–ฎ๐–ณ๐–ฏ ๐—Œ๐–พ๐—‡๐— ๐—๐—ˆ ๐—’๐—ˆ๐—Ž๐—‹ ๐—๐–พ๐—…๐–พ๐—€๐—‹๐–บ๐—† ๐–บ๐–ผ๐–ผ๐—ˆ๐—Ž๐—‡๐— ๐–ป๐—’ ๐—Œ๐–พ๐—‰๐–บ๐—‹๐–บ๐—๐—‚๐—‡๐—€ ๐–พ๐—๐–พ๐—‹๐—’ ๐—‡๐—Ž๐—†๐–ป๐–พ๐—‹ ๐—๐—‚๐—๐— ๐–บ ๐—Œ๐—‰๐–บ๐–ผ๐–พ. \n\n**๐–ค๐—‘๐–บ๐—†๐—‰๐—…๐–พ:** `2 4 1 7 4`\n\n__๐–ฒ๐–พ๐—‡๐–ฝ /cancel ๐—๐—ˆ ๐–ผ๐–บ๐—‡๐–ผ๐–พ๐—… ๐—๐—๐–พ ๐—ˆ๐—‰๐–พ๐—‹๐–บ๐—๐—‚๐—ˆ๐—‡.__", + filters=filters.text, + timeout=300, + ) + if ask_otp.text == "/cancel": + return await message.reply_text("**๐–ข๐–บ๐—‡๐–ผ๐–พ๐—…๐—…๐–พ๐–ฝ!**") + otp = ask_otp.text.replace(" ", "") + + try: + await client.sign_in(phone_number.text, code.phone_code_hash, otp) + except SessionPasswordNeeded: + two_step_pass = await hellbot.bot.ask( + message.chat.id, + "**3.** ๐–ค๐—‡๐—๐–พ๐—‹ ๐—’๐—ˆ๐—Ž๐—‹ ๐—๐—๐—ˆ ๐—Œ๐—๐–พ๐—‰ ๐—๐–พ๐—‹๐—‚๐–ฟ๐—‚๐–ผ๐–บ๐—๐—‚๐—ˆ๐—‡ ๐—‰๐–บ๐—Œ๐—Œ๐—๐—ˆ๐—‹๐–ฝ: \n\n__๐–ฒ๐–พ๐—‡๐–ฝ /cancel ๐—๐—ˆ ๐–ผ๐–บ๐—‡๐–ผ๐–พ๐—… ๐—๐—๐–พ ๐—ˆ๐—‰๐–พ๐—‹๐–บ๐—๐—‚๐—ˆ๐—‡.__", + filters=filters.text, + timeout=120, + ) + if two_step_pass.text == "/cancel": + return await message.reply_text("**๐–ข๐–บ๐—‡๐–ผ๐–พ๐—…๐—…๐–พ๐–ฝ!**") + await client.check_password(two_step_pass.text) + + session_string = await client.export_session_string() + await message.reply_text( + f"**๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ!** ๐–ธ๐—ˆ๐—Ž๐—‹ ๐—Œ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡ ๐—Œ๐—๐—‹๐—‚๐—‡๐—€ ๐—‚๐—Œ ๐—€๐–พ๐—‡๐–พ๐—‹๐–บ๐—๐–พ๐–ฝ. ๐– ๐–ฝ๐–ฝ๐—‚๐—‡๐—€ ๐—‚๐— ๐—๐—ˆ ๐–ฝ๐–บ๐—๐–บ๐–ป๐–บ๐—Œ๐–พ..." + ) + user_id = (await client.get_me()).id + await db.update_session(user_id, session_string) + await client.disconnect() + await message.reply_text( + "**๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ!** ๐–ฒ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡ ๐—Œ๐—๐—‹๐—‚๐—‡๐—€ ๐–บ๐–ฝ๐–ฝ๐–พ๐–ฝ ๐—๐—ˆ ๐–ฝ๐–บ๐—๐–บ๐–ป๐–บ๐—Œ๐–พ. ๐–ธ๐—ˆ๐—Ž ๐–ผ๐–บ๐—‡ ๐—‡๐—ˆ๐— ๐—Ž๐—Œ๐–พ ๐–ง๐–พ๐—…๐—…๐–ก๐—ˆ๐— ๐—ˆ๐—‡ ๐—๐—๐—‚๐—Œ ๐–บ๐–ผ๐–ผ๐—ˆ๐—Ž๐—‡๐— ๐–บ๐–ฟ๐—๐–พ๐—‹ ๐—‹๐–พ๐—Œ๐—๐–บ๐—‹๐—๐—‚๐—‡๐—€ ๐—๐—๐–พ ๐–ป๐—ˆ๐—.\n\n**๐–ญ๐–ฎ๐–ณ๐–ค:** ๐–ฅ๐—ˆ๐—‹ ๐—Œ๐–พ๐–ผ๐—Ž๐—‹๐—‚๐—๐—’ ๐—‰๐—Ž๐—‹๐—‰๐—ˆ๐—Œ๐–พ๐—Œ ๐—‡๐—ˆ๐–ป๐—ˆ๐–ฝ๐—’ ๐—๐—‚๐—…๐—… ๐—๐–บ๐—๐–พ ๐—๐—๐–พ ๐–บ๐–ผ๐–ผ๐–พ๐—Œ๐—Œ ๐—๐—ˆ ๐—’๐—ˆ๐—Ž๐—‹ ๐—Œ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡ ๐—Œ๐—๐—‹๐—‚๐—‡๐—€. ๐–ญ๐—ˆ๐— ๐–พ๐—๐–พ๐—‡ ๐—’๐—ˆ๐—Ž ๐—ˆ๐—‹ ๐—๐—๐–พ ๐–ป๐—ˆ๐—." + ) + except TimeoutError: + await message.reply_text( + "**๐–ณ๐—‚๐—†๐–พ๐—ˆ๐—Ž๐—๐–ค๐—‹๐—‹๐—ˆ๐—‹!** ๐–ธ๐—ˆ๐—Ž ๐—๐—ˆ๐—ˆ๐—„ ๐—…๐—ˆ๐—‡๐—€๐–พ๐—‹ ๐—๐—๐–บ๐—‡ ๐–พ๐—‘๐–ผ๐—‰๐–พ๐–ผ๐—๐–พ๐–ฝ ๐—๐—ˆ ๐–ผ๐—ˆ๐—†๐—‰๐—…๐–พ๐—๐–พ ๐—๐—๐–พ ๐—‰๐—‹๐—ˆ๐–ผ๐–พ๐—Œ๐—Œ. ๐–ฏ๐—…๐–พ๐–บ๐—Œ๐–พ ๐—๐—‹๐—’ ๐–บ๐—€๐–บ๐—‚๐—‡." + ) + except Exception as e: + await message.reply_text(f"**๐–ค๐—‹๐—‹๐—ˆ๐—‹!** {e}") + + +@hellbot.bot.on_message( + filters.regex(r"Delete โŒ") & Config.AUTH_USERS & filters.private +) +async def delete_session(_, message: Message): + all_sessions = await db.get_all_sessions() + if not all_sessions: + return await message.reply_text("๐–ญ๐—ˆ ๐—Œ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡๐—Œ ๐–ฟ๐—ˆ๐—Ž๐—‡๐–ฝ ๐—‚๐—‡ ๐–ฝ๐–บ๐—๐–บ๐–ป๐–บ๐—Œ๐–พ.") + + collection = [] + for i in all_sessions: + collection.append((i["user_id"], f"rm_session:{i['user_id']}")) + + buttons = gen_inline_keyboard(collection, 2) + buttons.append([InlineKeyboardButton("Cancel โŒ", "auth_close")]) + + await message.reply_text( + "**๐–ข๐—๐—ˆ๐—ˆ๐—Œ๐–พ ๐–บ ๐—Œ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡ ๐—๐—ˆ ๐–ฝ๐–พ๐—…๐–พ๐—๐–พ:**", + reply_markup=InlineKeyboardMarkup(buttons), + ) + + +@hellbot.bot.on_callback_query(filters.regex(r"rm_session")) +async def rm_session_cb(client: Client, cb: CallbackQuery): + collection = [] + user_id = int(cb.data.split(":")[1]) + all_sessions = await db.get_all_sessions() + + if not all_sessions: + return await cb.message.delete() + + try: + owner = await client.get_users(Config.OWNER_ID) + owner_id = owner.id + owner_name = owner.first_name + except: + owner_id = Config.OWNER_ID + owner_name = "๐–ฎ๐—๐—‡๐–พ๐—‹" + if cb.from_user.id not in [user_id, owner_id]: + return await cb.answer( + f"๐– ๐–ผ๐–ผ๐–พ๐—Œ๐—Œ ๐—‹๐–พ๐—Œ๐—๐—‹๐—‚๐–ผ๐—๐–พ๐–ฝ ๐—๐—ˆ ๐–บ๐—‡๐—ˆ๐—๐—๐–พ๐—‹ ๐—Ž๐—Œ๐–พ๐—‹๐—Œ. Only {owner_name} and session client can delete this session!", + show_alert=True, + ) + + await db.rm_session(user_id) + await cb.answer("**๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ!** ๐–ฒ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡ ๐–ฝ๐–พ๐—…๐–พ๐—๐–พ๐–ฝ ๐–ฟ๐—‹๐—ˆ๐—† ๐–ฝ๐–บ๐—๐–บ๐–ป๐–บ๐—Œ๐–พ. \n__Restart the bot to apply changes.__", show_alert=True) + + for i in all_sessions: + collection.append((i["user_id"], f"rm_session:{i['user_id']}")) + + buttons = gen_inline_keyboard(collection, 2) + buttons.append([InlineKeyboardButton("Cancel โŒ", "auth_close")]) + + await cb.message.edit_reply_markup(InlineKeyboardMarkup(buttons)) + + +@hellbot.bot.on_message(filters.regex(r"List ๐Ÿ“œ") & Config.AUTH_USERS & filters.private) +async def list_sessions(_, message: Message): + all_sessions = await db.get_all_sessions() + if not all_sessions: + return await message.reply_text("๐–ญ๐—ˆ ๐—Œ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡๐—Œ ๐–ฟ๐—ˆ๐—Ž๐—‡๐–ฝ ๐—‚๐—‡ ๐–ฝ๐–บ๐—๐–บ๐–ป๐–บ๐—Œ๐–พ.") + + text = f"**{Symbols.cross_mark} ๐–ซ๐—‚๐—Œ๐— ๐—ˆ๐–ฟ ๐—Œ๐–พ๐—Œ๐—Œ๐—‚๐—ˆ๐—‡๐—Œ:**\n\n" + for i, session in enumerate(all_sessions): + text += f"[{'0' if i <= 9 else ''}{i+1}] {Symbols.bullet} **๐–ด๐—Œ๐–พ๐—‹ ๐–จ๐–ฃ:** `{session['user_id']}`\n" + + await message.reply_text(text) + + +@hellbot.bot.on_message(filters.regex(r"Home ๐Ÿ ") & filters.private & Config.AUTH_USERS) +async def go_home(_, message: Message): + await message.reply_text( + "**Home ๐Ÿ **", + reply_markup=ReplyKeyboardRemove(), + ) + await message.reply_text( + START_MSG.format(message.from_user.mention), + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup(start_button()), + ) + + +BotHelp("Sessions").add( + "session", "This command is packed with tools to manage userbot sessions." +).info( + "Session ๐Ÿš€" +).done() diff --git a/Hellbot/plugins/bot/users.py b/Hellbot/plugins/bot/users.py new file mode 100644 index 0000000000000000000000000000000000000000..0ebed55b41175e9b4afff036bd8e054f5d187b58 --- /dev/null +++ b/Hellbot/plugins/bot/users.py @@ -0,0 +1,82 @@ +from pyrogram import Client, filters +from pyrogram.types import Message + +from . import BotHelp, Config, Symbols, hellbot + + +@hellbot.bot.on_message( + filters.command("addauth") & Config.AUTH_USERS +) +async def addauth(client: Client, message: Message): + if not message.reply_to_message: + if len(message.command) < 2: + return await message.reply_text( + "Reply to a user or give me a userid/username to add them as an auth user!" + ) + try: + user = await client.get_users(message.command[1]) + except Exception: + return await message.reply_text( + "Give me a valid userid/username to add them as an auth user!" + ) + else: + user = message.reply_to_message.from_user + + if user.is_self: + return await message.reply_text("I can't add myself as an auth user!") + + if user.id in Config.AUTH_USERS: + return await message.reply_text(f"**{user.mention} is already authorized**") + + Config.AUTH_USERS.add(user.id) + await message.reply_text(f"**Added {user.mention} to auth users!**") + + +@hellbot.bot.on_message( + filters.command("delauth") & Config.AUTH_USERS +) +async def delauth(client: Client, message: Message): + if not message.reply_to_message: + if len(message.command) < 2: + return await message.reply_text( + "Reply to a user or give me a userid/username to add them as an auth user!" + ) + try: + user = await client.get_users(message.command[1]) + except Exception: + return await message.reply_text( + "Give me a valid userid/username to add them as an auth user!" + ) + else: + user = message.reply_to_message.from_user + + if user.id in Config.AUTH_USERS: + Config.AUTH_USERS.remove(user.id) + await message.reply_text(f"**Removed {user.mention} from auth users!**") + else: + await message.reply_text(f"**{user.mention} is not authorized**") + + +@hellbot.bot.on_message( + filters.command("authlist") & Config.AUTH_USERS +) +async def authlist(client: Client, message: Message): + text = "**๐Ÿ€ Authorized Users:**\n\n" + for i, userid in enumerate(Config.AUTH_USERS): + try: + user = await client.get_users(userid) + text += f" {Symbols.anchor} {user.mention} (`{user.id}`)\n" + except: + text += f" {Symbols.anchor} Auth User #{i+1} (`{userid}`)\n" + + await message.reply_text(text) + + +BotHelp("Users").add( + "addauth", + "This command is used to add a user as an authorized user. An authorized user can create and manage userbot session!", +).add("delauth", "This command is used to remove a user from authorized users.").add( + "authlist", "This command is used to list all authorized users." +).info( + "Users Command ๐Ÿš€" +).done() diff --git a/Hellbot/plugins/btnsG.py b/Hellbot/plugins/btnsG.py new file mode 100644 index 0000000000000000000000000000000000000000..f4954e986e88b92aa4f1e0a0d0fae2da7370d720 --- /dev/null +++ b/Hellbot/plugins/btnsG.py @@ -0,0 +1,106 @@ +# G: Glass Buttons + +from math import ceil + +from pyrogram.types import InlineKeyboardButton + +from Hellbot.core import ENV, Symbols, db, Config + + +def gen_inline_keyboard(collection: list, row: int = 2) -> list[list[InlineKeyboardButton]]: + keyboard = [] + for i in range(0, len(collection), row): + kyb = [] + for x in collection[i : i + row]: + button = btn(*x) + kyb.append(button) + keyboard.append(kyb) + return keyboard + + +def btn(text, value, type="callback_data") -> InlineKeyboardButton: + return InlineKeyboardButton(text, **{type: value}) + + +async def gen_inline_help_buttons(page: int, plugins: list) -> tuple[list, int]: + buttons = [] + column = await db.get_env(ENV.btn_in_help) or 5 + column = int(column) + emoji = await db.get_env(ENV.help_emoji) or "โœง" + pairs = list(map(list, zip(plugins[::2], plugins[1::2]))) + + if len(plugins) % 2 == 1: + pairs.append([plugins[-1]]) + + max_pages = ceil(len(pairs) / column) + pairs = [pairs[i : i + column] for i in range(0, len(pairs), column)] + + for pair in pairs[page]: + btn_pair = [] + for i, plugin in enumerate(pair): + if i % 2 == 0: + btn_pair.append( + InlineKeyboardButton(f"{emoji} {plugin}", f"help_menu:{page}:{plugin}") + ) + else: + btn_pair.append( + InlineKeyboardButton(f"{plugin} {emoji}", f"help_menu:{page}:{plugin}") + ) + buttons.append(btn_pair) + + buttons.append( + [ + InlineKeyboardButton( + Symbols.previous, f"help_page:{(max_pages - 1) if page == 0 else (page - 1)}", + ), + InlineKeyboardButton( + Symbols.close, "help_data:c" + ), + InlineKeyboardButton( + Symbols.next, f"help_page:{0 if page == (max_pages - 1) else (page + 1)}", + ), + ] + ) + + return buttons, max_pages + + +async def gen_bot_help_buttons() -> list[list[InlineKeyboardButton]]: + buttons = [] + plugins = sorted(Config.BOT_CMD_MENU) + emoji = await db.get_env(ENV.help_emoji) or "โœง" + pairs = list(map(list, zip(plugins[::2], plugins[1::2]))) + + if len(plugins) % 2 == 1: + pairs.append([plugins[-1]]) + + for pair in pairs: + btn_pair = [] + for i, plugin in enumerate(pair): + if i % 2 == 0: + btn_pair.append( + InlineKeyboardButton(f"{emoji} {plugin}", f"bot_help_menu:{plugin}") + ) + else: + btn_pair.append( + InlineKeyboardButton(f"{plugin} {emoji}", f"bot_help_menu:{plugin}") + ) + buttons.append(btn_pair) + + buttons.append( + [ + InlineKeyboardButton("๐Ÿ ", "help_data:start"), + InlineKeyboardButton(Symbols.close, "help_data:botclose"), + ] + ) + + return buttons + + +def start_button() -> list[list[InlineKeyboardButton]]: + return [ + [ + InlineKeyboardButton("โš™๏ธ Help", "help_data:bothelp"), + InlineKeyboardButton("Source ๐Ÿ“ฆ", "help_data:source"), + ] + ] diff --git a/Hellbot/plugins/btnsK.py b/Hellbot/plugins/btnsK.py new file mode 100644 index 0000000000000000000000000000000000000000..47aad6ac13a20ed91e5cababebb871ea648a5945 --- /dev/null +++ b/Hellbot/plugins/btnsK.py @@ -0,0 +1,45 @@ +# K: Keyboard Buttons + +from pyrogram.types import KeyboardButton, ReplyKeyboardMarkup + + +def gen_keyboard(collection: list, row: int = 2) -> list[list[KeyboardButton]]: + keyboard = [] + for i in range(0, len(collection), row): + kyb = [] + for x in collection[i : i + row]: + kyb.append(KeyboardButton(x)) + keyboard.append(kyb) + return keyboard + + +def session_keyboard() -> ReplyKeyboardMarkup: + return ReplyKeyboardMarkup( + [ + [ + KeyboardButton("New ๐Ÿ’ซ"), + KeyboardButton("Delete โŒ"), + ], + [ + KeyboardButton("List ๐Ÿ“œ"), + KeyboardButton("Home ๐Ÿ "), + ], + ], + resize_keyboard=True, + ) + + +def start_keyboard() -> ReplyKeyboardMarkup: + return ReplyKeyboardMarkup( + [ + [ + KeyboardButton("๐Ÿ“Ÿ Session"), + KeyboardButton("Force Sub โœจ"), + ], + [ + KeyboardButton("๐Ÿ‘ฅ Users"), + KeyboardButton("Others ๐Ÿ“ฃ"), + ], + ], + resize_keyboard=True, + ) diff --git a/Hellbot/plugins/decorator.py b/Hellbot/plugins/decorator.py new file mode 100644 index 0000000000000000000000000000000000000000..b0e36b84fba19ca7591958f8284d992bd120c2ff --- /dev/null +++ b/Hellbot/plugins/decorator.py @@ -0,0 +1,67 @@ +from pyrogram import Client, filters +from pyrogram.enums import ChatType +from pyrogram.handlers import MessageHandler +from pyrogram.types import Message + +from Hellbot.core import Config, db, hellbot +from Hellbot.functions.admins import is_user_admin + + +def on_message( + command: str | list[str], + group: int = 0, + chat_type: list[ChatType] = None, + admin_only: bool = False, + allow_stan: bool = False, +): + if allow_stan: + _filter = ( + filters.command(command, Config.HANDLERS) + & (filters.me | Config.STAN_USERS) + & ~filters.forwarded + & ~filters.via_bot + ) + else: + _filter = ( + filters.command(command, Config.HANDLERS) + & filters.me + & ~filters.forwarded + & ~filters.via_bot + ) + + def decorator(func): + async def wrapper(client: Client, message: Message): + if client.me.id != message.from_user.id: + if not await db.is_stan(client.me.id, message.from_user.id): + return + + if admin_only and not message.chat.type == ChatType.PRIVATE: + if not await is_user_admin(message.chat, client.me.id): + return await hellbot.edit(message, "๐–จ ๐–บ๐—† ๐—‡๐—ˆ๐— ๐–บ๐—‡ ๐–บ๐–ฝ๐—†๐—‚๐—‡ ๐—๐–พ๐—‹๐–พ!") + + if chat_type and message.chat.type not in chat_type: + return await hellbot.edit(message, "๐–ข๐–บ๐—‡'๐— ๐—Ž๐—Œ๐–พ ๐—๐—๐—‚๐—Œ ๐–ผ๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ ๐—๐–พ๐—‹๐–พ!") + + await func(client, message) + message.continue_propagation() + + for user in hellbot.users: + user.add_handler(MessageHandler(wrapper, _filter), group) + + return wrapper + + return decorator + + +def custom_handler(filters: filters.Filter, group: int = 0): + def decorator(func): + async def wrapper(client: Client, message: Message): + await func(client, message) + message.continue_propagation() + + for user in hellbot.users: + user.add_handler(MessageHandler(wrapper, filters), group) + + return wrapper + + return decorator diff --git a/Hellbot/plugins/help.py b/Hellbot/plugins/help.py new file mode 100644 index 0000000000000000000000000000000000000000..e8f5c0a5ed2f9e3d214b7a8777fce820f40a3a4b --- /dev/null +++ b/Hellbot/plugins/help.py @@ -0,0 +1,132 @@ +from Hellbot.core.config import Config, Symbols + + +class HelpMenu: + def __init__(self, file: str) -> None: + self.filename = file + self.command_dict = {} + self.command_info = "" + + def add( + self, + command: str, + parameters: str = None, + description: str = None, + example: str = None, + note: str = None, + ): + self.command_dict[command] = { + "command": command, + "parameters": parameters, + "description": description, + "example": example, + "note": note, + } + return self + + def info(self, command_info: str): + self.command_info = command_info + return self + + def get_menu(self) -> str: + result = f"**๐–ฏ๐—…๐—Ž๐—€๐—‚๐—‡ ๐–ฅ๐—‚๐—…๐–พ:** `{self.filename}`" + if self.command_info: + result += f"\n**๐–ฏ๐—…๐—Ž๐—€๐—‚๐—‡ ๐–จ๐—‡๐–ฟ๐—ˆ:** __{self.command_info} ๐Ÿ€__" + result += "\n\n" + for command in self.command_dict: + command = self.command_dict[command] + result += f"**{Symbols.radio_select} ๐–ข๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ:** `{Config.HANDLERS[0]}{command['command']}" + if command["parameters"]: + result += f" {command['parameters']}`\n" + else: + result += "`\n" + if command["description"]: + result += ( + f"**{Symbols.arrow_right} ๐–ฃ๐–พ๐—Œ๐–ผ๐—‹๐—‚๐—‰๐—๐—‚๐—ˆ๐—‡:** __{command['description']}__\n" + ) + if command["example"]: + result += f"**{Symbols.arrow_right} ๐–ค๐—‘๐–บ๐—†๐—‰๐—…๐–พ:** `{Config.HANDLERS[0]}{command['example']}`\n" + if command["note"]: + result += f"**{Symbols.arrow_right} ๐–ญ๐—ˆ๐—๐–พ:** __{command['note']}__\n" + + result += "\n" + + Config.CMD_INFO[command["command"]] = { + "command": f"{command['command']} {command['parameters'] if command['parameters'] else ''}", + "description": command["description"], + "example": command["example"], + "note": command["note"], + "plugin": self.filename, + } + + return result + + def done(self) -> None: + Config.HELP_DICT[self.filename] = { + "commands": self.command_dict, + "info": self.command_info, + } + Config.CMD_MENU[self.filename] = self.get_menu() + + +class BotHelp: + def __init__(self, file: str) -> None: + self.category = file + self.command_dict = {} + self.command_info = "" + + def add(self, command: str, description: str): + self.command_dict[command] = {"command": command, "description": description} + return self + + def info(self, command_info: str): + self.command_info = command_info + return self + + def get_menu(self) -> str: + result = f"**๐–ฏ๐—…๐—Ž๐—€๐—‚๐—‡ ๐–ข๐–บ๐—๐–พ๐—€๐—ˆ๐—‹๐—’:** `{self.category}`" + if self.command_info: + result += f"\n**๐–ฏ๐—…๐—Ž๐—€๐—‚๐—‡ ๐–จ๐—‡๐–ฟ๐—ˆ:** __{self.command_info}__" + result += "\n\n" + for command in self.command_dict: + command = self.command_dict[command] + result += f"**{Symbols.radio_select} ๐–ข๐—ˆ๐—†๐—†๐–บ๐—‡๐–ฝ:** `/{command['command']}`\n" + if command["description"]: + result += ( + f"**{Symbols.arrow_right} ๐–ฃ๐–พ๐—Œ๐–ผ๐—‹๐—‚๐—‰๐—๐—‚๐—ˆ๐—‡:** __{command['description']}__\n" + ) + result += "\n" + + Config.BOT_CMD_INFO[command["command"]] = { + "command": command["command"], + "description": command["description"], + "category": self.category, + } + + return result + + def done(self) -> None: + Config.BOT_HELP[self.category] = { + "commands": self.command_dict, + "info": self.command_info, + } + Config.BOT_CMD_MENU[self.category] = self.get_menu() + + +# example usage of HelpMenu class +""" +HelpMenu("example").add( + "example", "", "description of command", "example of command", "note of command" +).info( + "information of plugin" +).done() +""" + +# example usage of BotHelp class +""" +BotHelp("example").add( + "example", "description of command" +).info( + "information of category" +).done() +""" diff --git a/Hellbot/plugins/user/__init__.py b/Hellbot/plugins/user/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6303c70a008653f924fba34a7f9cde94c12c987a --- /dev/null +++ b/Hellbot/plugins/user/__init__.py @@ -0,0 +1,16 @@ +from pyrogram.enums import ChatType + +from Hellbot.core.clients import hellbot +from Hellbot.core.config import Config, Symbols +from Hellbot.core.database import db +from Hellbot.plugins.decorator import custom_handler, on_message +from Hellbot.plugins.help import HelpMenu + +handler = Config.HANDLERS[0] +bot = hellbot.bot + +bot_only = [ChatType.BOT] +group_n_channel = [ChatType.GROUP, ChatType.SUPERGROUP, ChatType.CHANNEL] +group_only = [ChatType.GROUP, ChatType.SUPERGROUP] +private_n_bot = [ChatType.PRIVATE, ChatType.BOT] +private_only = [ChatType.PRIVATE] diff --git a/Hellbot/plugins/user/admins.py b/Hellbot/plugins/user/admins.py new file mode 100644 index 0000000000000000000000000000000000000000..453dd3a486b83c7ac0d0f85362c34a4c4d057baa --- /dev/null +++ b/Hellbot/plugins/user/admins.py @@ -0,0 +1,510 @@ +import asyncio + +from pyrogram import Client, filters +from pyrogram.types import ChatPermissions, ChatPrivileges, Message + +from Hellbot.core import LOGS + +from . import HelpMenu, custom_handler, db, group_only, handler, hellbot, on_message + + +@on_message( + "promote", + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def promote(client: Client, message: Message): + if len(message.command) < 2 and not message.reply_to_message: + return await hellbot.delete( + message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ ๐—ˆ๐—‹ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹ ๐—๐—ˆ ๐—‰๐—‹๐—ˆ๐—†๐—ˆ๐—๐–พ ๐—๐—๐–พ๐—†!" + ) + + if message.reply_to_message: + user = message.reply_to_message.from_user + title = await hellbot.input(message) + else: + user = await client.get_users(message.command[1]) + title = (await hellbot.input(message)).split(" ", 1)[1].strip() or "" + + try: + privileges = ChatPrivileges( + can_manage_chat=True, + can_delete_messages=True, + can_manage_video_chats=True, + can_restrict_members=False, + can_promote_members=False, + can_change_info=False, + can_invite_users=True, + can_pin_messages=True, + is_anonymous=False, + ) + await message.chat.promote_member(user.id, privileges) + await client.set_administrator_title(message.chat.id, user.id, title) + except Exception as e: + return await hellbot.error(message, e) + + await hellbot.delete(message, f"**๐Ÿ’ซ ๐–ฏ๐—‹๐—ˆ๐—†๐—ˆ๐—๐–พ๐–ฝ {user.mention} ๐—Œ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ๐–ฟ๐—Ž๐—…๐—…๐—’!**") + await hellbot.check_and_log( + "promote", + f"**Promoted User**\n\n**User:** {user.mention}\n**User ID:** `{user.id}`\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message( + "demote", + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def demote(client: Client, message: Message): + if len(message.command) < 2 and not message.reply_to_message: + return await hellbot.delete( + message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ ๐—ˆ๐—‹ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹ ๐—๐—ˆ ๐–ฝ๐–พ๐—†๐—ˆ๐—๐–พ ๐—๐—๐–พ๐—†!" + ) + + if message.reply_to_message: + user = message.reply_to_message.from_user + else: + user = await client.get_users(message.command[1]) + try: + privileges = ChatPrivileges( + can_manage_chat=False, + can_delete_messages=False, + can_manage_video_chats=False, + can_restrict_members=False, + can_promote_members=False, + can_change_info=False, + can_invite_users=False, + can_pin_messages=False, + is_anonymous=False, + ) + await message.chat.promote_member(user.id, privileges) + except Exception as e: + return await hellbot.error(message, e) + + await hellbot.delete(message, f"**๐Ÿ™„ ๐–ฃ๐–พ๐—†๐—ˆ๐—๐–พ๐–ฝ {user.mention} ๐—Œ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ๐–ฟ๐—Ž๐—…๐—…๐—’!**") + await hellbot.check_and_log( + "demote", + f"**Demoted User**\n\n**User:** {user.mention}\n**User ID:** `{user.id}`\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message( + ["ban", "dban"], + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def ban(client: Client, message: Message): + if message.reply_to_message: + user = message.reply_to_message.from_user + if len(message.command) < 2: + reason = None + else: + reason = await hellbot.input(message) + if message.command[0][0].lower() == "d": + await message.reply_to_message.delete() + elif len(message.command) == 2: + user = await client.get_users(message.command[1]) + reason = None + elif len(message.command) > 2: + user = await client.get_users(message.command[1]) + reason = (await hellbot.input(message)).split(" ", 1)[1].strip() + else: + return await hellbot.delete( + message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ ๐—ˆ๐—‹ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹ ๐—๐—ˆ ๐–ป๐–บ๐—‡ ๐—๐—๐–พ๐—†!" + ) + + try: + await message.chat.ban_member(user.id) + except Exception as e: + return await hellbot.error(message, e) + + reason = reason if reason else "Not Specified" + await hellbot.delete( + message, + f"**โ˜ ๏ธ ๐–ก๐–บ๐—‡๐—‡๐–พ๐–ฝ {user.mention} ๐—Œ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ๐–ฟ๐—Ž๐—…๐—…๐—’!**\n**๐–ฑ๐–พ๐–บ๐—Œ๐—ˆ๐—‡:** `{reason}`", + 30, + ) + await hellbot.check_and_log( + "ban", + f"**Banned User**\n\n**User:** {user.mention}\n**User ID:** `{user.id}`\n**Reason:** `{reason}`\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message( + "unban", + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def unban(client: Client, message: Message): + if len(message.command) < 2 and not message.reply_to_message: + return await hellbot.delete( + message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ ๐—ˆ๐—‹ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹ ๐—๐—ˆ ๐—Ž๐—‡๐–ป๐–บ๐—‡ ๐—๐—๐–พ๐—†!" + ) + + if message.reply_to_message: + user = message.reply_to_message.from_user + else: + user = await client.get_users(message.command[1]) + + try: + await message.chat.unban_member(user.id) + except Exception as e: + return await hellbot.error(message, e) + + await hellbot.delete(message, f"**๐Ÿค— ๐–ด๐—‡๐–ป๐–บ๐—‡๐—‡๐–พ๐–ฝ {user.mention} ๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ๐–ฟ๐—Ž๐—…๐—…๐—’!**", 30) + await hellbot.check_and_log( + "unban", + f"**Unbanned User**\n\n**User:** {user.mention}\n**User ID:** `{user.id}`\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message( + ["kick", "dkick"], + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def kick(client: Client, message: Message): + if message.reply_to_message: + user = message.reply_to_message.from_user + if len(message.command) < 2: + reason = None + else: + reason = await hellbot.input(message) + if message.command[0][0].lower() == "d": + await message.reply_to_message.delete() + elif len(message.command) == 2: + user = await client.get_users(message.command[1]) + reason = None + elif len(message.command) > 2: + user = await client.get_users(message.command[1]) + reason = (await hellbot.input(message)).split(" ", 1)[1].strip() + else: + return await hellbot.delete( + message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ ๐—ˆ๐—‹ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹ ๐—๐—ˆ ๐—„๐—‚๐–ผ๐—„ ๐—๐—๐–พ๐—†!" + ) + + try: + await message.chat.ban_member(user.id) + except Exception as e: + return await hellbot.error(message, e) + + reason = reason if reason else "Not Specified" + await hellbot.delete( + message, + f"**๐Ÿ‘‹ ๐–ช๐—‚๐–ผ๐—„๐–พ๐–ฝ {user.mention} ๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ๐–ฟ๐—Ž๐—…๐—…๐—’!**\n**๐–ฑ๐–พ๐–บ๐—Œ๐—ˆ๐—‡:** `{reason}`", + 30, + ) + await hellbot.check_and_log( + "kick", + f"**Kicked User**\n\n**User:** {user.mention}\n**User ID:** `{user.id}`\n**Reason:** `{reason}`\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + await asyncio.sleep(5) + await message.chat.unban_member(user.id) + + +@on_message( + "mute", + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def mute(client: Client, message: Message): + if message.reply_to_message: + user = message.reply_to_message.from_user + if len(message.command) < 2: + reason = None + else: + reason = await hellbot.input(message) + elif len(message.command) == 2: + user = await client.get_users(message.command[1]) + reason = None + elif len(message.command) > 2: + user = await client.get_users(message.command[1]) + reason = (await hellbot.input(message)).split(" ", 1)[1].strip() + else: + return await hellbot.delete( + message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ ๐—ˆ๐—‹ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹ ๐—๐—ˆ ๐—†๐—Ž๐—๐–พ ๐—๐—๐–พ๐—†!" + ) + + try: + permissions = ChatPermissions( + can_send_messages=False, + ) + await message.chat.restrict_member(user.id, permissions) + except Exception as e: + return await hellbot.error(message, e) + + reason = reason if reason else "Not Specified" + await hellbot.delete( + message, + f"**๐Ÿค ๐–ฌ๐—Ž๐—๐–พ๐–ฝ {user.mention} ๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ๐–ฟ๐—Ž๐—…๐—…๐—’!**\n**๐–ฑ๐–พ๐–บ๐—Œ๐—ˆ๐—‡:** `{reason}`", + 30, + ) + await hellbot.check_and_log( + "mute", + f"**Muted User**\n\n**User:** {user.mention}\n**User ID:** `{user.id}`\n**Reason:** `{reason}`\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message( + "unmute", + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def unmute(client: Client, message: Message): + if len(message.command) < 2 and not message.reply_to_message: + return await hellbot.delete( + message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ ๐—ˆ๐—‹ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹ ๐—๐—ˆ ๐—Ž๐—‡๐—†๐—Ž๐—๐–พ ๐—๐—๐–พ๐—†!" + ) + + if message.reply_to_message: + user = message.reply_to_message.from_user + else: + user = await client.get_users(message.command[1]) + + try: + permissions = ChatPermissions( + can_send_messages=True, + ) + await message.chat.restrict_member(user.id, permissions) + except Exception as e: + return await hellbot.error(message, e) + + await hellbot.delete(message, f"**๐Ÿ˜ ๐–ด๐—‡๐—†๐—Ž๐—๐–พ๐–ฝ {user.mention} ๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ๐–ฟ๐—Ž๐—…๐—…๐—’!**", 30) + await hellbot.check_and_log( + "unmute", + f"**Unmuted User**\n\n**User:** {user.mention}\n**User ID:** `{user.id}`\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message("dmute", allow_stan=True) +async def dmute(client: Client, message: Message): + if message.reply_to_message: + user = message.reply_to_message.from_user + if len(message.command) < 2: + reason = None + else: + reason = await hellbot.input(message) + elif len(message.command) == 2: + user = await client.get_users(message.command[1]) + reason = None + elif len(message.command) > 2: + user = await client.get_users(message.command[1]) + reason = (await hellbot.input(message)).split(" ", 1)[1].strip() + else: + return await hellbot.delete( + message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ ๐—ˆ๐—‹ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹ ๐—๐—ˆ ๐—†๐—Ž๐—๐–พ ๐—๐—๐–พ๐—†!" + ) + + if await db.is_muted(client.me.id, user.id, message.chat.id): + return await hellbot.delete(message, "This user is already dmuted.") + + reason = reason if reason else "Not Specified" + await db.add_mute(client.me.id, user.id, message.chat.id, reason) + await hellbot.delete( + message, + f"**๐Ÿค ๐–ฌ๐—Ž๐—๐–พ๐–ฝ {user.mention} ๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ๐–ฟ๐—Ž๐—…๐—…๐—’!**\n**๐–ฑ๐–พ๐–บ๐—Œ๐—ˆ๐—‡:** `{reason}`", + 30, + ) + await hellbot.check_and_log( + "dmute", + f"**D-Muted User**\n\n**User:** {user.mention}\n**User ID:** `{user.id}`\n**Reason:** `{reason}`\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title or message.chat.first_name}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message("undmute", allow_stan=True) +async def undmute(client: Client, message: Message): + if len(message.command) < 2 and not message.reply_to_message: + return await hellbot.delete( + message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ ๐—ˆ๐—‹ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐–บ ๐—Ž๐—Œ๐–พ๐—‹ ๐—๐—ˆ ๐—Ž๐—‡๐—†๐—Ž๐—๐–พ ๐—๐—๐–พ๐—†!" + ) + + if message.reply_to_message: + user = message.reply_to_message.from_user + else: + user = await client.get_users(message.command[1]) + + if not await db.is_muted(client.me.id, user.id, message.chat.id): + return await hellbot.delete(message, "๐–ณ๐—๐–พ ๐—Ž๐—Œ๐–พ๐—‹ ๐—‚๐—Œ ๐—‡๐—ˆ๐— ๐—†๐—Ž๐—๐–พ๐–ฝ!") + + reason = await db.rm_mute(client.me.id, user.id, message.chat.id) + await hellbot.delete( + message, + f"**๐Ÿ˜ ๐–ด๐—‡๐—†๐—Ž๐—๐–พ๐–ฝ {user.mention} ๐–ฒ๐—Ž๐–ผ๐–ผ๐–พ๐—Œ๐—Œ๐–ฟ๐—Ž๐—…๐—…๐—’!**\n\n**Mute reason was:** `{reason}`", + 30, + ) + await hellbot.check_and_log( + "unmute", + f"**D-Unmuted User**\n\n**User:** {user.mention}\n**User ID:** `{user.id}`\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message( + "pin", + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def pin(_, message: Message): + if not message.reply_to_message: + return await hellbot.delete(message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐—‰๐—‚๐—‡ ๐–บ ๐—†๐–พ๐—Œ๐—Œ๐–บ๐—€๐–พ!") + + try: + await message.reply_to_message.pin() + except Exception as e: + return await hellbot.error(message, e) + + await hellbot.delete( + message, + f"**๐Ÿ“Œ ๐–ฏ๐—‚๐—‡๐—‡๐–พ๐–ฝ [๐–ฌ๐–พ๐—Œ๐—Œ๐–บ๐—€๐–พ]({message.reply_to_message.link}) ๐—‚๐—‡ {message.chat.title}!**", + 30, + ) + await hellbot.check_and_log( + "pin", + f"**Pinned Message**\n\n**Message:** [Click Here]({message.reply_to_message.link})\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message( + "unpin", + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def unpin(_, message: Message): + if not message.reply_to_message: + return await hellbot.delete(message, "๐–ญ๐–พ๐–พ๐–ฝ ๐–บ ๐—‹๐–พ๐—‰๐—…๐—’ ๐—๐—ˆ ๐—Ž๐—‡๐—‰๐—‚๐—‡ ๐–บ ๐—†๐–พ๐—Œ๐—Œ๐–บ๐—€๐–พ!") + + try: + await message.reply_to_message.unpin() + except Exception as e: + return await hellbot.error(message, e) + + await hellbot.delete( + message, + f"**๐Ÿ“Œ ๐–ด๐—‡๐—‰๐—‚๐—‡๐—‡๐–พ๐–ฝ [๐–ฌ๐–พ๐—Œ๐—Œ๐–บ๐—€๐–พ]({message.reply_to_message.link}) ๐—‚๐—‡ {message.chat.title}!**", + 30, + ) + await hellbot.check_and_log( + "unpin", + f"**Unpinned Message**\n\n**Message:** [Click Here]({message.reply_to_message.link})\n**Admin:** `{message.from_user.mention}`\n**Group:** `{message.chat.title}`\n**Group ID:** `{message.chat.id}`", + ) + + +@on_message( + "zombies", + chat_type=group_only, + admin_only=True, + allow_stan=True, +) +async def zombies(_, message: Message): + hell = await hellbot.edit(message, "โ˜ ๏ธ ๐–ฃ๐–พ๐—๐–พ๐–ผ๐—๐—‚๐—‡๐—€ ๐—“๐—ˆ๐—†๐–ป๐—‚๐–พ๐—Œ...") + ded_users = [] + async for members in message.chat.get_members(): + if members.user.is_deleted: + ded_users.append(members.user.id) + + if not ded_users: + return await hell.edit( + "๐Ÿซก ๐–ฃ๐—ˆ๐—‡'๐— ๐—๐–บ๐—๐–พ ๐–บ๐—‡๐—’ ๐—“๐—ˆ๐—†๐–ป๐—‚๐–พ๐—Œ ๐—‚๐—‡ ๐—๐—๐—‚๐—Œ ๐—€๐—‹๐—ˆ๐—Ž๐—‰. **๐–ฆ๐—‹๐—ˆ๐—Ž๐—‰๐—Œ' ๐–ผ๐—…๐–พ๐–บ๐—‡ ๐– ๐–ฅ!**" + ) + + if len(message.command) > 1 and message.command[1].lower() == "clean": + await hell.edit( + f"โ˜ ๏ธ ๐–ฅ๐—ˆ๐—Ž๐—‡๐–ฝ {len(ded_users)} ๐—“๐—ˆ๐—†๐–ป๐—‚๐–พ๐—Œ... **๐Ÿ”ซ ๐–ณ๐—‚๐—†๐–พ ๐—๐—ˆ ๐—‰๐—Ž๐—‹๐—€๐–พ ๐—๐—๐–พ๐—†!**" + ) + failed = 0 + success = 0 + for user in ded_users: + try: + await message.chat.ban_member(user) + success += 1 + except Exception as e: + LOGS.error(e) + failed += 1 + + await hell.edit(f"**๐–ฏ๐—Ž๐—‹๐—€๐–พ๐–ฝ {success} ๐—“๐—ˆ๐—†๐–ป๐—‚๐–พ๐—Œ!**\n`{failed}` holds immunity!") + else: + await hell.edit( + f"**โ˜ ๏ธ ๐–ฅ๐—ˆ๐—Ž๐—‡๐–ฝ {len(ded_users)} ๐—“๐—ˆ๐—†๐–ป๐—‚๐–พ๐—Œ!**\n\n__Use__ `{handler}zombies clean` __to kill them!__" + ) + + +@custom_handler(filters.incoming) +async def multiple_handler(client: Client, message: Message): + if not message.from_user: + return + + if await db.is_muted(client.me.id, message.from_user.id, message.chat.id): + try: + await message.delete() + except: + pass + + elif await db.is_gmuted(message.from_user.id): + try: + await message.delete() + except: + pass + + elif await db.is_echo(client.me.id, message.chat.id, message.from_user.id): + await message.copy(message.chat.id, reply_to_message_id=message.id) + + +HelpMenu("admin").add( + "promote", + "<๐—Ž๐—Œ๐–พ๐—‹๐—‡๐–บ๐—†๐–พ/๐—‚๐–ฝ/reply> <๐—๐—‚๐—๐—…๐–พ>", + "Promote a user to admin.", + "promote @ForGo10God hellboy", +).add( + "demote", "", "Demote a user from admin.", "demote @ForGo10God" +).add( + "ban", + " ", + "Ban a user from the group.", + "ban @ForGo10God", + "You can also use dban to delete the message of the user.", +).add( + "unban", "", "Unban a user from the group.", "unban @ForGo10God" +).add( + "kick", + " ", + "Kick a user from the group.", + "kick @ForGo10God", + "You can also use dkick to delete the message of the user.", +).add( + "mute", + " ", + "Mute a user in the group", + "mute @ForGo10God", + "You can also use dmute to delete the message of the user.", +).add( + "unmute", "", "Unmute a user in the group.", "unmute @ForGo10God" +).add( + "dmute", + "", + "Mute a user by deleting their new messages in the group.", + "dmute @ForGo10God", + "Need delete message permission for proper functioning.", +).add( + "undmute", + "", + "Unmute a user who's muted using 'dmute' command in the group.", + "undmute @ForGo10God", +).add( + "pin", "", "Pin the replied message in the group." +).add( + "unpin", "", "Unpin the replied pinned message in the group." +).add( + "zombies", + "clean", + "Finds the total number of deleted users present in that group and ban them.", +).info( + "Admin Menu" +).done() diff --git a/Hellbot/plugins/user/afk.py b/Hellbot/plugins/user/afk.py new file mode 100644 index 0000000000000000000000000000000000000000..4f67181865ec43a5145507073e21692a33c54909 --- /dev/null +++ b/Hellbot/plugins/user/afk.py @@ -0,0 +1,157 @@ +import os +import random +import time + +from pyrogram import Client, filters +from pyrogram.enums import MessageMediaType +from pyrogram.types import Message + +from Hellbot.core import Config, db, hellbot +from Hellbot.functions.formatter import add_to_dict, get_from_dict, readable_time + +from . import HelpMenu, custom_handler, group_only, on_message + +afk_quotes = [ + "๐Ÿšถโ€โ™‚๏ธ Taking a break, be back soon!", + "โณ AFK - Away From the Keyboard momentarily.", + "๐Ÿ”œ Stepped away, but I'll return shortly.", + "๐Ÿ‘‹ Gone for a moment, not forgotten.", + "๐ŸŒฟ Taking a breather, back in a bit.", + "๐Ÿ“ต Away for a while, feel free to leave a message!", + "โฐ On a short break, back shortly.", + "๐ŸŒˆ Away from the screen, catching a breath.", + "๐Ÿ’ค Offline for a moment, but still here in spirit.", + "๐Ÿš€ Exploring the real world, back in a moment!", + "๐Ÿต Taking a tea break, back shortly!", + "๐ŸŒ™ Resting my keyboard, back after a short nap.", + "๐Ÿšถโ€โ™€๏ธ Stepping away for a moment of peace.", + "๐ŸŽต AFK but humming along, back shortly!", + "๐ŸŒž Taking a sunshine break, back soon!", + "๐ŸŒŠ Away, catching some waves of relaxation.", + "๐Ÿšช Temporarily closed, be back in a bit!", + "๐ŸŒธ Taking a moment to smell the digital roses.", + "๐Ÿƒ Stepped into the real world for a while.", +] + + +@on_message("afk") +async def afk(_, message: Message): + if await db.is_afk(message.from_user.id): + return await hellbot.delete(message, "๐Ÿ™„ ๐–จ'๐—† ๐–บ๐—…๐—‹๐–พ๐–บ๐–ฝ๐—’ ๐– ๐–ฅ๐–ช!") + + media_type = None + media = None + + if message.reply_to_message and message.reply_to_message.media: + if message.reply_to_message.media == MessageMediaType.ANIMATION: + media_type = "animation" + elif message.reply_to_message.media == MessageMediaType.AUDIO: + media_type = "audio" + elif message.reply_to_message.media == MessageMediaType.PHOTO: + media_type = "photo" + elif message.reply_to_message.media == MessageMediaType.STICKER: + media_type = "sticker" + elif message.reply_to_message.media == MessageMediaType.VIDEO: + media_type = "video" + elif message.reply_to_message.media == MessageMediaType.VOICE: + media_type = "voice" + + media = await message.reply_to_message.forward(Config.LOGGER_ID) + + reason = await hellbot.input(message) + reason = reason if reason else "Not specified" + + await db.set_afk( + message.from_user.id, reason, media.id if media else None, media_type + ) + await hellbot.delete(message, "๐Ÿซก ๐–ฆ๐—ˆ๐—‚๐—‡๐—€ ๐– ๐–ฅ๐–ช! ๐–ฒ๐–พ๐–พ ๐—’๐–บ'๐—…๐—… ๐—…๐–บ๐—๐–พ๐—‹.") + await hellbot.check_and_log( + "afk", + f"Going AFK! \n\n**Reason:** `{reason}`", + ) + add_to_dict(Config.AFK_CACHE, [message.from_user.id, message.chat.id]) + + +@custom_handler(filters.incoming & ~filters.bot & ~filters.service) +async def afk_watch(client: Client, message: Message): + afk_data = await db.get_afk(client.me.id) + if not afk_data: + return + + if message.from_user.id == afk_data["user_id"]: + return + + if message.chat.type in group_only: + if not message.mentioned: + return + + afk_time = readable_time(round(time.time() - afk_data["time"])) + caption = f"**{random.choice(afk_quotes)}**\n\n**๐Ÿ’ซ ๐–ฑ๐–พ๐–บ๐—Œ๐—ˆ๐—‡:** {afk_data['reason']}\n**โฐ ๐– ๐–ฅ๐–ช ๐–ฅ๐—‹๐—ˆ๐—†:** `{afk_time}`" + + if afk_data["media_type"] == "animation": + media = await client.get_messages(Config.LOGGER_ID, afk_data["media"]) + sent = await client.send_animation( + message.chat.id, media.animation.file_id, caption, True + ) + + elif afk_data["media_type"] in ["audio", "photo", "video", "voice"]: + sent = await client.copy_message( + message.chat.id, + Config.LOGGER_ID, + afk_data["media"], + caption, + reply_to_message_id=message.id, + ) + + elif afk_data["media_type"] == "sticker": + media = await client.get_messages(Config.LOGGER_ID, afk_data["media"]) + await client.download_media(media, "afk.png") + sent = await message.reply_photo("afk.png", caption=caption) + os.remove("afk.png") + + else: + sent = await message.reply_text(caption) + + link = message.link if message.chat.type in group_only else "No DM Link" + + await hellbot.check_and_log( + "afk", + f"{message.from_user.mention} mentioned you when you were AFK! \n\n**Link:** {link}", + ) + try: + data = get_from_dict(Config.AFK_CACHE, [afk_data["user_id"], message.chat.id]) + if data: + await client.delete_messages(message.chat.id, data) + add_to_dict(Config.AFK_CACHE, [afk_data["user_id"], message.chat.id], sent.id) + except KeyError: + add_to_dict(Config.AFK_CACHE, [afk_data["user_id"], message.chat.id], sent.id) + + +@custom_handler(filters.outgoing, 2) +async def remove_afk(_, message: Message): + if await db.is_afk(message.from_user.id): + if "afk" in message.text: + return + + data = await db.get_afk(message.from_user.id) + total_afk_time = readable_time(round(time.time() - data["time"])) + + hell = await message.reply_text( + f"๐Ÿซก **๐–ก๐–บ๐–ผ๐—„ ๐—๐—ˆ ๐—๐—‚๐—‹๐—๐—Ž๐–บ๐—… ๐—๐—ˆ๐—‹๐—…๐–ฝ! \n\nโŒš Was away for:** `{total_afk_time}`" + ) + await message.delete() + + await db.rm_afk(message.from_user.id) + await hellbot.check_and_log( + "afk", + f"Returned from AFK! \n\n**Time:** `{total_afk_time}`\n**Link:** {hell.link}", + ) + + +HelpMenu("afk").add( + "afk", + "", + "Set your status as AFK. When someone mentions' you, the bot will tell them you're currently Offline! You can also use a media by replying to it.", + "afk good night!", + "To unset afk you can send a message to any chat and it'll automaticslly get disabled! You can use 'afk' in your message to bypass automatic disabling of afk.", +).info("Away From Keyboard").done() diff --git a/Hellbot/plugins/user/anime.py b/Hellbot/plugins/user/anime.py new file mode 100644 index 0000000000000000000000000000000000000000..34fb52fefc4b5c0720eb332991d8217056a1346b --- /dev/null +++ b/Hellbot/plugins/user/anime.py @@ -0,0 +1,184 @@ +import os + +from pyrogram.errors import ChatSendMediaForbidden +from pyrogram.types import Message + +from Hellbot.core import hellbot +from Hellbot.functions.scraping import ( + get_airing_info, + get_anilist_user_info, + get_anime_info, + get_character_info, + get_filler_info, + get_manga_info, + get_watch_order, +) + +from . import HelpMenu, on_message + + +@on_message("anime", allow_stan=True) +async def anime(_, message: Message): + if len(message.command) < 2: + return await hellbot.delete(message, "Give me an anime name to search!") + + query = await hellbot.input(message) + hell = await hellbot.edit(message, "Searching ...") + caption, photo = await get_anime_info(query) + + try: + await message.reply_photo(photo, caption=caption) + await hell.delete() + except ChatSendMediaForbidden: + await hell.edit(caption, disable_web_page_preview=True) + + if os.path.exists(photo): + os.remove(photo) + + +@on_message("manga", allow_stan=True) +async def manga(_, message: Message): + if len(message.command) < 2: + return await hellbot.delete(message, "Give me a manga name to search!") + + query = await hellbot.input(message) + hell = await hellbot.edit(message, "Searching ...") + caption, photo = await get_manga_info(query) + + try: + await message.reply_photo(photo, caption=caption) + await hell.delete() + except ChatSendMediaForbidden: + await hell.edit(caption, disable_web_page_preview=True) + + if os.path.exists(photo): + os.remove(photo) + + +@on_message("character", allow_stan=True) +async def character(_, message: Message): + if len(message.command) < 2: + return await hellbot.delete(message, "Give me a character name to search!") + + query = await hellbot.input(message) + hell = await hellbot.edit(message, "Searching ...") + caption, photo = await get_character_info(query) + + try: + await message.reply_photo(photo, caption=caption) + await hell.delete() + except ChatSendMediaForbidden: + await hell.edit(caption, disable_web_page_preview=True) + + if os.path.exists(photo): + os.remove(photo) + + +@on_message("airing", allow_stan=True) +async def airing(_, message: Message): + if len(message.command) < 2: + return await hellbot.delete(message, "Give me an anime name to search!") + + query = await hellbot.input(message) + hell = await hellbot.edit(message, "Searching ...") + caption, photo = await get_airing_info(query) + + try: + await message.reply_photo(photo, caption=caption) + await hell.delete() + except ChatSendMediaForbidden: + await hell.edit(caption, disable_web_page_preview=True) + + if os.path.exists(photo): + os.remove(photo) + + +@on_message(["anilistuser", "aniuser"], allow_stan=True) +async def anilist_user(_, message: Message): + if len(message.command) < 2: + return await hellbot.delete(message, "Give me an anilist username to search!") + + query = await hellbot.input(message) + hell = await hellbot.edit(message, "Searching ...") + caption, photo = await get_anilist_user_info(query) + + try: + await message.reply_photo(photo, caption=caption) + await hell.delete() + except ChatSendMediaForbidden: + await hell.edit(caption, disable_web_page_preview=True) + + if os.path.exists(photo): + os.remove(photo) + + +@on_message(["filler", "canon"], allow_stan=True) +async def fillers(_, message: Message): + if len(message.command) < 2: + return await hellbot.delete(message, "Give me an anime name to search!") + + query = await hellbot.input(message) + hell = await hellbot.edit(message, "Searching ...") + + caption = await get_filler_info(query) + if caption == "": + return await hellbot.delete(hell, "No results found!") + + await hell.edit(caption, disable_web_page_preview=True) + + +@on_message("watchorder", allow_stan=True) +async def watch_order(_, message: Message): + if len(message.command) < 2: + return await hellbot.delete(message, "Give me an anime name to search!") + + query = await hellbot.input(message) + hell = await hellbot.edit(message, "Searching ...") + + caption = await get_watch_order(query) + if caption == "": + return await hellbot.delete(hell, "No results found!") + + await hell.edit(caption, disable_web_page_preview=True) + + +HelpMenu("anime").add( + "anime", + "", + "Get a detailed information about the mentioned anime.", + "anime one piece", +).add( + "manga", + "", + "Get a detailed information about the mentioned manga.", + "manga one piece", +).add( + "character", + "", + "Get a detailed information about the mentioned character.", + "character monkey d luffy", +).add( + "airing", + "", + "Get a detailed airing information about the mentioned anime.", + "airing one piece", +).add( + "anilistuser", + "", + "Get a detailed information about the mentioned anilist user.", + "anilistuser meizhellboy", + "You can also use 'aniuser' as alias", +).add( + "filler", + "", + "Get the list of filler/canon episodes about the mentioned anime.", + "filler one piece", + "You can also use 'canon' as alias", +).add( + "watchorder", + "", + "Get the watch order about the mentioned anime.", + "watchorder one piece", +).info( + "Anime Menu" +).done() diff --git a/Hellbot/plugins/user/antiflood.py b/Hellbot/plugins/user/antiflood.py new file mode 100644 index 0000000000000000000000000000000000000000..bf790aa4d52d72606327dd6ff3b4a81010aeaa4d --- /dev/null +++ b/Hellbot/plugins/user/antiflood.py @@ -0,0 +1,200 @@ +import asyncio +import datetime +import time + +from pyrogram import Client, filters +from pyrogram.types import ChatPermissions, Message + +from Hellbot.core import Config, Symbols, db +from Hellbot.functions.utility import Flood + +from . import HelpMenu, custom_handler, group_only, hellbot, on_message + + +@on_message("setflood", chat_type=group_only, admin_only=True, allow_stan=True) +async def setflood(client: Client, message: Message): + count = 5 + mtime = 0 + mode = "mute" + + try: + time_data = "N/A" + if len(message.command) == 2: + count = int(message.command[1]) + elif len(message.command) == 3: + count = int(message.command[1]) + mode = message.command[2] + elif len(message.command) >= 4: + count = int(message.command[1]) + mode = message.command[2] + time_data = message.command[3] + if time_data.endswith(("d", "day", "days")): + mtime = int(time_data.split("d")[0].strip()) * 24 * 60 * 60 + elif time_data.endswith(("h", "hrs", "hour", "hours")): + mtime = int(time_data.split("h")[0].strip()) * 60 * 60 + elif time_data.endswith(("m", "mins", "minute", "minutes")): + mtime = int(time_data.split("m")[0].strip()) * 60 + else: + return await hellbot.error( + message, + "Please pass time in correct format!\n\nExample: 12d or 12h or 12m", + ) + except Exception as e: + return await hellbot.error(message, str(e)) + + if mode.lower() not in ["mute", "kick", "ban"]: + return await hellbot.error( + message, "**Invalid mode! Choose one: **\n`mute`, `kick`, `ban`" + ) + + settings = { + "mode": mode, + "limit": count, + "time": mtime, + } + + await db.set_flood((client.me.id, message.chat.id), settings) + Flood.updateSettings(client.me.id, message.chat.id, settings) + + if count == 0: + return await hellbot.delete(message, "Antiflood disabled!") + + await hellbot.delete( + message, + f"**Antiflood enabled!**\n\n**{Symbols.triangle_right} Mode:** `{mode}`\n**{Symbols.triangle_right} Limit:** `{count}`\n**{Symbols.triangle_right} Time:** `{time_data}`", + 20, + ) + + +@custom_handler( + filters.all + & filters.group + & filters.incoming + & ~filters.bot + & ~Config.AUTH_USERS + & ~filters.me + & ~filters.service +) +async def antiflood(client: Client, message: Message): + mode, mtime, limit = Flood.getSettings(client.me.id, message.chat.id) + + if limit == 0: + return + if not Flood.check_client_chat(client.me.id, message.chat.id): + return + + last_user, count = Flood.getLastUser(client.me.id, message.chat.id) + + if last_user == message.from_user.id: + if (count + 1) >= limit: + template = ( + "**๐Ÿคซ ๐– ๐—‡๐—๐—‚๐–ฅ๐—…๐—ˆ๐—ˆ๐–ฝ {mode}!!** \n\n" + "**{symbol} ๐–ด๐—Œ๐–พ๐—‹:** `{mention}`\n" + "**{symbol} ๐–ณ๐—‚๐—…๐—… ๐–ฃ๐–บ๐—๐–พ:** `๐Ÿ—“๏ธ {till_date}`\n" + ) + hell = await message.reply_text("Flood Detected!") + + if mode == "mute": + permission = ChatPermissions(can_send_messages=False) + until_date = datetime.datetime.fromtimestamp(time.time() + mtime) + try: + await client.restrict_chat_member( + message.chat.id, + message.from_user.id, + permission, + until_date, + ) + except Exception as e: + return await hellbot.error( + hell, f"__Error in Antiflood while trying to mute!__\n{str(e)}" + ) + + Flood.updateFlood( + client.me.id, message.chat.id, message.from_user.id, 0 + ) + till_date = "Forever" if mtime == 0 else until_date.ctime() + + return await hell.edit( + template.format( + mode=mode.title(), + symbol=Symbols.triangle_right, + mention=message.from_user.mention, + till_date=till_date, + ) + ) + + elif mode == "kick": + try: + await client.ban_chat_member(message.chat.id, message.from_user.id) + except Exception as e: + return await hellbot.error( + hell, f"__Error in Antiflood while trying to kick!__\n{str(e)}" + ) + + await hell.edit( + template.format( + mode=mode.title(), + symbol=Symbols.triangle_right, + mention=message.from_user.mention, + till_date="Kicked Users can join back after 5 seconds!", + ) + ) + Flood.updateFlood( + client.me.id, message.chat.id, message.from_user.id, 0 + ) + await asyncio.sleep(5) + await client.unban_chat_member(message.chat.id, message.from_user.id) + return + + elif mode == "ban": + until_date = datetime.datetime.fromtimestamp(time.time() + mtime) + try: + await client.ban_chat_member( + message.chat.id, + message.from_user.id, + until_date, + ) + except Exception as e: + return await hellbot.error( + hell, f"__Error in Antiflood while trying to ban!__\n{str(e)}" + ) + + Flood.updateFlood( + client.me.id, message.chat.id, message.from_user.id, 0 + ) + till_date = "Forever" if mtime == 0 else until_date.ctime() + + return await hell.edit( + template.format( + mode=mode.title(), + symbol=Symbols.triangle_right, + mention=message.from_user.mention, + till_date=till_date, + ) + ) + else: + return + else: + count += 1 + Flood.updateFlood( + client.me.id, message.chat.id, message.from_user.id, count + ) + return + else: + Flood.updateFlood(client.me.id, message.chat.id, message.from_user.id, 1) + + +HelpMenu("antiflood").add( + "setflood", + "