From 9e3d9f81c80bc47eb61154b4b618b7169484f500 Mon Sep 17 00:00:00 2001 From: AbOuLfOoOoOuF <67458252+AbOuLfOoOoOuF@users.noreply.github.com> Date: Tue, 27 Jul 2021 06:15:40 +0000 Subject: [PATCH] admin: check admin perms before pin-unpin (#33) check whether the admin has perms to pin messages before pinning or unpinnig --- AstrakoBot/modules/admin.py | 545 ++++++++++++++++++++++++++++++++++++ tg_bot/modules/admin.py | 13 + 2 files changed, 558 insertions(+) create mode 100644 AstrakoBot/modules/admin.py diff --git a/AstrakoBot/modules/admin.py b/AstrakoBot/modules/admin.py new file mode 100644 index 000000000..a3493d462 --- /dev/null +++ b/AstrakoBot/modules/admin.py @@ -0,0 +1,545 @@ +import html + +from telegram import ParseMode, Update +from telegram.error import BadRequest +from telegram.ext import CallbackContext, CommandHandler, Filters, run_async +from telegram.utils.helpers import mention_html + +from AstrakoBot import SUDO_USERS, dispatcher +from AstrakoBot.modules.disable import DisableAbleCommandHandler +from AstrakoBot.modules.helper_funcs.chat_status import ( + bot_admin, + can_pin, + can_promote, + connection_status, + user_admin, + ADMIN_CACHE, +) + +from AstrakoBot.modules.helper_funcs.extraction import ( + extract_user, + extract_user_and_text, +) +from AstrakoBot.modules.log_channel import loggable +from AstrakoBot.modules.helper_funcs.alternate import send_message + + +@connection_status +@bot_admin +@can_promote +@user_admin +@loggable +def promote(update: Update, context: CallbackContext) -> str: + bot = context.bot + args = context.args + + message = update.effective_message + chat = update.effective_chat + user = update.effective_user + + promoter = chat.get_member(user.id) + + if ( + not (promoter.can_promote_members or promoter.status == "creator") + and user.id not in SUDO_USERS + ): + message.reply_text("You don't have the necessary rights to do that!") + return + + user_id = extract_user(message, args) + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect.." + ) + return + + try: + user_member = chat.get_member(user_id) + except: + return + + if user_member.status == "administrator" or user_member.status == "creator": + message.reply_text("How am I meant to promote someone that's already an admin?") + return + + if user_id == bot.id: + message.reply_text("I can't promote myself! Get an admin to do it for me.") + return + + # set same perms as bot - bot can't assign higher perms than itself! + bot_member = chat.get_member(bot.id) + + try: + bot.promoteChatMember( + chat.id, + user_id, + can_change_info=bot_member.can_change_info, + can_post_messages=bot_member.can_post_messages, + can_edit_messages=bot_member.can_edit_messages, + can_delete_messages=bot_member.can_delete_messages, + can_invite_users=bot_member.can_invite_users, + # can_promote_members=bot_member.can_promote_members, + can_restrict_members=bot_member.can_restrict_members, + can_pin_messages=bot_member.can_pin_messages, + ) + except BadRequest as err: + if err.message == "User_not_mutual_contact": + message.reply_text("I can't promote someone who isn't in the group.") + else: + message.reply_text("An error occured while promoting.") + return + + bot.sendMessage( + chat.id, + f"Sucessfully promoted {user_member.user.first_name or user_id}!", + parse_mode=ParseMode.HTML, + ) + + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#PROMOTED\n" + f"Admin: {mention_html(user.id, user.first_name)}\n" + f"User: {mention_html(user_member.user.id, user_member.user.first_name)}" + ) + + return log_message + + +@connection_status +@bot_admin +@can_promote +@user_admin +@loggable +def demote(update: Update, context: CallbackContext) -> str: + bot = context.bot + args = context.args + + chat = update.effective_chat + message = update.effective_message + user = update.effective_user + + demoter = chat.get_member(user.id) + + if ( + not (demoter.can_promote_members or demoter.status == "creator") + and user.id not in SUDO_USERS + ): + message.reply_text("You don't have the necessary rights to do that!") + return + + user_id = extract_user(message, args) + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect.." + ) + return + + try: + user_member = chat.get_member(user_id) + except: + return + + if user_member.status == "creator": + message.reply_text("This person CREATED the chat, how would I demote them?") + return + + if not user_member.status == "administrator": + message.reply_text("Can't demote what wasn't promoted!") + return + + if user_id == bot.id: + message.reply_text("I can't demote myself! Get an admin to do it for me.") + return + + try: + bot.promoteChatMember( + chat.id, + user_id, + can_change_info=False, + can_post_messages=False, + can_edit_messages=False, + can_delete_messages=False, + can_invite_users=False, + can_restrict_members=False, + can_pin_messages=False, + can_promote_members=False, + ) + + bot.sendMessage( + chat.id, + f"Sucessfully demoted {user_member.user.first_name or user_id}!", + parse_mode=ParseMode.HTML, + ) + + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#DEMOTED\n" + f"Admin: {mention_html(user.id, user.first_name)}\n" + f"User: {mention_html(user_member.user.id, user_member.user.first_name)}" + ) + + return log_message + except BadRequest: + message.reply_text( + "Could not demote. I might not be admin, or the admin status was appointed by another" + " user, so I can't act upon them!" + ) + return + + +@user_admin +def refresh_admin(update, _): + try: + ADMIN_CACHE.pop(update.effective_chat.id) + except KeyError: + pass + + update.effective_message.reply_text("Admins cache refreshed!") + + +@connection_status +@bot_admin +@can_promote +@user_admin +def set_title(update: Update, context: CallbackContext): + bot = context.bot + args = context.args + + chat = update.effective_chat + message = update.effective_message + + user_id, title = extract_user_and_text(message, args) + try: + user_member = chat.get_member(user_id) + except: + return + + if not user_id: + message.reply_text( + "You don't seem to be referring to a user or the ID specified is incorrect.." + ) + return + + if user_member.status == "creator": + message.reply_text( + "This person CREATED the chat, how can i set custom title for him?" + ) + return + + if user_member.status != "administrator": + message.reply_text( + "Can't set title for non-admins!\nPromote them first to set custom title!" + ) + return + + if user_id == bot.id: + message.reply_text( + "I can't set my own title myself! Get the one who made me admin to do it for me." + ) + return + + if not title: + message.reply_text("Setting blank title doesn't do anything!") + return + + if len(title) > 16: + message.reply_text( + "The title length is longer than 16 characters.\nTruncating it to 16 characters." + ) + + try: + bot.setChatAdministratorCustomTitle(chat.id, user_id, title) + except BadRequest: + message.reply_text("Either they aren't promoted by me or you set a title text that is impossible to set.") + return + + bot.sendMessage( + chat.id, + f"Sucessfully set title for {user_member.user.first_name or user_id} " + f"to {html.escape(title[:16])}!", + parse_mode=ParseMode.HTML, + ) + + +@bot_admin +@can_pin +@user_admin +@loggable +def pin(update: Update, context: CallbackContext) -> str: + bot = context.bot + args = context.args + + user = update.effective_user + chat = update.effective_chat + + is_group = chat.type != "private" and chat.type != "channel" + prev_message = update.effective_message.reply_to_message + + is_silent = True + if len(args) >= 1: + is_silent = not ( + args[0].lower() == "notify" + or args[0].lower() == "loud" + or args[0].lower() == "violent" + ) + + message = update.effective_message + pinner = chat.get_member(user.id) + + if ( + not (pinner.can_pin_messages or pinner.status == "creator") + and user.id not in SUDO_USERS + ): + message.reply_text("You don't have the necessary rights to do that!") + return + + if prev_message and is_group: + try: + bot.pinChatMessage( + chat.id, prev_message.message_id, disable_notification=is_silent + ) + except BadRequest as excp: + if excp.message == "Chat_not_modified": + pass + else: + raise + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#PINNED\n" + f"Admin: {mention_html(user.id, html.escape(user.first_name))}" + ) + + return log_message + + +@bot_admin +@can_pin +@user_admin +@loggable +def unpin(update: Update, context: CallbackContext) -> str: + bot = context.bot + chat = update.effective_chat + user = update.effective_user + message = update.effective_message + unpinner = chat.get_member(user.id) + + if ( + not (unpinner.can_pin_messages or unpinner.status == "creator") + and user.id not in SUDO_USERS + ): + message.reply_text("You don't have the necessary rights to do that!") + return + + try: + bot.unpinChatMessage(chat.id) + except BadRequest as excp: + if excp.message == "Chat_not_modified": + pass + else: + raise + + log_message = ( + f"{html.escape(chat.title)}:\n" + f"#UNPINNED\n" + f"Admin: {mention_html(user.id, html.escape(user.first_name))}" + ) + + return log_message + + +@bot_admin +@user_admin +@connection_status +def invite(update: Update, context: CallbackContext): + bot = context.bot + chat = update.effective_chat + + if chat.username: + update.effective_message.reply_text(f"https://t.me/{chat.username}") + elif chat.type in [chat.SUPERGROUP, chat.CHANNEL]: + bot_member = chat.get_member(bot.id) + if bot_member.can_invite_users: + invitelink = bot.exportChatInviteLink(chat.id) + update.effective_message.reply_text(invitelink) + else: + update.effective_message.reply_text( + "I don't have access to the invite link, try changing my permissions!" + ) + else: + update.effective_message.reply_text( + "I can only give you invite links for supergroups and channels, sorry!" + ) + + +@connection_status +def adminlist(update: Update, context: CallbackContext): + chat = update.effective_chat # type: Optional[Chat] + user = update.effective_user # type: Optional[User] + args = context.args + bot = context.bot + + if update.effective_message.chat.type == "private": + send_message(update.effective_message, "This command only works in Groups.") + return + + chat = update.effective_chat + chat_id = update.effective_chat.id + chat_name = update.effective_message.chat.title + + try: + msg = update.effective_message.reply_text( + "Fetching group admins...", parse_mode=ParseMode.HTML + ) + except BadRequest: + msg = update.effective_message.reply_text( + "Fetching group admins...", quote=False, parse_mode=ParseMode.HTML + ) + + administrators = bot.getChatAdministrators(chat_id) + text = "Admins in {}:".format(html.escape(update.effective_chat.title)) + + bot_admin_list = [] + + for admin in administrators: + user = admin.user + status = admin.status + custom_title = admin.custom_title + + if user.first_name == "": + name = "ā˜  Deleted Account" + else: + name = "{}".format( + mention_html( + user.id, html.escape(user.first_name + " " + (user.last_name or "")) + ) + ) + + if user.is_bot: + bot_admin_list.append(name) + administrators.remove(admin) + continue + + # if user.username: + # name = escape_markdown("@" + user.username) + if status == "creator": + text += "\n šŸ‘‘ Creator:" + text += "\n ā€¢ {}\n".format(name) + + if custom_title: + text += f" ā”—ā” {html.escape(custom_title)}\n" + + text += "\nšŸ”± Admins:" + + custom_admin_list = {} + normal_admin_list = [] + + for admin in administrators: + user = admin.user + status = admin.status + custom_title = admin.custom_title + + if user.first_name == "": + name = "ā˜  Deleted Account" + else: + name = "{}".format( + mention_html( + user.id, html.escape(user.first_name + " " + (user.last_name or "")) + ) + ) + # if user.username: + # name = escape_markdown("@" + user.username) + if status == "administrator": + if custom_title: + try: + custom_admin_list[custom_title].append(name) + except KeyError: + custom_admin_list.update({custom_title: [name]}) + else: + normal_admin_list.append(name) + + for admin in normal_admin_list: + text += "\n ā€¢ {}".format(admin) + + for admin_group in custom_admin_list.copy(): + if len(custom_admin_list[admin_group]) == 1: + text += "\n ā€¢ {} | {}".format( + custom_admin_list[admin_group][0], html.escape(admin_group) + ) + custom_admin_list.pop(admin_group) + + text += "\n" + for admin_group, value in custom_admin_list.items(): + text += "\nšŸšØ {}".format(admin_group) + for admin in value: + text += "\n ā€¢ {}".format(admin) + text += "\n" + + text += "\nšŸ¤– Bots:" + for each_bot in bot_admin_list: + text += "\n ā€¢ {}".format(each_bot) + + try: + msg.edit_text(text, parse_mode=ParseMode.HTML) + except BadRequest: # if original message is deleted + return + + +__help__ = """ + ā€¢ `/admins`*:* list of admins in the chat + +*Admins only:* + ā€¢ `/pin`*:* silently pins the message replied to - add `'loud'` or `'notify'` to give notifs to users + ā€¢ `/unpin`*:* unpins the currently pinned message + ā€¢ `/invitelink`*:* gets invitelink + ā€¢ `/link`*:* same as invitelink + ā€¢ `/promote`*:* promotes the user replied to + ā€¢ `/demote`*:* demotes the user replied to + ā€¢ `/title `*:* sets a custom title for an admin that the bot promoted + ā€¢ `/admincache`*:* force refresh the admins list + ā€¢ `/zombies`*:* scan and clean zombies +""" + +ADMINLIST_HANDLER = DisableAbleCommandHandler("admins", adminlist, run_async=True) + +PIN_HANDLER = CommandHandler("pin", pin, filters=Filters.chat_type.groups, run_async=True) +UNPIN_HANDLER = CommandHandler("unpin", unpin, filters=Filters.chat_type.groups, run_async=True) + +INVITE_HANDLER = DisableAbleCommandHandler(["invitelink", "link"], invite, run_async=True) + +PROMOTE_HANDLER = DisableAbleCommandHandler("promote", promote, run_async=True) +DEMOTE_HANDLER = DisableAbleCommandHandler("demote", demote, run_async=True) + +SET_TITLE_HANDLER = CommandHandler("title", set_title, run_async=True) +ADMIN_REFRESH_HANDLER = CommandHandler( + "admincache", refresh_admin, filters=Filters.chat_type.groups +) + +dispatcher.add_handler(ADMINLIST_HANDLER) +dispatcher.add_handler(PIN_HANDLER) +dispatcher.add_handler(UNPIN_HANDLER) +dispatcher.add_handler(INVITE_HANDLER) +dispatcher.add_handler(PROMOTE_HANDLER) +dispatcher.add_handler(DEMOTE_HANDLER) +dispatcher.add_handler(SET_TITLE_HANDLER) +dispatcher.add_handler(ADMIN_REFRESH_HANDLER) + +__mod_name__ = "Admin" +__command_list__ = [ + "adminlist", + "admins", + "invitelink", + "promote", + "demote", + "admincache", +] +__handlers__ = [ + ADMINLIST_HANDLER, + PIN_HANDLER, + UNPIN_HANDLER, + INVITE_HANDLER, + PROMOTE_HANDLER, + DEMOTE_HANDLER, + SET_TITLE_HANDLER, + ADMIN_REFRESH_HANDLER, +] diff --git a/tg_bot/modules/admin.py b/tg_bot/modules/admin.py index 06e23e403..fec7ae50c 100644 --- a/tg_bot/modules/admin.py +++ b/tg_bot/modules/admin.py @@ -348,6 +348,13 @@ def pin(update: Update, context: CallbackContext) -> str: if len(args) >= 1: is_silent = not (args[0].lower() == 'notify' or args[0].lower() == 'loud' or args[0].lower() == 'violent') + message = update.effective_message + pinner = chat.get_member(user.id) + + if (not (pinner.can_pin_messages or pinner.status == "creator") and user.id not in SUDO_USERS): + message.reply_text("You don't have the necessary rights to do that!") + return + if prev_message and is_group: try: bot.pinChatMessage(chat.id, prev_message.message_id, disable_notification=is_silent) @@ -371,6 +378,12 @@ def unpin(update: Update, context: CallbackContext) -> str: bot = context.bot chat = update.effective_chat user = update.effective_user # type: Optional[User] + message = update.effective_message + unpinner = chat.get_member(user.id) + + if (not (unpinner.can_pin_messages or unpinner.status == "creator") and user.id not in SUDO_USERS): + message.reply_text("You don't have the necessary rights to do that!") + return try: bot.unpinChatMessage(chat.id)