Source code for redbot.core.utils.mod

import asyncio
from datetime import timedelta
from typing import List, Iterable, Union, TYPE_CHECKING, Dict, Optional

import discord

if TYPE_CHECKING:
    from ..bot import Red
    from ..commands import Context

__all__ = (
    "mass_purge",
    "slow_deletion",
    "get_audit_reason",
    "is_mod_or_superior",
    "strfdelta",
    "is_admin_or_superior",
    "check_permissions",
)


[docs]async def mass_purge( messages: List[discord.Message], channel: Union[ discord.TextChannel, discord.VoiceChannel, discord.StageChannel, discord.Thread ], *, reason: Optional[str] = None, ): """Bulk delete messages from a channel. If more than 100 messages are supplied, the bot will delete 100 messages at a time, sleeping between each action. Note ---- Messages must not be older than 14 days, and the bot must not be a user account. Parameters ---------- messages : `list` of `discord.Message` The messages to bulk delete. channel : `discord.TextChannel`, `discord.VoiceChannel`, `discord.StageChannel`, or `discord.Thread` The channel to delete messages from. reason : `str`, optional The reason for bulk deletion, which will appear in the audit log. Raises ------ discord.Forbidden You do not have proper permissions to delete the messages or you’re not using a bot account. discord.HTTPException Deleting the messages failed. """ while messages: # discord.NotFound can be raised when `len(messages) == 1` and the message does not exist. # As a result of this obscure behavior, this error needs to be caught just in case. try: await channel.delete_messages(messages[:100], reason=reason) except discord.errors.HTTPException: pass messages = messages[100:] await asyncio.sleep(1.5)
[docs]async def slow_deletion(messages: Iterable[discord.Message]): """Delete a list of messages one at a time. Any exceptions raised when trying to delete the message will be silenced. Parameters ---------- messages : `iterable` of `discord.Message` The messages to delete. """ for message in messages: try: await message.delete() except discord.HTTPException: pass
[docs]def get_audit_reason(author: discord.Member, reason: str = None, *, shorten: bool = False): """Construct a reason to appear in the audit log. Parameters ---------- author : discord.Member The author behind the audit log action. reason : str The reason behind the audit log action. shorten : bool When set to ``True``, the returned audit reason string will be shortened to fit the max length allowed by Discord audit logs. Returns ------- str The formatted audit log reason. """ audit_reason = ( "Action requested by {} (ID {}). Reason: {}".format(author, author.id, reason) if reason else "Action requested by {} (ID {}).".format(author, author.id) ) if shorten and len(audit_reason) > 512: audit_reason = f"{audit_reason[:509]}..." return audit_reason
[docs]async def is_mod_or_superior( bot: "Red", obj: Union[discord.Message, discord.Member, discord.Role] ): """Check if an object has mod or superior permissions. If a message is passed, its author's permissions are checked. If a role is passed, it simply checks if it is one of either the admin or mod roles. Parameters ---------- bot : redbot.core.bot.Red The bot object. obj : `discord.Message` or `discord.Member` or `discord.Role` The object to check permissions for. Returns ------- bool :code:`True` if the object has mod permissions. Raises ------ TypeError If the wrong type of ``obj`` was passed. """ if isinstance(obj, discord.Message): user = obj.author elif isinstance(obj, discord.Member): user = obj elif isinstance(obj, discord.Role): gid = obj.guild.id if obj in await bot.get_admin_role_ids(gid): return True if obj in await bot.get_mod_role_ids(gid): return True return False else: raise TypeError("Only messages, members or roles may be passed") if await bot.is_owner(user): return True if await bot.is_mod(user): return True return False
[docs]def strfdelta(delta: timedelta): """Format a timedelta object to a message with time units. Parameters ---------- delta : datetime.timedelta The duration to parse. Returns ------- str A message representing the timedelta with units. """ s = [] if delta.days: ds = "%i day" % delta.days if delta.days > 1: ds += "s" s.append(ds) hrs, rem = divmod(delta.seconds, 60 * 60) if hrs: hs = "%i hr" % hrs if hrs > 1: hs += "s" s.append(hs) mins, secs = divmod(rem, 60) if mins: s.append("%i min" % mins) if secs: s.append("%i sec" % secs) return " ".join(s)
[docs]async def is_admin_or_superior( bot: "Red", obj: Union[discord.Message, discord.Member, discord.Role] ): """Same as `is_mod_or_superior` except for admin permissions. If a message is passed, its author's permissions are checked. If a role is passed, it simply checks if it is the admin role. Parameters ---------- bot : redbot.core.bot.Red The bot object. obj : `discord.Message` or `discord.Member` or `discord.Role` The object to check permissions for. Returns ------- bool :code:`True` if the object has admin permissions. Raises ------ TypeError If the wrong type of ``obj`` was passed. """ if isinstance(obj, discord.Message): user = obj.author elif isinstance(obj, discord.Member): user = obj elif isinstance(obj, discord.Role): return obj.id in await bot.get_admin_role_ids(obj.guild.id) else: raise TypeError("Only messages, members or roles may be passed") if await bot.is_owner(user): return True if await bot.is_admin(user): return True return False
[docs]async def check_permissions(ctx: "Context", perms: Dict[str, bool]) -> bool: """Check if the author has required permissions. This will always return ``True`` if the author is a bot owner, or has the ``administrator`` permission. If ``perms`` is empty, this will only check if the user is a bot owner. Parameters ---------- ctx : Context The command invocation context to check. perms : Dict[str, bool] A dictionary mapping permissions to their required states. Valid permission names are those listed as properties of the `discord.Permissions` class. Returns ------- bool ``True`` if the author has the required permissions. """ if await ctx.bot.is_owner(ctx.author): return True elif not perms: return False resolved = ctx.permissions return resolved.administrator or all( getattr(resolved, name, None) == value for name, value in perms.items() )