async def attempt_unbanish(ctx: Context, coginfo: CogInfo, template_engine: TemplateEngine) -> str: """Undo one or more banishes.""" if coginfo.bot: bot: MrFreeze = coginfo.bot mentions = ctx.message.mentions victims = [ u for u in mentions if not u.guild_permissions.administrator and u != bot.user ] success_list: List[Member] = list() fails_list: List[Member] = list() success_string = "" fails_string = "" error_string = "" http_exception = False forbidden_exception = False other_exception = False for victim in victims: error = await mute_db.carry_out_unbanish(bot, victim, logger) if isinstance(error, Exception): fails_list.append(victim) if isinstance(error, discord.HTTPException): http_exception = True elif isinstance(error, discord.Forbidden): forbidden_exception = True else: other_exception = True else: success_list.append(victim) success_string = default.mentions_list(success_list) fails_string = default.mentions_list(fails_list) error_string = get_error_string(http_exception, forbidden_exception, other_exception) template = get_mute_response_type(success_list, fails_list, undo=True) logger.debug(f"attempt_banish(): Setting template to {template}") response_template = template_engine.get_template(ctx.invoked_with, template) if response_template: response = response_template.substitute( author=ctx.author.mention, victims=success_string, fails=fails_string, errors=error_string, ) return f"{ctx.author.mention} {response}" else: return f"{ctx.author.mention} Something went wrong, I'm literally at a loss for words."
async def _kick(self, ctx: Context, *args: str) -> None: """Force a user to leave the server temporarily.""" success_list = list() fail_list = list() mods_list = list() mentions = ctx.message.mentions forbidden_error = False http_error = False reason = self.extract_reason(" ".join(args)) verb = ctx.invoked_with # If they tried to kick a mod christmas is cancelled. mods_list = [ user for user in mentions if user.guild_permissions.administrator ] ment_mods = default.mentions_list(mods_list) tried_to_kick_mod = False if len(mods_list) > 0: tried_to_kick_mod = True # Start the kicking. if len(mentions) > 0 and not tried_to_kick_mod: for victim in mentions: try: if reason is None: await ctx.guild.kick(victim) status = f"{WHITE_B}{victim.name}#{victim.discriminator}{CYAN} was " status += f"{RED_B}{verb}ed from {ctx.guild.name} {CYAN}by {GREEN_B}" status += f"{ctx.author.name}#{ctx.author.discriminator}" else: await ctx.guild.kick(victim, reason=reason) status = f"{WHITE_B}{victim.name}#{victim.discriminator}{CYAN} was " status += f"{RED_B}{verb}ed from {ctx.guild.name} {CYAN}by {GREEN_B}" status += f"{ctx.author.name}#{ctx.author.discriminator}{CYAN}." status += f"\n{WHITE_B}Reason given: {WHITE}{reason}{RESET}" success_list.append(victim) self.logger.info(status) except discord.Forbidden: fail_list.append(victim) forbidden_error = True status = f"{RED_B}ERROR {CYAN}I was not allowed to {RED_B}!{verb} " status += f"{WHITE_B}{victim.name}#{victim.discriminator}" status += f"{CYAN} in {RED_B}{ctx.guild.name}{CYAN}.{RESET}" self.logger.info(status) except discord.HTTPException: fail_list.append(victim) http_error = True status = f"{RED_B}ERROR {CYAN}I couldn't {RED_B}!{verb} {WHITE_B}" status += f"{victim.name}#{victim.discriminator}{CYAN} in {RED_B}" status += f"{ctx.guild.name} {CYAN}due to an HTTP Exception.{RESET}" self.logger.info(status) # This will convert the lists into mentions suitable for text display: # user1, user2 and user 3 ment_success = default.mentions_list(success_list) ment_fail = default.mentions_list(fail_list) # Preparation of replystrings. # Errors are added further down. # Had at least one success and no fails. if (len(success_list) > 0) and (len(fail_list) == 0): # Singular if len(success_list) == 1: replystr = f"{ctx.author.mention} The smud who goes by the name of {ment_success} " replystr += f"has been {verb}ed from the server, never to be seen again!" # Plural else: replystr = f"{ctx.author.mention} The smuds who go by the names of {ment_success} " replystr += f"have been {verb}ed from the server, never to be seen again!" # Had no successes and at least one fail. elif (len(success_list) == 0) and (len(fail_list) > 0): # Singular if len(fail_list) == 1: replystr = f"{ctx.author.mention} So... it seems I wasn't able to {verb} " replystr += f"{ment_fail} due to: " # Plural else: replystr = f"{ctx.author.mention} So... it seems I wasn't able to " replystr += f"{verb} any of {ment_fail}.\nThis was due to: " # Had at least one success and at least one fail. elif (len(success_list) > 0) and (len(fail_list) > 0): # Singular and plural don't matter here. replystr = f"{ctx.author.mention} The request was executed with mixed results." replystr += f"\n{verb.capitalize()}ed: {ment_success}\n" replystr += f"Not {verb.capitalize()}ed: {ment_fail}\nThis was due to: " # Had no mentions whatsoever. elif len(mentions) == 0: # Singular and plural don't matter here. replystr = f"{ctx.author.mention} You forgot to mention anyone " replystr += f"you doofus. Who exactly am I meant to {verb}??" # Now we're adding in the error codes if there are any. if forbidden_error and http_error: replystr += "Insufficient privilegies and HTTP exception." elif not forbidden_error and http_error: replystr += "HTTP exception." elif forbidden_error and not http_error: replystr += "Insufficient privilegies." # Finally, a special message to people who tried to kick a mod. if tried_to_kick_mod: if (len(mods_list) == 1) and ctx.author in mods_list: replystr = f"{ctx.author.mention} You can't {verb} yourself, silly." elif (len(mods_list)) == 1: replystr = f"{ctx.author.mention} Unfortunately you can't {verb} " replystr += f"{ment_mods}, because they're a mod." else: replystr = f"{ctx.author.mention} Unfortunately you can't {verb} " replystr += f"{ment_mods}, because they're mods." await ctx.send(replystr)
async def _unban(self, ctx: Context, *args: str) -> None: """Unpermanent removal from sight of a previously banned user.""" forbidden_error = False http_error = False banlist = list() # This is a shortcut to invoke the banlist command with !unban list. if args == ("list", ): showbans = True else: showbans = False try: banlist = await ctx.guild.bans() except discord.Forbidden: forbidden_error = True except discord.HTTPException: http_error = True # We will assume that all args that are digits are ids # and all args of the form characters#fourdigits are # user names. usr_names = re.findall(r"(?<=\s)\S+#\d{4}(?=\s)", (" " + ctx.message.content + " ")) usr_ids = re.findall(r"(?<=\s)\d+(?=\s)", (" " + ctx.message.content + " ")) success_list = list() fail_list = list() found_anyone = False for ban_entry in banlist: user = ban_entry.user user_str = f"{user.name}#{user.discriminator}" # Below is an easy and expandable way to add criterias for unban. # Every if-statement corresponds to one criteria. # # For example we could easily add this as an extra criteria # if we wanted to. This would match any username, not requiring # the #identifier if user_str in usr_names: entry_hit = True elif (str(user.id) in usr_ids): entry_hit = True else: entry_hit = False # If any of the above resulted in a hit we'll try to remove the ban if entry_hit: found_anyone = True try: await ctx.guild.unban(user) success_list.append(user) except discord.Forbidden: forbidden_error = True fail_list.append(user) except discord.HTTPException: http_error = True fail_list.append(user) any_error = False if forbidden_error or http_error: any_error = True if forbidden_error and http_error: error_str = 'a mix of insufficient privilegies and HTTP issues' elif forbidden_error: error_str = 'insufficient privilegies' elif http_error: error_str = 'HTTP issues' else: error_str = 'unknown error' # Now all we need is a reply string. ment_success = default.mentions_list(success_list) ment_fail = default.mentions_list(fail_list) if showbans: # This is just a shortcut to invoke the listban command. await ctx.invoke(self.bot.get_command('listban')) elif not found_anyone and not any_error: # No banned users were found in the message. replystr = f"{ctx.author.mention} I wasn't able to spot any banned usernames " replystr += "or IDs in that message of yours." elif any_error and len(fail_list) == 0: # Encountered an error during listing, # no unban attempts have been made. replystr = f"{ctx.author.mention} Due to {error_str} I wasn't able to " replystr += "retrieve the list of banned users. Without that list I can't " replystr += "even try to unban them." elif len(success_list) == 1 and len(fail_list) == 0: # Singular success, no fails. replystr = f"{ctx.author.mention} Smuddy McSmudface, I mean {ment_success}, " replystr += "has been unbanned... for some reason. :shrug:" elif len(success_list) > 1 and len(fail_list) == 0: # Plural success, no fails. replystr = f"{ctx.author.mention} The users known as {ment_success} " replystr += "have been unbanned... but why?" elif len(success_list) > 0 and len(fail_list) > 0: # Mixed results. replystr = f"{ctx.author.mention} The unbanning was a success! Partially anyway...\n" replystr += f"Unbanned user(s): {ment_success}\n" replystr += f"Still banned user(s): {ment_fail}\n" replystr += f"Failure was caused by {error_str}." elif len(success_list) == 0 and len(fail_list) == 1: # No success, singular fail. replystr = f"{ctx.author.mention} I wasn't able to unban " replystr += f"{ment_fail} due to {error_str}." elif len(success_list) == 0 and len(fail_list) > 1: # No success, plural fails. ment_fail = ment_fail.replace(' and ', ' or ') replystr = f"{ctx.author.mention} I wasn't able to unban " replystr += f"{ment_fail} due to {error_str}." else: replystr = f"{ctx.author.mention} Quick someone call <@!154516898434908160>! " replystr += "I don't know what's going on!!!\n" if not showbans: await ctx.send(replystr)
async def _ban(self, ctx: Context, *args: str) -> None: """Remove a user from our sight - permanently.""" # This function simply bans a user from the server in which it's issued reason = self.extract_reason(" ".join(args)) forbidden_error = False http_error = False success_list = list() fail_list = list() if "list" in args: # This is just a shortcut to invoke the listban command. banlist = True else: banlist = False if ctx.invoked_with == "ban": do_purge = False else: do_purge = True mod_role = discord.utils.get(ctx.guild.roles, name="Administration") mods_list = [ user for user in ctx.message.mentions if mod_role in user.roles ] for victim in [ user for user in ctx.message.mentions if user not in mods_list ]: try: if not do_purge: await ctx.guild.ban(victim, reason=reason, delete_message_days=0) else: await ctx.guild.ban(victim, reason=reason, delete_message_days=7) success_list.append(victim) except discord.Forbidden: forbidden_error = True fail_list.append(victim) except discord.HTTPException: http_error = True fail_list.append(victim) # And now we compile a response. ment_success = default.mentions_list(success_list) ment_fail = default.mentions_list(fail_list) # Error list: if forbidden_error and not http_error: error_str = 'Insufficient privilegies.' elif not forbidden_error and http_error: error_str = 'HTTP issues.' else: error_str = 'A mix of insufficient privilegies and HTTP issues.' if banlist: # This is just a shortcut to invoke the listban command. await ctx.invoke(self.bot.get_command('listban')) elif len(ctx.message.mentions) == 0: # No mentions replystr = "Sure, I'll go right ahead and ban... wait who should I ban? " replystr += f"You didn't mention anyone? Freeze in hell {ctx.author.mention}!" elif len(ctx.message.mentions) == len(mods_list): # No mentions that weren't mods replystr = f"{ctx.author.mention} Every single person on that list of yours " replystr += "is a mod. This is mutiny!" elif (len(success_list) == 1) and (len(fail_list) == 0): # Singular success replystr = f"{ctx.author.mention} The smuddy little smud {ment_success} won't " replystr += "bother us no more, if you know what I mean... :hammer:" elif (len(success_list) > 1) and (len(fail_list) == 0): # Plural success ban_hammer = (":hammer:" * len(success_list)) replystr = f"{ctx.author.mention} Those smuddy little smuds {ment_success} " replystr += f"won't bother us no more. Because they're all BANNED! {ban_hammer}" elif (len(success_list) > 0) and (len(fail_list) > 0): # Mixed results error_str = error_str.lower().replace('.', '').replace('http', 'HTTP') replystr = f"{ctx.author.mention} My powers are disapating, due to {error_str} " replystr += "I wasn't able to ban all of the users requested." replystr += f"\nBanned: {ment_success}\nNot banned: {ment_fail}" elif (len(success_list) == 0) and (len(fail_list) == 1): # Singular fail error_str = error_str.lower().replace('.', '').replace('http', 'HTTP') replystr = f"{ctx.author.mention} The smuddy little smud {ment_fail}... will " replystr += "actually keep bothering us. I wasn't able to ban them due " replystr += f"to {error_str}." elif (len(success_list) == 0) and (len(fail_list) > 1): # Plural fail ment_fail = ment_fail.replace(' and ', ' or ') replystr = f"{ctx.author.mention} I'm deeply ashamed to say that my systems " replystr += f"are malfunctioning and I wasn't able to ban {ment_fail}.\n" replystr += f"This seems to be due to: {error_str}" if not banlist: await ctx.send(replystr)
async def banish(ctx: Context, coginfo: CogInfo, template_engine: TemplateEngine, args: Tuple[str, ...]) -> None: """Carry out one or more banishes.""" if coginfo.bot: bot: MrFreeze = coginfo.bot else: raise InsufficientCogInfo() # Parse targetted users bot_mentioned = bot.user in ctx.message.mentions self_mentioned = ctx.author in ctx.message.mentions mentions = ctx.message.mentions mods = [ u for u in mentions if u.guild_permissions.administrator and u != bot.user ] users = [ u for u in mentions if not u.guild_permissions.administrator and u != bot.user ] if bot_mentioned or self_mentioned or mods: # Illegal banish, prepare silly response. if bot_mentioned and len(mentions) == 1: logger.debug("Setting template to MuteResponseType.FREEZE") template = MuteResponseType.FREEZE fails_list = [bot.user] elif bot_mentioned and self_mentioned and len(mentions) == 2: logger.debug("Setting template to MuteResponseType.FREEZE_SELF") template = MuteResponseType.FREEZE_SELF fails_list = [bot.user, ctx.author] elif bot_mentioned: logger.debug("Setting template to MuteResponseType.FREEZE_OTHERS") template = MuteResponseType.FREEZE_OTHERS fails_list = mods + users elif self_mentioned and len(mentions) == 1: logger.debug("Setting template to MuteResponseType.SELF") template = MuteResponseType.SELF fails_list = mods elif mods and len(mentions) == 1: logger.debug("Setting template to MuteResponseType.MOD") template = MuteResponseType.MOD fails_list = mods elif mods: logger.debug("Setting template to MuteResponseType.MODS") template = MuteResponseType.MODS fails_list = mods else: logger.warn("Setting template to MuteResponseType.INVALID") logger.warn( f"bot_mentioned={bot_mentioned}, self_mentioned={self_mentioned}" ) logger.warn(f"{len(mentions)} mentions={mentions}") logger.warn(f"{len(mods)} mods={mods}") logger.warn(f"{len(users)} users={users}") template = MuteResponseType.INVALID fails_list = mentions banish_template = template_engine.get_template(ctx.invoked_with, template) mention_fails = default.mentions_list(fails_list) if banish_template: msg = banish_template.substitute(author=ctx.author.mention, fails=mention_fails) await ctx.send(msg) else: # Legal banish, attempt banish. msg = await attempt_banish(ctx, coginfo, template_engine, users, args) await ctx.send(msg)
async def attempt_banish(ctx: Context, coginfo: CogInfo, template_engine: TemplateEngine, victims: List[Member], args: Tuple[str, ...]) -> str: """Attempt to carry banish some people, then return an appropriate response.""" if coginfo.bot: bot: MrFreeze = coginfo.bot success_list: List[Member] = list() fails_list: List[Member] = list() success_string = "" fails_string = "" error_string = "" http_exception = False forbidden_exception = False other_exception = False duration, end_date = get_unbanish_duration(ctx, template_engine, args) for victim in victims: error = await mute_db.carry_out_banish(bot, victim, logger, end_date) if isinstance(error, Exception): fails_list.append(victim) if isinstance(error, discord.HTTPException): http_exception = True elif isinstance(error, discord.Forbidden): forbidden_exception = True else: other_exception = True else: success_list.append(victim) success_string = default.mentions_list(success_list) fails_string = default.mentions_list(fails_list) error_string = get_error_string(http_exception, forbidden_exception, other_exception) template = get_mute_response_type(success_list, fails_list) logger.debug(f"attempt_banish(): Setting template to {template}") timestamp_template = template_engine.get_template( ctx.invoked_with, MuteResponseType.TIMESTAMP) if timestamp_template: timestamp = timestamp_template.substitute( duration=time.parse_timedelta(duration)) else: logger.warn( f"template_engine.get_template({ctx.invoked_with}, TIMESTAMP) returned None!" ) timestamp = "" response_template = template_engine.get_template(ctx.invoked_with, template) if response_template: response = response_template.substitute(author=ctx.author.mention, victims=success_string, fails=fails_string, errors=error_string, timestamp=timestamp) return f"{ctx.author.mention} {response}" else: return f"{ctx.author.mention} Something went wrong, I'm literally at a loss for words."
async def run_command(ctx: Context, coginfo: CogInfo, template_engine: TemplateEngine, error: Exception) -> None: """ Trigger on unauthorized banish, i.e. when a non-administrator try to banish people. When _banish() encounters an error this method is automatically triggered. If the error is an instance of discord.ext.commands.CheckFailure the user will be punished accordingly, if not the error is raised again. There are four relevant templates that can be used when sending the response. USER_NONE User invoked mute with no arguments USER_SELF User tried muting themselves USER_USER User tried muting other user(s) USER_MIXED User tried musing themselves and other user(s) """ if coginfo.bot and coginfo.default_self_mute_time and coginfo.logger: bot: MrFreeze = coginfo.bot default_self_mute_time: int = coginfo.default_self_mute_time logger: Logger = coginfo.logger else: raise InsufficientCogInfo if not isinstance(error, CheckFailure): # Only run this on Check Failure. return mentions = ctx.message.mentions author = ctx.author server = ctx.guild none = (len(mentions) == 0) selfmute = (len(mentions) == 1 and author in mentions) mix = (not selfmute and author in mentions) user = (not selfmute and not mix and len(mentions) > 0) fails = default.mentions_list( [mention for mention in mentions if mention != author]) if none: template = MuteResponseType.USER_NONE elif selfmute: template = MuteResponseType.USER_SELF elif user: template = MuteResponseType.USER_USER elif mix: template = MuteResponseType.USER_MIXED self_mute_time: int = bot.get_self_mute_time( server) or default_self_mute_time duration = datetime.timedelta(minutes=float(self_mute_time)) end_date = datetime.datetime.now() + duration duration = time.parse_timedelta(duration) # Carry out the banish with resulting end date banish_error = await mute_db.carry_out_banish(bot, author, logger, end_date) error_msg = "unspecified error" if isinstance(banish_error, Exception): if isinstance(banish_error, discord.Forbidden): error_msg = "**a lack of privilegies**" elif isinstance(banish_error, discord.HTTPException): error_msg = "**an HTTP exception**" else: error_msg = "**an unknown error**" template = MuteResponseType.USER_FAIL banish_template = template_engine.get_template(ctx.invoked_with, template) if banish_template: reply = banish_template.substitute(author=author.mention, fails=fails, errors=error_msg, timestamp=duration) await ctx.send(reply) else: reply = "I couldn't find an appropriate response, but anyway... you're not " reply += f"allowed to do that! Bad {ctx.author.mention}!" await ctx.send(reply)
async def unbanish_loop(server: Guild, coginfo: CogInfo) -> None: """Check for people to unbanish every self.banish_interval seconds.""" if coginfo.bot and coginfo.logger and coginfo.default_mute_interval: bot: MrFreeze = coginfo.bot logger: Logger = coginfo.logger default_mute_interval: int = coginfo.default_mute_interval else: raise Exception( "Failed to create unbanish loop, insufficient cog info.") while not bot.is_closed(): # Wait the time specified by the server (or the default time) before running interval = bot.settings.get_mute_interval( server) or default_mute_interval logger.debug(f"Running unbanish loop for {server.name} in {interval}.") await asyncio.sleep(interval) logger.debug(f"Running unbanish loop for {server.name}.") # Fetch mute role/channel, which might fail. try: mute_role = await bot.get_mute_role(server) mute_channel = await bot.get_mute_channel(server, silent=True) except Exception: logger.warning( f"{server.name} Unbanish loop failed to fetch mute role or channel." ) continue unmuted = list() # Create a list of mutes which are due for unmuting current_time = datetime.datetime.now() mutes = mute_db.mdb_fetch(bot, server) timed_mutes = [i for i in mutes if i.until is not None] due_mutes = [i for i in timed_mutes if i.until < current_time] logger.debug( f"{server.name} mutes: {len(mutes)}/{len(timed_mutes)}/{len(due_mutes)}" ) for mute in due_mutes: logger.debug(f"{mute} is due for unbanish!") # Refresh member to make sure we have their latest roles. try: member = await server.fetch_member(mute.member.id) logger.debug( f"Refreshed {member}, they have {len(member.roles)} roles." ) except Exception as e: logger.error(f"Failed to refresh muted member: {e}") continue # Will try again next unbanish loop # Calculate how late we were in unbanishing diff = bot.parse_timedelta(current_time - mute.until) if diff == "": diff = "now" else: diff = f"{diff} ago" # Remove from database mute_db.mdb_del(bot, member, logger) if mute_role in member.roles: logger.debug(f"{member} has the mute role! Removing it.") try: await member.remove_roles(mute_role) logger.debug( f"{member} should no longer have the mute role.") # Members are only considered unmuted if they had the antarctica role unmuted.append(member) log = f"Auto-unmuted {CYAN_B}{member.name}#" log += f"{member.discriminator} @ {server.name}." log += f"{YELLOW} (due {diff}){RESET}" logger.info(log) except Exception as e: log = f"Failed to remove mute role of {YELLOW}" log += f"{member.name}#{member.discriminator}" log += f"{CYAN_B} @ {MAGENTA} {server.name}:" log += f"\n{RED}==> {RESET}{e}" logger.error(log) else: log = f"User {YELLOW}{member.name}#{member.discriminator}" log += f"{CYAN_B} @ {MAGENTA} {server.name}{CYAN} " log += f"due for unmute but does not have a mute role!{RESET}" logger.warning(log) # Time for some great regrets if len(unmuted) > 0: unmuted_str = default.mentions_list(unmuted) if len(unmuted_str) == 1: msg = "It's with great regret that I must inform you all that " msg += f"{unmuted_str}'s exile has come to an end." else: msg = "It's with great regret that I must inform you all that the exile of " msg += f"{unmuted_str} has come to an end." await mute_channel.send(msg)