Module aethersprite.authz
Authorization module
Expand source code
"""Authorization module"""
# stdlib
from os import environ
from typing import Sequence
# 3rd party
from discord import DMChannel, Member, Role
from discord.ext.commands import Context
# local
from . import config, log
from .emotes import POLICE_OFFICER
from .settings import settings
owner = config["bot"].get("owner", environ.get("NCFACBOT_OWNER", None))
_help = config["bot"]["help_command"]
async def channel_only(ctx) -> bool:
"""
Check for bot commands that should only operate in a channel.
Args:
ctx: The current context
Returns:
Whether the user is authorized
"""
if isinstance(ctx.channel, DMChannel):
await ctx.send("This command must be used in a channel.")
return False
return True
def is_in_any_role(user: Member, roles: Sequence[Role]) -> bool:
"""
Whether or not a member is in any of the given roles.
Args:
user: The user in question
roles: The roles to check for membership
Returns:
Whether the user is authorized
"""
if len(roles) > 0 and len([r for r in user.roles if r in roles]) > 0:
return True
return False
async def react_if_not_help(ctx: Context):
"""
If the command was not invoked as an alias of the help command, react with
the police officer emoji.
Args:
ctx: The current context
"""
cog = ctx.bot.get_cog("alias")
if cog is None:
log.warn("alias cog not loaded")
return
aliases = cog.get_aliases(ctx, _help)
help_aliases = [_help] + aliases
# only react if they invoked the command directly (i.e. not via !help)
if ctx.invoked_with not in help_aliases:
await ctx.message.add_reaction(POLICE_OFFICER)
log.warn(
f"{ctx.author} attempted to access unauthorized command "
f"{ctx.command}"
)
async def require_admin(ctx: Context) -> bool:
"""
Check for requiring admin/mod privileges to execute a command.
Args:
ctx: The current context
Returns:
Whether the user is authorized
"""
assert isinstance(ctx.author, Member)
perms = ctx.channel.permissions_for(ctx.author)
if (
perms.administrator
or perms.manage_channels
or perms.manage_guild
or owner == str(ctx.author)
):
return True
await react_if_not_help(ctx)
return False
async def require_roles(ctx: Context, roles: Sequence[Role]) -> bool:
"""
Check for requiring particular roles to execute a command. Membership in at
least one of the roles is requied to pass the filter.
Args:
ctx: The current context
roles: The roles to authorize
Returns:
Whether the user is authorized
"""
assert isinstance(ctx.author, Member)
if is_in_any_role(ctx.author, roles):
return True
await react_if_not_help(ctx)
return False
async def require_roles_from_setting(
ctx: Context, setting: str | Sequence, open_by_default=True
) -> bool:
"""
Check for requiring particular roles (loaded from the given setting) to
execute a command. For more than one setting (if `setting` is a
list/tuple), the aggregate list will be used. Membership in at least one of
the roles pulled from the settings is required to pass the filter.
If this check is used and the setting is empty or nonexistent, the default
behavior is to allow anyone and everyone to use the command. If you would
like for it to default to "closed" behavior, set the `open_by_default`
argument to `False`.
Example, if your setting with role(s) is `setting.name`:
```python
from functools import partial
from discord.ext.commands import check, command
from aethersprite.authz require_roles, import require_roles_from_setting
from my_super_secret_special_code import get_roles
authz = partial(require_roles, get_roles())
authz_setting = partial(require_roles_from_setting, setting="setting.name")
@command()
@check(authz)
async def my_command(ctx):
await ctx.send("You are authorized. Congratulations!")
@command()
@check(authz_setting)
async def my_other_command(ctx):
# to set via bot command: !set setting.name SomeRoleName, SomeOtherRole
await ctx.send(
"You are a member of one of the authorized roles. Congratulations!"
)
```
Args:
setting: The name of the setting to pull the roles from
setting: str or list or tuple
Returns:
Whether the user is authorized
"""
assert isinstance(ctx.author, Member)
perms = ctx.channel.permissions_for(ctx.author)
if (
perms.administrator
or perms.manage_channels
or perms.manage_guild
or owner == str(ctx.author)
):
# Superusers get a pass
return True
roles_id = []
if isinstance(setting, str):
roles_id = [int(r) for r in settings[setting].get(ctx, raw=True)]
elif isinstance(setting, Sequence):
for s in setting:
roles_id += [int(r) for r in settings[s].get(ctx, raw=True)]
else:
raise ValueError(setting)
if len(roles_id) == 0:
# no roles set, use default
return open_by_default
for r in ctx.author.roles:
if r.id in roles_id:
return True
await react_if_not_help(ctx)
return False
Functions
async def channel_only(ctx) ‑> bool
-
Check for bot commands that should only operate in a channel.
Args
ctx
- The current context
Returns
Whether the user is authorized
Expand source code
async def channel_only(ctx) -> bool: """ Check for bot commands that should only operate in a channel. Args: ctx: The current context Returns: Whether the user is authorized """ if isinstance(ctx.channel, DMChannel): await ctx.send("This command must be used in a channel.") return False return True
def is_in_any_role(user: discord.member.Member, roles: Sequence[discord.role.Role]) ‑> bool
-
Whether or not a member is in any of the given roles.
Args
user
- The user in question
roles
- The roles to check for membership
Returns
Whether the user is authorized
Expand source code
def is_in_any_role(user: Member, roles: Sequence[Role]) -> bool: """ Whether or not a member is in any of the given roles. Args: user: The user in question roles: The roles to check for membership Returns: Whether the user is authorized """ if len(roles) > 0 and len([r for r in user.roles if r in roles]) > 0: return True return False
async def react_if_not_help(ctx: discord.ext.commands.context.Context)
-
If the command was not invoked as an alias of the help command, react with the police officer emoji.
Args
ctx
- The current context
Expand source code
async def react_if_not_help(ctx: Context): """ If the command was not invoked as an alias of the help command, react with the police officer emoji. Args: ctx: The current context """ cog = ctx.bot.get_cog("alias") if cog is None: log.warn("alias cog not loaded") return aliases = cog.get_aliases(ctx, _help) help_aliases = [_help] + aliases # only react if they invoked the command directly (i.e. not via !help) if ctx.invoked_with not in help_aliases: await ctx.message.add_reaction(POLICE_OFFICER) log.warn( f"{ctx.author} attempted to access unauthorized command " f"{ctx.command}" )
async def require_admin(ctx: discord.ext.commands.context.Context) ‑> bool
-
Check for requiring admin/mod privileges to execute a command.
Args
ctx
- The current context
Returns
Whether the user is authorized
Expand source code
async def require_admin(ctx: Context) -> bool: """ Check for requiring admin/mod privileges to execute a command. Args: ctx: The current context Returns: Whether the user is authorized """ assert isinstance(ctx.author, Member) perms = ctx.channel.permissions_for(ctx.author) if ( perms.administrator or perms.manage_channels or perms.manage_guild or owner == str(ctx.author) ): return True await react_if_not_help(ctx) return False
async def require_roles(ctx: discord.ext.commands.context.Context, roles: Sequence[discord.role.Role]) ‑> bool
-
Check for requiring particular roles to execute a command. Membership in at least one of the roles is requied to pass the filter.
Args
ctx
- The current context
roles
- The roles to authorize
Returns
Whether the user is authorized
Expand source code
async def require_roles(ctx: Context, roles: Sequence[Role]) -> bool: """ Check for requiring particular roles to execute a command. Membership in at least one of the roles is requied to pass the filter. Args: ctx: The current context roles: The roles to authorize Returns: Whether the user is authorized """ assert isinstance(ctx.author, Member) if is_in_any_role(ctx.author, roles): return True await react_if_not_help(ctx) return False
async def require_roles_from_setting(ctx: discord.ext.commands.context.Context, setting: Union[str, Sequence], open_by_default=True) ‑> bool
-
Check for requiring particular roles (loaded from the given setting) to execute a command. For more than one setting (if
setting
is a list/tuple), the aggregate list will be used. Membership in at least one of the roles pulled from the settings is required to pass the filter.If this check is used and the setting is empty or nonexistent, the default behavior is to allow anyone and everyone to use the command. If you would like for it to default to "closed" behavior, set the
open_by_default
argument toFalse
.Example, if your setting with role(s) is
setting.name
:from functools import partial from discord.ext.commands import check, command from aethersprite.authz require_roles, import require_roles_from_setting from my_super_secret_special_code import get_roles authz = partial(require_roles, get_roles()) authz_setting = partial(require_roles_from_setting, setting="setting.name") @command() @check(authz) async def my_command(ctx): await ctx.send("You are authorized. Congratulations!") @command() @check(authz_setting) async def my_other_command(ctx): # to set via bot command: !set setting.name SomeRoleName, SomeOtherRole await ctx.send( "You are a member of one of the authorized roles. Congratulations!" )
Args
setting
- The name of the setting to pull the roles from
setting
- str or list or tuple
Returns
Whether the user is authorized
Expand source code
async def require_roles_from_setting( ctx: Context, setting: str | Sequence, open_by_default=True ) -> bool: """ Check for requiring particular roles (loaded from the given setting) to execute a command. For more than one setting (if `setting` is a list/tuple), the aggregate list will be used. Membership in at least one of the roles pulled from the settings is required to pass the filter. If this check is used and the setting is empty or nonexistent, the default behavior is to allow anyone and everyone to use the command. If you would like for it to default to "closed" behavior, set the `open_by_default` argument to `False`. Example, if your setting with role(s) is `setting.name`: ```python from functools import partial from discord.ext.commands import check, command from aethersprite.authz require_roles, import require_roles_from_setting from my_super_secret_special_code import get_roles authz = partial(require_roles, get_roles()) authz_setting = partial(require_roles_from_setting, setting="setting.name") @command() @check(authz) async def my_command(ctx): await ctx.send("You are authorized. Congratulations!") @command() @check(authz_setting) async def my_other_command(ctx): # to set via bot command: !set setting.name SomeRoleName, SomeOtherRole await ctx.send( "You are a member of one of the authorized roles. Congratulations!" ) ``` Args: setting: The name of the setting to pull the roles from setting: str or list or tuple Returns: Whether the user is authorized """ assert isinstance(ctx.author, Member) perms = ctx.channel.permissions_for(ctx.author) if ( perms.administrator or perms.manage_channels or perms.manage_guild or owner == str(ctx.author) ): # Superusers get a pass return True roles_id = [] if isinstance(setting, str): roles_id = [int(r) for r in settings[setting].get(ctx, raw=True)] elif isinstance(setting, Sequence): for s in setting: roles_id += [int(r) for r in settings[s].get(ctx, raw=True)] else: raise ValueError(setting) if len(roles_id) == 0: # no roles set, use default return open_by_default for r in ctx.author.roles: if r.id in roles_id: return True await react_if_not_help(ctx) return False