# <============================================== IMPORTS =========================================================> from functools import wraps from threading import RLock from time import perf_counter from cachetools import TTLCache from telegram import Chat, ChatMember, ChatMemberAdministrator, ChatMemberOwner, Update from telegram.constants import ChatMemberStatus, ChatType from telegram.error import Forbidden from telegram.ext import ContextTypes from Mikobot import DEL_CMDS, DEV_USERS, DRAGONS, SUPPORT_CHAT, dispatcher # <=======================================================================================================> # stores admemes in memory for 10 min. ADMIN_CACHE = TTLCache(maxsize=512, ttl=60 * 10, timer=perf_counter) THREAD_LOCK = RLock() # <================================================ FUNCTION =======================================================> def check_admin( permission: str = None, is_bot: bool = False, is_user: bool = False, is_both: bool = False, only_owner: bool = False, only_sudo: bool = False, only_dev: bool = False, no_reply: object = False, ) -> object: """Check for permission level to perform some operations Args: permission (str, optional): permission type to check. Defaults to None. is_bot (bool, optional): if bot can perform the action. Defaults to False. is_user (bool, optional): if user can perform the action. Defaults to False. is_both (bool, optional): if both user and bot can perform the action. Defaults to False. only_owner (bool, optional): if only owner can perform the action. Defaults to False. only_sudo (bool, optional): if only sudo users can perform the operation. Defaults to False. only_dev (bool, optional): if only dev users can perform the operation. Defaults to False. no_reply (boot, optional): if should not reply. Defaults to False. """ def wrapper(func): @wraps(func) async def wrapped( update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs ): nonlocal permission chat = update.effective_chat user = update.effective_user message = update.effective_message if chat.type == ChatType.PRIVATE and not ( only_dev or only_sudo or only_owner ): return await func(update, context, *args, **kwargs) bot_member = ( await chat.get_member(context.bot.id) if is_bot or is_both else None ) user_member = await chat.get_member(user.id) if is_user or is_both else None if only_owner: if isinstance(user_member, ChatMemberOwner) or user.id in DEV_USERS: return await func(update, context, *args, **kwargs) else: return await message.reply_text( "Only chat owner can perform this action." ) if only_dev: if user.id in DEV_USERS: return await func(update, context, *args, **kwargs) else: return await update.effective_message.reply_text( "Hey little kid" "\nWho the hell are you to say me what to execute on my server?", ) if only_sudo: if user.id in DRAGONS: return await func(update, context, *args, **kwargs) else: return await update.effective_message.reply_text( "Who the hell are you to say me what to do?", ) if message.from_user.id == 1087968824: return await func(update, context, *args, **kwargs) if permission: no_permission = permission.replace("_", " ").replace("can", "") if is_bot: if ( getattr(bot_member, permission) if isinstance(bot_member, ChatMemberAdministrator) else False ): return await func(update, context, *args, **kwargs) elif no_reply: return else: return await message.reply_text( f"I don't have permission to {no_permission}." ) if is_user: if isinstance(user_member, ChatMemberOwner): return await func(update, context, *args, **kwargs) elif ( getattr(user_member, permission) if isinstance(user_member, ChatMemberAdministrator) else False or user.id in DRAGONS ): return await func(update, context, *args, **kwargs) elif no_reply: return else: return await message.reply_text( f"You don't have permission to {no_permission}." ) if is_both: if ( getattr(bot_member, permission) if isinstance(bot_member, ChatMemberAdministrator) else False ): pass elif no_reply: return else: return await message.reply_text( f"I don't have permission to {no_permission}." ) if isinstance(user_member, ChatMemberOwner) or user.id in DEV_USERS: pass elif ( getattr(user_member, permission) if isinstance(user_member, ChatMemberAdministrator) else False or user.id in DRAGONS ): pass elif no_reply: return else: return await message.reply_text( f"You don't have permission to {no_permission}." ) return await func(update, context, *args, **kwargs) else: if is_bot: if bot_member.status == ChatMemberStatus.ADMINISTRATOR: return await func(update, context, *args, **kwargs) else: return await message.reply_text("I'm not admin here.") elif is_user: if user_member.status in [ ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.OWNER, ]: return await func(update, context, *args, **kwargs) elif user.id in DRAGONS: return await func(update, context, *args, **kwargs) else: return await message.reply_text("You are not admin here.") elif is_both: if bot_member.status == ChatMemberStatus.ADMINISTRATOR: pass else: return await message.reply_text("I'm not admin here.") if user_member.status in [ ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.OWNER, ]: pass elif user.id in DRAGONS: pass else: return await message.reply_text("You are not admin here.") return await func(update, context, *args, **kwargs) return wrapped return wrapper def is_whitelist_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: return any(user_id in user for user in [DRAGONS, DEV_USERS]) def is_support_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: return user_id in DRAGONS or user_id in DEV_USERS def is_sudo_plus(chat: Chat, user_id: int, member: ChatMember = None) -> bool: return user_id in DRAGONS or user_id in DEV_USERS async def is_user_admin(chat: Chat, user_id: int, member: ChatMember = None) -> bool: if ( chat.type == "private" or user_id in DRAGONS or user_id in DEV_USERS or user_id in [777000, 1087968824] ): # Count telegram and Group Anonymous as admin return True if not member: with THREAD_LOCK: # try to fetch from cache first. try: return user_id in ADMIN_CACHE[chat.id] except KeyError: # keyerror happend means cache is deleted, # so query bot api again and return user status # while saving it in cache for future usage... try: chat_admins = await dispatcher.bot.getChatAdministrators(chat.id) except Forbidden: return False admin_list = [x.user.id for x in chat_admins] ADMIN_CACHE[chat.id] = admin_list return user_id in admin_list else: return member.status in (ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.OWNER) async def is_bot_admin(chat: Chat, bot_id: int, bot_member: ChatMember = None) -> bool: if chat.type == "private": return True if not bot_member: bot_member = await chat.get_member(bot_id) return bot_member.status in (ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.OWNER) async def can_delete(chat: Chat, bot_id: int) -> bool: chat_member = await chat.get_member(bot_id) if isinstance(chat_member, ChatMemberAdministrator): return chat_member.can_delete_messages async def is_user_ban_protected( chat: Chat, user_id: int, member: ChatMember = None ) -> bool: if ( chat.type == "private" or user_id in DRAGONS or user_id in DEV_USERS or user_id in [777000, 1087968824] ): # Count telegram and Group Anonymous as admin return True if not member: member = await chat.get_member(user_id) return member.status in (ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.OWNER) async def is_user_in_chat(chat: Chat, user_id: int) -> bool: member = await chat.get_member(user_id) return member.status in (ChatMemberStatus.LEFT, ChatMemberStatus.RESTRICTED) def dev_plus(func): @wraps(func) def is_dev_plus_func( update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs ): context.bot user = update.effective_user if user.id in DEV_USERS: return func(update, context, *args, **kwargs) elif not user: pass elif DEL_CMDS and " " not in update.effective_message.text: try: update.effective_message.delete() except: pass else: update.effective_message.reply_text( "This is a developer restricted command." " You do not have permissions to run this." ) return is_dev_plus_func def sudo_plus(func): @wraps(func) def is_sudo_plus_func( update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs ): context.bot user = update.effective_user chat = update.effective_chat if user and is_sudo_plus(chat, user.id): return func(update, context, *args, **kwargs) elif not user: pass elif DEL_CMDS and " " not in update.effective_message.text: try: update.effective_message.delete() except: pass else: update.effective_message.reply_text( "Who dis non-admin telling me what to do? You want a punch?" ) return is_sudo_plus_func def support_plus(func): @wraps(func) async def is_support_plus_func( update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs ): bot = context.bot user = update.effective_user chat = update.effective_chat if user and is_support_plus(chat, user.id): return await func(update, context, *args, **kwargs) elif DEL_CMDS and " " not in update.effective_message.text: try: await update.effective_message.delete() except: pass return is_support_plus_func def whitelist_plus(func): @wraps(func) async def is_whitelist_plus_func( update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs, ): bot = context.bot user = update.effective_user chat = update.effective_chat if user and is_whitelist_plus(chat, user.id): return await func(update, context, *args, **kwargs) else: await update.effective_message.reply_text( f"You don't have access to use this.\nVisit @{SUPPORT_CHAT}", ) return is_whitelist_plus_func def user_not_admin(func): @wraps(func) async def is_not_admin( update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs ): bot = context.bot user = update.effective_user chat = update.effective_chat if user and not await is_user_admin(chat, user.id): return await func(update, context, *args, **kwargs) elif not user: pass return is_not_admin def connection_status(func): @wraps(func) async def connected_status( update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs ): conn = await connected( context.bot, update, update.effective_chat, update.effective_user.id, need_admin=False, ) if conn: chat = await dispatcher.bot.getChat(conn) update.__setattr__("_effective_chat", chat) return await func(update, context, *args, **kwargs) else: if update.effective_message.chat.type == "private": await update.effective_message.reply_text( "Send /connect in a group that you and I have in common first.", ) return connected_status return await func(update, context, *args, **kwargs) return connected_status # <=======================================================================================================> # <=======================================================================================================> # Workaround for circular import with connection.py from Mikobot.plugins import connection connected = connection.connected # <================================================ END =======================================================>