DiscordLiz 6bdc9606f6 [Core] Multiple mod admin roles (#2783)
* Adds Schema versioning
  - Adds Migration tool
  - Adds tool to migrate to allow multiple admin and mod roles
  - Supports Multiple mod and admin roles

* Ensures migration is run prior to cog load and connection to discord

* Updates to not rely on singular mod/admin role id

* Update requires logic for multiple mod/admin roles

* Add new commands for managing mod/admin roles

* Feedback

Update strings
Update docstrings
Add aliases

* Use snowflakelist

* paginate

* Change variable name

* Fix mistake

* handle settings view fix

* Fix name error

* I'm bad at Ux

* style fix
2019-06-23 23:36:00 -04:00

255 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
from datetime import timedelta
from typing import List, Iterable, Union, TYPE_CHECKING, Dict
import discord
if TYPE_CHECKING:
from .. import Config
from ..bot import Red
from ..commands import Context
async def mass_purge(messages: List[discord.Message], channel: discord.TextChannel):
"""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
The channel to delete messages from.
Raises
------
discord.Forbidden
You do not have proper permissions to delete the messages or youre not
using a bot account.
discord.HTTPException
Deleting the messages failed.
"""
while messages:
if len(messages) > 1:
await channel.delete_messages(messages[:100])
messages = messages[100:]
else:
await messages[0].delete()
messages = []
await asyncio.sleep(1.5)
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
def get_audit_reason(author: discord.Member, reason: str = None):
"""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.
Returns
-------
str
The formatted audit log reason.
"""
return (
"Action requested by {} (ID {}). Reason: {}".format(author, author.id, reason)
if reason
else "Action requested by {} (ID {}).".format(author, author.id)
)
async def is_allowed_by_hierarchy(
bot: "Red", settings: "Config", guild: discord.Guild, mod: discord.Member, user: discord.Member
):
if not await settings.guild(guild).respect_hierarchy():
return True
is_special = mod == guild.owner or await bot.is_owner(mod)
return mod.top_role.position > user.top_role.position or is_special
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):
if obj.id in await bot.db.guild(obj.guild).mod_role():
return True
if obj.id in await bot.db.guild(obj.guild).admin_role():
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
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)
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.db.guild(obj.guild).admin_role()
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
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 invokation 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.channel.permissions_for(ctx.author)
return resolved.administrator or all(
getattr(resolved, name, None) == value for name, value in perms.items()
)