From b4500a9479fcd16e5268190dd8e3c8cc0d5b57fc Mon Sep 17 00:00:00 2001 From: haliphax Date: Wed, 8 Nov 2023 10:44:24 -0600 Subject: [PATCH] massive cleanup effort --- aethersprite/__init__.py | 5 +- aethersprite/authz.py | 12 ++- aethersprite/common.py | 50 +++++---- aethersprite/extensions/__init__.py | 1 + aethersprite/extensions/base/_all.py | 2 +- aethersprite/extensions/base/alias.py | 37 ++++--- aethersprite/extensions/base/badnames.py | 6 +- aethersprite/extensions/base/github.py | 10 +- aethersprite/extensions/base/gmt.py | 7 +- aethersprite/extensions/base/greet.py | 8 +- aethersprite/extensions/base/name_only.py | 2 +- aethersprite/extensions/base/nick.py | 9 +- aethersprite/extensions/base/only.py | 118 ++++++++++++++-------- aethersprite/extensions/base/poll.py | 51 +++++++--- aethersprite/extensions/base/prefix.py | 4 +- aethersprite/extensions/base/roles.py | 110 ++++++++++++++------ aethersprite/extensions/base/settings.py | 86 ++++++++++------ aethersprite/extensions/base/wipe.py | 2 +- aethersprite/extensions/base/yeet.py | 43 ++++++-- aethersprite/filters.py | 45 ++++++--- aethersprite/settings.py | 97 ++++++++++++------ aethersprite/webapp/__init__.py | 22 ++-- aethersprite/webapp/__main__.py | 6 +- requirements/requirements.in | 1 - requirements/requirements.txt | 6 +- 25 files changed, 488 insertions(+), 252 deletions(-) diff --git a/aethersprite/__init__.py b/aethersprite/__init__.py index 04ad20c..95c58b4 100644 --- a/aethersprite/__init__.py +++ b/aethersprite/__init__.py @@ -101,6 +101,7 @@ def invoked_with(self): def get_prefixes(bot: Bot, message: Message): from .settings import settings + assert bot.user user_id = bot.user.id base = [f"<@!{user_id}> ", f"<@{user_id}> "] default = [config.get("bot", {}).get("prefix", "!")] @@ -149,17 +150,19 @@ async def on_command_error(ctx: Context, error: Exception): return bot: Bot = ctx.bot - cog: Alias | None = bot.get_cog("Alias") + cog: Alias | None = bot.get_cog("Alias") # type: ignore if cog is None: return + assert ctx.guild guild = str(ctx.guild.id) aliases = cog.aliases[guild] if guild in cog.aliases else None if aliases is None: return + assert ctx.prefix name = ctx.message.content.replace(ctx.prefix, "").split(" ")[0].strip() if name not in aliases: diff --git a/aethersprite/authz.py b/aethersprite/authz.py index 2963d4d..419c319 100644 --- a/aethersprite/authz.py +++ b/aethersprite/authz.py @@ -64,7 +64,8 @@ async def react_if_not_help(ctx: Context): 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}" + f"{ctx.author} attempted to access unauthorized command " + f"{ctx.command}" ) return @@ -73,6 +74,7 @@ async def react_if_not_help(ctx: Context): async def require_admin(ctx: Context): """Check for requiring admin/mod privileges to execute a command.""" + assert isinstance(ctx.author, Member) perms = ctx.channel.permissions_for(ctx.author) if ( @@ -83,7 +85,7 @@ async def require_admin(ctx: Context): ): return True - await react_if_not_help() + await react_if_not_help(ctx) return False @@ -97,6 +99,8 @@ async def require_roles(ctx: Context, roles: Sequence[Role]) -> bool: :param roles: The roles to authorize """ + assert isinstance(ctx.author, Member) + if is_in_any_role(ctx.author, roles): return True @@ -106,7 +110,7 @@ async def require_roles(ctx: Context, roles: Sequence[Role]) -> bool: async def require_roles_from_setting( - ctx: Context, setting: Union[str, Sequence], open_by_default=True + ctx: Context, setting: str | Sequence, open_by_default=True ) -> bool: """ Check for requiring particular roles (loaded from the given setting) to @@ -147,6 +151,8 @@ async def my_other_command(ctx): :type setting: str or list or tuple """ + assert isinstance(ctx.author, Member) + perms = ctx.channel.permissions_for(ctx.author) if ( diff --git a/aethersprite/common.py b/aethersprite/common.py index 973d6ee..6dce1a7 100644 --- a/aethersprite/common.py +++ b/aethersprite/common.py @@ -1,7 +1,11 @@ -"Common functions module" +"""Common functions module""" + +# typing +from typing import Any # 3rd party from discord.ext.commands import Context + # stdlib from collections import namedtuple from datetime import datetime, timezone @@ -11,23 +15,27 @@ from typing import Callable, Tuple # constants -#: One minute in seconds MINUTE = 60 -#: One hour in seconds +"""One minute in seconds""" + HOUR = MINUTE * 60 -#: One day in seconds +"""One hour in seconds""" + DAY = HOUR * 24 -#: 15 minutes in seconds +"""One day in seconds""" + FIFTEEN_MINS = MINUTE * 15 -#: Formatting string for datetime objects -DATETIME_FORMAT = '%a %Y-%m-%d %H:%M:%S %Z' +"""15 minutes in seconds""" + +DATETIME_FORMAT = "%a %Y-%m-%d %H:%M:%S %Z" +"""Formatting string for datetime objects""" # structs -#: Fake a context for use in certain functions that expect one -FakeContext = namedtuple('FakeContext', ('guild',)) +FakeContext = namedtuple("FakeContext", ("guild",)) +"""Fake a context for use in certain functions that expect one""" -def get_channel_for_id(guild, id: int) -> str: +def get_channel_for_id(guild, id: int) -> str | None: """ Return channel name for given guild and channel ID. @@ -41,7 +49,7 @@ def get_channel_for_id(guild, id: int) -> str: return chans[0] if len(chans) else None -def get_id_for_channel(guild, channel: str) -> int: +def get_id_for_channel(guild, channel: str) -> int | None: """ Return channel ID for given guild and channel name. @@ -56,7 +64,7 @@ def get_id_for_channel(guild, channel: str) -> int: return ids[0] if len(ids) else None -def get_mixed_channels(value: str) -> Tuple[Tuple[str, str]]: +def get_mixed_channels(value: str) -> list[Any]: """ Return a series of group pairs matched from the provided value. The first element in each pair will be the channel ID if the match was a mention; @@ -67,10 +75,10 @@ def get_mixed_channels(value: str) -> Tuple[Tuple[str, str]]: :returns: A series of group pairs (channel ID, channel text) """ - return re.findall(r'<#(\d+)> ?|([-_a-zA-Z0-9]+)[, ]*', value.strip()) + return re.findall(r"<#(\d+)> ?|([-_a-zA-Z0-9]+)[, ]*", value.strip()) -def get_id_for_role(guild, role: str) -> int: +def get_id_for_role(guild, role: str) -> int | None: """ Return role ID for given guild and role name. @@ -85,7 +93,7 @@ def get_id_for_role(guild, role: str) -> int: return ids[0] if len(ids) else None -def get_role_for_id(guild, id: int) -> str: +def get_role_for_id(guild, id: int) -> str | None: """ Return role name for given guild and role ID. @@ -99,7 +107,7 @@ def get_role_for_id(guild, id: int) -> str: return roles[0] if len(roles) else None -def get_mixed_roles(value: str) -> Tuple[Tuple[str, str]]: +def get_mixed_roles(value: str) -> list[Any]: """ Return a series of group pairs matched from the provided value. The first element in each pair will be the role ID if the match was a mention; @@ -110,7 +118,7 @@ def get_mixed_roles(value: str) -> Tuple[Tuple[str, str]]: :returns: A series of group pairs (role ID, role text) """ - return re.findall(r'<@&(\d+)> ?|([^,]+)[, ]*', value.strip()) + return re.findall(r"<@&(\d+)> ?|([^,]+)[, ]*", value.strip()) def get_timespan_chunks(string: str): @@ -122,11 +130,11 @@ def get_timespan_chunks(string: str): :rtype: tuple """ - s = re.search(r'.*?(-?\d+)d.*', string) + s = re.search(r".*?(-?\d+)d.*", string) days = int(s.groups()[0]) if s else 0 - s = re.search(r'.*?(-?\d+)h.*', string) + s = re.search(r".*?(-?\d+)h.*", string) hours = int(s.groups()[0]) if s else 0 - s = re.search(r'.*?(-?\d+)m.*', string) + s = re.search(r".*?(-?\d+)m.*", string) minutes = int(s.groups()[0]) if s else 0 return (days, hours, minutes) @@ -164,4 +172,4 @@ def seconds_to_str(ts): if seconds > 0: until.append(f'{seconds} second{"s" if seconds > 1 else ""}') - return ', '.join(until) + return ", ".join(until) diff --git a/aethersprite/extensions/__init__.py b/aethersprite/extensions/__init__.py index e69de29..0fd4a2c 100644 --- a/aethersprite/extensions/__init__.py +++ b/aethersprite/extensions/__init__.py @@ -0,0 +1 @@ +"""Aethersprite extensions""" diff --git a/aethersprite/extensions/base/_all.py b/aethersprite/extensions/base/_all.py index fec11c3..6bc227e 100644 --- a/aethersprite/extensions/base/_all.py +++ b/aethersprite/extensions/base/_all.py @@ -1,4 +1,4 @@ -"""Load all command extensions""" +"""Load all extensions""" # 3rd party from discord.ext.commands import Bot diff --git a/aethersprite/extensions/base/alias.py b/aethersprite/extensions/base/alias.py index 924fbed..2ab82a0 100644 --- a/aethersprite/extensions/base/alias.py +++ b/aethersprite/extensions/base/alias.py @@ -1,26 +1,29 @@ -"Alias cog" +"""Alias cog""" # 3rd party -from discord.ext.commands import Bot, command, Cog +from discord.ext.commands import Bot, Cog, command, Context from sqlitedict import SqliteDict # local from aethersprite import data_folder, log from aethersprite.authz import channel_only, require_admin -#: Aliases database aliases = SqliteDict( f"{data_folder}alias.sqlite3", tablename="aliases", autocommit=True ) +"""Aliases database""" + +bot: Bot class Alias(Cog): - "Alias commands; add and remove command aliases" + """Alias commands; add and remove command aliases""" @staticmethod - def get_aliases(ctx, cmd): - "Get aliases for the given command and context." + def get_aliases(ctx: Context, cmd: str): + """Get aliases for the given command and context.""" + assert ctx.guild mylist = list() guild = str(ctx.guild.id) @@ -40,9 +43,10 @@ def __init__(self, bot): self.aliases = aliases @command(name="alias.add") - async def add(self, ctx, alias: str, command: str): - "Add an alias of for " + async def add(self, ctx: Context, alias: str, command: str): + """Add an alias of for """ + assert ctx.guild guild = str(ctx.guild.id) if ctx.guild.id not in aliases: @@ -55,7 +59,7 @@ async def add(self, ctx, alias: str, command: str): return - cmd = ctx.bot.get_command(command) + cmd = bot.get_command(command) if cmd is None: await ctx.send(f":scream: No such command!") @@ -68,9 +72,10 @@ async def add(self, ctx, alias: str, command: str): await ctx.send(f":sunglasses: Done.") @command(name="alias.remove") - async def remove(self, ctx, alias: str): - "Remove " + async def remove(self, ctx: Context, alias: str): + """Remove """ + assert ctx.guild guild = str(ctx.guild.id) als = aliases[guild] if guild in aliases else None @@ -90,9 +95,10 @@ async def remove(self, ctx, alias: str): await ctx.send(":wastebasket: Removed.") @command(name="alias.list") - async def list(self, ctx): - "List all command aliases" + async def list(self, ctx: Context): + """List all command aliases""" + assert ctx.guild guild = str(ctx.guild.id) if guild not in aliases: @@ -108,7 +114,10 @@ async def list(self, ctx): await ctx.send(f":detective: **{output}**") -async def setup(bot: Bot): +async def setup(bot_: Bot): + global bot + + bot = bot_ cog = Alias(bot) for c in cog.get_commands(): diff --git a/aethersprite/extensions/base/badnames.py b/aethersprite/extensions/base/badnames.py index 0aaf42d..8e36d84 100644 --- a/aethersprite/extensions/base/badnames.py +++ b/aethersprite/extensions/base/badnames.py @@ -1,4 +1,6 @@ -"Badnames extension; automatically kick users whose names match a blacklist." +""" +Badnames extension; automatically kick users whose names match a blacklist. +""" # local from aethersprite import log @@ -10,7 +12,7 @@ async def on_member_join(member: Member): - "Check member names against blacklist on join." + """Check member names against blacklist on join.""" badnames_setting: str = settings["badnames"].get(member) diff --git a/aethersprite/extensions/base/github.py b/aethersprite/extensions/base/github.py index 91631d4..8e65726 100644 --- a/aethersprite/extensions/base/github.py +++ b/aethersprite/extensions/base/github.py @@ -1,14 +1,16 @@ -"GitHub URL command" +"""GitHub URL command""" # 3rd party -from discord.ext.commands import Bot, command +from discord.ext.commands import Bot, command, Context # local from aethersprite import log -@command(brief="GitHub URL for bot source code, feature requests", pass_context=True) -async def github(ctx): +@command( + brief="GitHub URL for bot source code, feature requests", pass_context=True +) +async def github(ctx: Context): """ This bot is running on Aethersprite, an open source bot software built with discord.py. My source code is available for free. Contributions in the form of code, bug reports, and feature requests are all welcome. diff --git a/aethersprite/extensions/base/gmt.py b/aethersprite/extensions/base/gmt.py index 3d2d581..3cde908 100644 --- a/aethersprite/extensions/base/gmt.py +++ b/aethersprite/extensions/base/gmt.py @@ -1,8 +1,7 @@ -"GMT time check and offset command" +"""GMT time check and offset command""" # stdlib from datetime import datetime, timedelta -import typing # 3rd party from discord.ext.commands import Bot, command, Context @@ -25,7 +24,7 @@ async def _time(ctx: Context, tz: str, offset: str | None): @command(brief="Get current time or offset in GMT") -async def gmt(ctx: Context, *, offset: typing.Optional[str]): +async def gmt(ctx: Context, *, offset: str | None = None): """ Get current time in GMT or offset by days, hours, and minutes. @@ -40,7 +39,7 @@ async def gmt(ctx: Context, *, offset: typing.Optional[str]): @command(brief="Get current time or offset in UTC") -async def utc(ctx: Context, *, offset: typing.Optional[str]): +async def utc(ctx: Context, *, offset: str | None = None): """ Get current time in UTC or offset by days, hours, and minutes. diff --git a/aethersprite/extensions/base/greet.py b/aethersprite/extensions/base/greet.py index ad902b5..83b1ba2 100644 --- a/aethersprite/extensions/base/greet.py +++ b/aethersprite/extensions/base/greet.py @@ -10,6 +10,7 @@ # 3rd party from discord import Member +from discord.channel import TextChannel from discord.ext.commands import Bot # filters @@ -26,8 +27,11 @@ async def on_member_join(member: Member): return channel = [c for c in member.guild.channels if c.name == chan_setting][0] - log.info(f"Greeting new member {member} in {member.guild.name} " f"#{channel.name}") - await channel.send( + log.info( + f"Greeting new member {member} in {member.guild.name} " + f"#{channel.name}" + ) + await channel.send( # type: ignore msg_setting.format( mention=member.mention, name=member.display_name, diff --git a/aethersprite/extensions/base/name_only.py b/aethersprite/extensions/base/name_only.py index c94b83b..966d68b 100644 --- a/aethersprite/extensions/base/name_only.py +++ b/aethersprite/extensions/base/name_only.py @@ -13,7 +13,7 @@ async def check_name_only(ctx: Context): - "If the bot wasn't mentioned, refuse the command." + """If the bot wasn't mentioned, refuse the command.""" # don't bother with DMs if isinstance(ctx.channel, DMChannel): diff --git a/aethersprite/extensions/base/nick.py b/aethersprite/extensions/base/nick.py index ccda7f5..63078e0 100644 --- a/aethersprite/extensions/base/nick.py +++ b/aethersprite/extensions/base/nick.py @@ -1,7 +1,7 @@ -"Nick command module" +"""Nick command module""" # 3rd party -from discord.ext.commands import check, command +from discord.ext.commands import check, command, Context # local from aethersprite import log @@ -11,9 +11,10 @@ @command() @check(require_admin) @check(channel_only) -async def nick(ctx, *, nick): - "Change the bot's nickname on this server" +async def nick(ctx: Context, *, nick: str): + """Change the bot's nickname on this server""" + assert ctx.guild await ctx.guild.me.edit(nick=nick) await ctx.send(":thumbsup:") log.info(f"{ctx.author} set bot nickname to {nick}") diff --git a/aethersprite/extensions/base/only.py b/aethersprite/extensions/base/only.py index c8aaf00..42f132f 100644 --- a/aethersprite/extensions/base/only.py +++ b/aethersprite/extensions/base/only.py @@ -1,49 +1,54 @@ -"Only cog" +"""Only cog""" -# stdlib -from functools import partial -import typing +# TODO server whitelist # 3rd party -from discord import DMChannel, TextChannel -from discord.ext.commands import Cog, command -from discord_argparse import ArgumentConverter -from discord_argparse.argparse import OptionalArgument +from discord import DMChannel +from discord.channel import TextChannel +from discord.ext.commands import Cog, command, Context from sqlitedict import SqliteDict # local from aethersprite import data_folder, log from aethersprite.authz import channel_only, require_admin -#: Only whitelist database -onlies = SqliteDict(f"{data_folder}only.sqlite3", tablename="onlies", autocommit=True) -#: Argument converter for optional channel argument by keyword -channel_arg = ArgumentConverter( - channel=OptionalArgument(TextChannel, "The channel to use (if not this one)") +onlies = SqliteDict( + f"{data_folder}only.sqlite3", tablename="onlies", autocommit=True ) +"""Only whitelist database""" class Only(Cog): - "Only commands; disable all commands except for those in a whitelist" + """Only commands; disable all commands except for those in a whitelist""" def __init__(self, bot): self.bot = bot @command(name="only.add") async def add( - self, ctx, command: str, *, params: channel_arg = channel_arg.defaults() + self, + ctx: Context, + command: str, + channel: TextChannel | None = None, ): """ Add the given command to the Only whitelist Enables in this channel. - Use the 'channel' parameter to specify a channel other than the current one. + You may specify a channel other than the current one. - Example: only.add test channel=#lobby + Examples: + !only.add test + !only.add test #lobby """ - channel = params["channel"] if "channel" in params else ctx.channel + assert ctx.guild + + if not channel: + channel = ctx.channel # type: ignore + assert channel + chan_id = str(channel.id) guild = str(ctx.guild.id) @@ -70,23 +75,35 @@ async def add( @command(name="only.remove") async def remove( - self, ctx, command, *, params: channel_arg = channel_arg.defaults() + self, + ctx: Context, + command: str, + channel: TextChannel | None = None, ): """ Remove the given command from the Only whitelist If whitelisting is enabled for this channel, the removed command can no longer be executed. - Use the 'channel' parameter to specify a channel other than the current one. + You may provide a channel other than the current one. - Example: only.remove test channel=#lobby + Examples: + !only.remove test + !only.remove test #lobby """ - channel = params["channel"] if "channel" in params else ctx.channel + assert ctx.guild + + if not channel: + channel = ctx.channel # type: ignore + assert channel + chan_id = str(channel.id) guild = str(ctx.guild.id) - ours = onlies[guild] if guild in onlies else None - ourchan = ours[chan_id] if ours is not None and chan_id in ours else None + ours = onlies[guild] if guild in onlies else {} + ourchan = ( + ours[chan_id] if ours is not None and chan_id in ours else None + ) if ourchan is None: await ctx.send(":person_shrugging: None set.") @@ -111,20 +128,25 @@ async def remove( @command(name="only.list") async def list( self, - ctx, - server: typing.Optional[bool] = False, - *, - params: channel_arg = channel_arg.defaults(), + ctx: Context, + channel: TextChannel | None = None, ): """ List all current channel's whitelisted commands - Use the 'channel' parameter to specify a channel other than the current one. + You may provide a channel other than the current one. - Example: only.list channel=#lobby" + Examples: + !only.list + !only.list #lobby """ - channel = params["channel"] if "channel" in params else ctx.channel + assert ctx.guild + + if not channel: + channel = ctx.channel # type: ignore + assert channel + chan_id = str(channel.id) guild = str(ctx.guild.id) @@ -136,7 +158,7 @@ async def list( if channel not in ours: ours[channel] = set([]) - output = "**, **".join(ours[chan_id]) + output = "**, **".join(ours.get(chan_id, [])) if not len(output): output = "None" @@ -145,22 +167,35 @@ async def list( await ctx.send(f":guard: **{output}**") @command(name="only.reset") - async def reset(self, ctx, *, params: channel_arg = channel_arg.defaults()): + async def reset( + self, + ctx: Context, + channel: TextChannel | None = None, + ): """ Reset Only whitelist Using this command will disable whitelisting behavior and remove the existing whitelist. - Use the 'channel' parameter to specify a channel other than the current one. + You may provide a channel other than the current one. - Example: only.reset channel=#lobby + Examples: + !only.reset + !only.reset #lobby """ - channel = params["channel"] if "channel" in params else ctx.channel + assert ctx.guild + + if not channel: + channel = ctx.channel # type: ignore + assert channel + chan_id = str(channel.id) guild = str(ctx.guild.id) - ours = onlies[guild] if guild in onlies else None - ourchan = ours[chan_id] if ours is not None and chan_id in ours else None + ours = onlies[guild] if guild in onlies else {} + ourchan = ( + ours[chan_id] if ours is not None and chan_id in ours else None + ) if ourchan is None: await ctx.send(":person_shrugging: None set.") @@ -178,8 +213,11 @@ async def reset(self, ctx, *, params: channel_arg = channel_arg.defaults()): log.info(f"{ctx.author} reset Only whitelist for {channel}") -async def check_only(ctx): - "Check that command is in the Only whitelist" +async def check_only(ctx: Context): + """Check that command is in the Only whitelist""" + + assert ctx.guild + assert ctx.command if isinstance(ctx.channel, DMChannel): # can't whitelist commands via DM, since we need a guild to check diff --git a/aethersprite/extensions/base/poll.py b/aethersprite/extensions/base/poll.py index 27d31c6..a663c4e 100644 --- a/aethersprite/extensions/base/poll.py +++ b/aethersprite/extensions/base/poll.py @@ -7,6 +7,7 @@ # 3rd party from discord import Color, Embed, Message, Member +from discord.abc import GuildChannel from discord.channel import TextChannel from discord.ext.commands import check, command, Context from discord.ext.commands.bot import Bot @@ -33,9 +34,11 @@ BAR_WIDTH = 20 POLL_EXPIRY = 86400 * 90 # 90 days -bot: Bot = None +bot: Bot # database -polls = SqliteDict(f"{data_folder}poll.sqlite3", tablename="polls", autocommit=True) +polls = SqliteDict( + f"{data_folder}poll.sqlite3", tablename="polls", autocommit=True +) # filters create_filter = RoleFilter("poll.createroles") vote_filter = RoleFilter("poll.voteroles") @@ -102,7 +105,7 @@ async def poll(ctx: Context, *, options: str): await ctx.message.delete() -def _get_embed(poll): +def _get_embed(poll: dict): total = sum([int(o["count"]) for _, o in poll["options"].items()]) open = "open" if poll["open"] else "closed" prohib_text = "Close" if poll["open"] else "Open" @@ -119,7 +122,9 @@ def _get_embed(poll): for key, opt in poll["options"].items(): count = int(opt["count"]) - rawpct = round(0 if (total == 0 or count == 0) else (count / total) * 100, 2) + rawpct = round( + 0 if (total == 0 or count == 0) else (count / total) * 100, 2 + ) pct = 0 if (total == 0 or count == 0) else round((count / total) * 20) left = 20 - pct bar = f"{SOLID_BLOCK * pct}{SHADE_BLOCK * left}" @@ -132,7 +137,12 @@ def _get_embed(poll): return embed -async def _update_poll(member: Member, message: Message, emoji: str, adjustment: int): +async def _update_poll( + member: Member, + message: Message, + emoji: str, + adjustment: int, +): poll = polls[message.id] opts = poll["options"] opt = opts[emoji] @@ -204,14 +214,21 @@ def _allowed(setting: str, message: Message, member: Member) -> bool: async def on_raw_reaction_add(payload: RawReactionActionEvent): """Handle on_reaction_add event.""" + assert bot.user + assert payload.member + if payload.user_id == bot.user.id or payload.message_id not in polls: return poll = polls[payload.message_id] - channel: TextChannel = payload.member.guild.get_channel(payload.channel_id) - msg: Message = await channel.fetch_message(payload.message_id) + channel = payload.member.guild.get_channel(payload.channel_id) + assert channel + msg: Message = await channel.fetch_message( # type: ignore + payload.message_id, + ) async def _delete(): + assert payload.member prompt = poll["prompt"] delete = payload.member.id in poll["delete"] confirm = payload.member.id in poll["confirm"] @@ -260,14 +277,22 @@ async def _delete(): async def on_raw_reaction_remove(payload: RawReactionActionEvent): "Handle on_reaction_remove event." + assert bot.user + assert payload.guild_id + if payload.user_id == bot.user.id or payload.message_id not in polls: return poll = polls[payload.message_id] - guild: Guild = bot.get_guild(payload.guild_id) - member: Member = guild.get_member(payload.user_id) - channel: TextChannel = guild.get_channel(payload.channel_id) - msg: Message = await channel.fetch_message(payload.message_id) + guild = bot.get_guild(payload.guild_id) + assert guild + member = guild.get_member(payload.user_id) + assert member + channel = guild.get_channel(payload.channel_id) + assert channel + msg: Message = await channel.fetch_message( # type: ignore + payload.message_id, + ) if payload.emoji.name == WASTEBASKET and member.id in poll["delete"]: poll["delete"].remove(member.id) @@ -281,7 +306,9 @@ async def on_raw_reaction_remove(payload: RawReactionActionEvent): return - if payload.emoji.name == PROHIBITED and _allowed("poll.createroles", msg, member): + if payload.emoji.name == PROHIBITED and _allowed( + "poll.createroles", msg, member + ): poll["open"] = True polls[msg.id] = poll await msg.edit(embed=_get_embed(poll)) diff --git a/aethersprite/extensions/base/prefix.py b/aethersprite/extensions/base/prefix.py index 2368f03..4dfadbb 100644 --- a/aethersprite/extensions/base/prefix.py +++ b/aethersprite/extensions/base/prefix.py @@ -1,4 +1,4 @@ -"Prefix command" +"""Prefix setting""" # 3rd party from discord.ext.commands import Context @@ -15,7 +15,7 @@ def get_prefixes(ctx: Context): - "Get bot prefixes." + """Get bot prefixes.""" return settings["prefix"].get(ctx) diff --git a/aethersprite/extensions/base/roles.py b/aethersprite/extensions/base/roles.py index 40fc7cc..e850795 100644 --- a/aethersprite/extensions/base/roles.py +++ b/aethersprite/extensions/base/roles.py @@ -1,4 +1,4 @@ -"Roles self-service cog" +"""Roles self-service cog""" # stdlib import asyncio as aio @@ -29,28 +29,34 @@ class DirectoryUpdateFilter(RoleFilter): - "Automatically update directory post when roles.catalog is updated" + + """Automatically update directory post when roles.catalog is updated""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def in_(self, ctx: Context, value: str) -> None: + def in_(self, ctx: Context, value: str) -> list[int] | None: "Filter input." + assert ctx.guild val = super().in_(ctx, value) - directory = directories[ctx.guild.id] if ctx.guild.id in directories else None + directory = ( + directories[ctx.guild.id] if ctx.guild.id in directories else None + ) if directory is None: return val - chan: TextChannel = ctx.guild.get_channel(directory["channel"]) + chan = ctx.guild.get_channel(directory["channel"]) if chan is None: return val async def update(): try: - msg = await chan.fetch_message(directory["message"]) + msg = await chan.fetch_message( # type: ignore + directory["message"], + ) await _get_message(ctx, msg) except NotFound: pass @@ -64,7 +70,9 @@ async def update(): async def _get_message( - ctx: Context, msg: Optional[Message] = None, expiry: Optional[str] = None + ctx: Context, + msg: Message | None = None, + expiry: str | None = None, ): roles_ = settings["roles.catalog"].get(ctx)[:10] embed = Embed( @@ -83,7 +91,7 @@ async def _get_message( count += 1 if msg is None: - msg: Message = await ctx.send(embed=embed) + msg = await ctx.send(embed=embed) else: await msg.edit(embed=embed) await msg.clear_reactions() @@ -97,15 +105,21 @@ async def _get_message( @command() @check(channel_only) async def roles(ctx: Context): - "Manage your membership in available roles" + """Manage your membership in available roles""" + + assert ctx.guild expiry_raw = settings["roles.postexpiry"].get(ctx) expiry = seconds_to_str(expiry_raw) roles_ = settings["roles.catalog"].get(ctx) if roles_ is None or len(roles_) == 0: - await ctx.send(":person_shrugging: There are no available self-service roles.") - log.warn(f"{ctx.author} invoked roles self-service, but no roles are available") + await ctx.send( + ":person_shrugging: There are no available self-service roles." + ) + log.warn( + f"{ctx.author} invoked roles self-service, but no roles are available" + ) return @@ -125,7 +139,9 @@ async def roles(ctx: Context): @check(channel_only) @check(require_admin) async def catalog(ctx: Context): - "Create a permanent roles catalog post in the current channel." + """Create a permanent roles catalog post in the current channel.""" + + assert ctx.guild roles_ = settings["roles.catalog"].get(ctx) @@ -144,11 +160,13 @@ async def catalog(ctx: Context): if guild_id in directories: existing = directories[guild_id] - chan: TextChannel = ctx.guild.get_channel(existing["channel"]) + chan = ctx.guild.get_channel(existing["channel"]) if chan is not None: try: - msg = await chan.fetch_message(existing["message"]) + msg = await chan.fetch_message( # type: ignore + existing["message"], + ) await msg.delete() except NotFound: pass @@ -161,13 +179,18 @@ async def catalog(ctx: Context): async def on_raw_reaction_add(payload: RawReactionActionEvent): - "Handle on_reaction_add event." + """Handle on_reaction_add event.""" + + assert bot.user + assert payload.guild_id if payload.user_id == bot.user.id: return directory = ( - directories[payload.guild_id] if payload.guild_id in directories else None + directories[payload.guild_id] + if payload.guild_id in directories + else None ) if payload.message_id not in posts and ( @@ -175,10 +198,15 @@ async def on_raw_reaction_add(payload: RawReactionActionEvent): ): return - guild: Guild = bot.get_guild(payload.guild_id) - channel: TextChannel = guild.get_channel(payload.channel_id) - message: Message = await channel.fetch_message(payload.message_id) - member: Member = guild.get_member(payload.user_id) + guild = bot.get_guild(payload.guild_id) + assert guild + channel = guild.get_channel(payload.channel_id) + assert channel + message = await channel.fetch_message( # type: ignore + payload.message_id, + ) + member = guild.get_member(payload.user_id) + assert member split = str(payload.emoji).split("\ufe0f") if len(split) != 2: @@ -189,7 +217,8 @@ async def on_raw_reaction_add(payload: RawReactionActionEvent): fake_ctx = FakeContext(guild=guild) setting = settings["roles.catalog"].get(fake_ctx, raw=True) roles_ = sorted( - [r for r in guild.roles if r.id in setting], key=lambda x: x.name.lower() + [r for r in guild.roles if r.id in setting], + key=lambda x: x.name.lower(), ) which = int(split[0]) @@ -204,13 +233,18 @@ async def on_raw_reaction_add(payload: RawReactionActionEvent): async def on_raw_reaction_remove(payload: RawReactionActionEvent): - "Handle on_reaction_remove event." + """Handle on_reaction_remove event.""" + + assert bot.user + assert payload.guild_id if payload.user_id == bot.user.id: return directory = ( - directories[payload.guild_id] if payload.guild_id in directories else None + directories[payload.guild_id] + if payload.guild_id in directories + else None ) if payload.message_id not in posts and ( @@ -223,12 +257,15 @@ async def on_raw_reaction_remove(payload: RawReactionActionEvent): if len(split) != 2: return - guild: Guild = bot.get_guild(payload.guild_id) - member: Member = guild.get_member(payload.user_id) + guild = bot.get_guild(payload.guild_id) + assert guild + member = guild.get_member(payload.user_id) + assert member fake_ctx = FakeContext(guild=guild) setting = settings["roles.catalog"].get(fake_ctx, raw=True) roles_ = sorted( - [r for r in guild.roles if r.id in setting], key=lambda x: x.name.lower() + [r for r in guild.roles if r.id in setting], + key=lambda x: x.name.lower(), ) which = int(split[0]) @@ -241,14 +278,18 @@ async def on_raw_reaction_remove(payload: RawReactionActionEvent): async def on_ready(): - "Clear expired/missing roles posts on startup." + """Clear expired/missing roles posts on startup.""" # clean up missing directories for guild_id, directory in directories.items(): try: - guild: Guild = bot.get_guild(int(guild_id)) - chan: TextChannel = guild.get_channel(directory["channel"]) - msg = await chan.fetch_message(directory["message"]) + guild = bot.get_guild(int(guild_id)) + assert guild + chan = guild.get_channel(directory["channel"]) + assert chan + msg = await chan.fetch_message( # type: ignore + directory["message"], + ) except NotFound: log.warn(f"Deleted missing directory post for {guild_id}") del directories[guild_id] @@ -271,12 +312,15 @@ def _delete(id: int): return post = posts[id] - guild: Guild = bot.get_guild(post["guild"]) - channel: TextChannel = guild.get_channel(post["channel"]) + guild = bot.get_guild(post["guild"]) + assert guild + channel = guild.get_channel(post["channel"]) async def f(): try: - msg: Message = await channel.fetch_message(id) + msg: Message = await channel.fetch_message( # type: ignore + id, + ) await msg.delete() except NotFound: pass diff --git a/aethersprite/extensions/base/settings.py b/aethersprite/extensions/base/settings.py index e8a689d..11bcca0 100644 --- a/aethersprite/extensions/base/settings.py +++ b/aethersprite/extensions/base/settings.py @@ -1,12 +1,8 @@ -"Settings commands; store, view, and manipulate settings" - -# stdlib -import re -from typing import Optional +"""Settings commands; store, view, and manipulate settings""" # 3rd party -from discord import TextChannel -from discord.ext.commands import Bot, Cog, command +from discord.channel import TextChannel +from discord.ext.commands import Bot, Cog, command, Context from functools import partial # local @@ -18,10 +14,12 @@ #: messages MSG_NO_SETTING = ":person_shrugging: No such setting exists." -#: authorization decorator authz = partial( - require_roles_from_setting, setting="settings.adminroles", open_by_default=False + require_roles_from_setting, + setting="settings.adminroles", + open_by_default=False, ) +"""Authorization decorator""" class Settings(Cog): @@ -35,12 +33,19 @@ class Settings(Cog): def __init__(self, bot): self.bot = bot + @command(name="get.all") + async def get_all(self, ctx: Context): + """View a list of all settings""" + + settings_str = "**, **".join(sorted(settings.keys())) + await ctx.send(f":gear: All settings: **{settings_str}**") + log.info(f"{ctx.author} viewed all settings") + @command() async def get( self, - ctx, - name: Optional[str] = None, - *, + ctx: Context, + name: str, channel: TextChannel | None = None, ): """ @@ -48,17 +53,15 @@ async def get( Use the 'channel' parameter to specify a channel other than the current one. - Example: get key channel=#lobby + Examples: + !get key + !get key #lobby """ - if name is None: - settings_str = "**, **".join(sorted(settings.keys())) - await ctx.send(f":gear: All settings: **{settings_str}**") - log.info(f"{ctx.author} viewed all settings") + if not channel: + channel = ctx.channel # type: ignore + assert channel - return - - channel = ctx.channel if not channel else channel val = settings[name].get(ctx, channel=channel) default = settings[name].default await ctx.send( @@ -70,19 +73,27 @@ async def get( @command() async def set( - self, ctx, name: str, *, value: str, channel: TextChannel | None = None + self, + ctx: Context, + name: str, + value: str, + channel: TextChannel | None = None, ): """ Change/view a setting's value - If [name] is not provided, a list of all settings (but not their values) will be shown. If [value] is not provided, the setting's current value (and its default) will be shown, instead. Note that channel-dependent settings must be set and viewed in relevant channels. + Note that channel-dependent settings must be set and viewed in relevant channels. - Use the 'channel' parameter to specify a channel other than the current one. + You may provide a channel other than the current one. - Example: set key value channel=#lobby + Examples: + !set key value + !set key value #lobby """ - channel = ctx.channel if not channel else channel + if not channel: + channel = ctx.channel # type: ignore + assert channel if name not in settings: await ctx.send(MSG_NO_SETTING) @@ -95,7 +106,9 @@ async def set( if settings[name].set(ctx, value, channel=channel): await ctx.send(f":thumbsup: Value updated.") - log.info(f"{ctx.author} updated setting {name}: {value} in {channel}") + log.info( + f"{ctx.author} updated setting {name}: {value} in {channel}" + ) else: await ctx.send(f":thumbsdown: Error updating value.") log.warn( @@ -104,16 +117,25 @@ async def set( ) @command() - async def clear(self, ctx, name: str, *, channel: TextChannel): + async def clear( + self, + ctx: Context, + name: str, + channel: TextChannel | None = None, + ): """ Reset setting to its default value - Use the 'channel' parameter to specify a channel other than the current one. + You may provide a channel other than the current one. - Example: clear key channel=#lobby + Examples: + !clear key + !clear key #lobby """ - channel = ctx.channel if not channel else channel + if not channel: + channel = ctx.channel # type: ignore + assert channel if name not in settings: log.warn( @@ -129,8 +151,8 @@ async def clear(self, ctx, name: str, *, channel: TextChannel): log.info(f"{ctx.author} cleared setting {name} in {channel}") @command() - async def desc(self, ctx, name: str): - "View description of setting " + async def desc(self, ctx: Context, name: str): + """View description of setting """ if name not in settings: await ctx.send(MSG_NO_SETTING) diff --git a/aethersprite/extensions/base/wipe.py b/aethersprite/extensions/base/wipe.py index 53ccce7..af4dc31 100644 --- a/aethersprite/extensions/base/wipe.py +++ b/aethersprite/extensions/base/wipe.py @@ -86,7 +86,7 @@ async def on_raw_reaction_add(payload: RawReactionActionEvent): @command() @check(channel_only) @check(require_admin) -async def wipe(ctx: Context, str: Optional[str] = None): +async def wipe(ctx: Context): """Delete all messages in a channel.""" assert ctx.guild diff --git a/aethersprite/extensions/base/yeet.py b/aethersprite/extensions/base/yeet.py index 28f7943..2f6bf59 100644 --- a/aethersprite/extensions/base/yeet.py +++ b/aethersprite/extensions/base/yeet.py @@ -6,7 +6,6 @@ # 3rd party from discord import DMChannel, TextChannel from discord.ext.commands import Bot, Cog, command, Context -from discord_argparse import ArgumentConverter, OptionalArgument from sqlitedict import SqliteDict # local @@ -14,10 +13,13 @@ from aethersprite.authz import require_admin #: Yeets database -yeets = SqliteDict(f"{data_folder}yeet.sqlite3", tablename="yeets", autocommit=True) +yeets = SqliteDict( + f"{data_folder}yeet.sqlite3", tablename="yeets", autocommit=True +) class Yeet(Cog): + """Yeet commands; enable and disable commands per-server and per-channel""" def __init__(self, bot): @@ -28,7 +30,7 @@ async def add( self, ctx: Context, command: str, - channel: typing.Optional[TextChannel] = None, + channel: TextChannel | None = None, ): """ Disable the given command @@ -40,8 +42,13 @@ async def add( !yeet test #lobby <-- disables test command in #lobby channel """ + assert ctx.guild server = True if not channel else False - channel = ctx.channel if not channel else channel + + if not channel: + channel = ctx.channel # type: ignore + assert channel + server_key = command.lower().strip() key = f"{server_key}#{channel.id}" guild = str(ctx.guild.id) @@ -75,7 +82,7 @@ async def remove( self, ctx: Context, command: str, - channel: typing.Optional[TextChannel] = None, + channel: TextChannel | None = None, ): """ Enable the given command @@ -87,8 +94,13 @@ async def remove( !unyeet test #lobby <-- enable test command in #lobby channel """ + assert ctx.guild server = True if not channel else False - channel = ctx.channel if not channel else channel + + if not channel: + channel = ctx.channel # type: ignore + assert channel + server_key = command.lower().strip() key = f"{server_key}#{channel.id}" guild = str(ctx.guild.id) @@ -106,14 +118,16 @@ async def remove( ys.remove(server_key if server else key) yeets[guild] = ys - log.info(f"{ctx.author} removed {server_key if server else key} in {channel}") + log.info( + f"{ctx.author} removed {server_key if server else key} in {channel}" + ) await ctx.send(":tada: Re-enabled.") @command(name="yeets") async def list( self, ctx: Context, - channel: typing.Optional[TextChannel] = None, + channel: TextChannel | None = None, ): """ List all yeeted commands on this server @@ -125,8 +139,13 @@ async def list( !yeets #lobby <-- list yeets in #lobby channel """ + assert ctx.guild server = True if not channel else False - channel = ctx.channel if not channel else channel + + if not channel: + channel = ctx.channel # type: ignore + assert channel + guild = str(ctx.guild.id) if guild not in yeets: @@ -160,6 +179,10 @@ async def check_yeet(ctx: Context): # settings values return True + assert ctx.channel + assert ctx.command + assert ctx.guild + guild = str(ctx.guild.id) if guild not in yeets: @@ -173,7 +196,7 @@ async def check_yeet(ctx: Context): log.debug( f"Suppressing yeeted command from " f"{ctx.author}: {ctx.command.name} in " - f"#{ctx.channel.name} ({ctx.guild.name})" + f"#{ctx.channel.name} ({ctx.guild.name})" # type: ignore ) return False diff --git a/aethersprite/filters.py b/aethersprite/filters.py index d38b7d4..df550a3 100644 --- a/aethersprite/filters.py +++ b/aethersprite/filters.py @@ -1,8 +1,8 @@ -"Setting filters module" +"""Setting filters module""" # stdlib import re -from typing import Any, List +from typing import Any # 3rd party from discord.ext.commands import Context @@ -20,15 +20,16 @@ class SettingFilter(object): - "A class with methods for filtering a setting's input and output" - #: The name of the setting to filter - setting: str = None + """A class with methods for filtering a setting's input and output""" + + setting: str | None = None + """The name of the setting to filter""" def __init__(self, setting: str): self.setting = setting - def in_(self, ctx: Context, value: str) -> None: + def in_(self, ctx: Context, value: str | None) -> None: """ Must override; input filter method. @@ -51,16 +52,17 @@ def out(self, ctx: Context, value: Any) -> Any: class ChannelFilter(SettingFilter): - "Filter used for converting channel names to IDs and back" - #: True to allow multiple values + """Filter used for converting channel names to IDs and back""" + multiple: bool = False + """True to allow multiple values""" def __init__(self, setting: str, multiple: bool = False): super().__init__(setting) self.multiple = multiple - def in_(self, ctx: Context, value: str) -> None: + def in_(self, ctx: Context, value: str | None) -> list[int] | None: """ Filter setting input. @@ -68,6 +70,9 @@ def in_(self, ctx: Context, value: str) -> None: :param value: The incoming value """ + if not value: + return + channels = get_mixed_channels(value) ids = [] @@ -90,7 +95,11 @@ def in_(self, ctx: Context, value: str) -> None: return ids - def out(self, ctx: Context, value: List[int]) -> List[str]: + def out( + self, + ctx: Context, + value: list[int], + ) -> list[str | None] | str | None: """ Filter setting output. @@ -118,16 +127,17 @@ def out(self, ctx: Context, value: List[int]) -> List[str]: class RoleFilter(SettingFilter): - "Filter used for converting role names to IDs and back" - #: True to allow multiple values + """Filter used for converting role names to IDs and back""" + multiple: bool = True + """True to allow multiple values""" def __init__(self, setting: str, multiple: bool = True): super().__init__(setting) self.multiple = multiple - def in_(self, ctx: Context, value: str) -> None: + def in_(self, ctx: Context, value: str | None) -> list[int] | None: """ Filter setting input. @@ -135,6 +145,9 @@ def in_(self, ctx: Context, value: str) -> None: :param value: The incoming value """ + if not value: + return + roles = get_mixed_roles(value) ids = [] @@ -157,7 +170,11 @@ def in_(self, ctx: Context, value: str) -> None: return ids - def out(self, ctx: Context, value: List[int]) -> List[str]: + def out( + self, + ctx: Context, + value: list[int], + ) -> list[str | None] | str | None: """ Filter setting output. diff --git a/aethersprite/settings.py b/aethersprite/settings.py index 9aff2f0..2b1144b 100644 --- a/aethersprite/settings.py +++ b/aethersprite/settings.py @@ -44,44 +44,56 @@ async def reset(ctx): # 3rd party from sqlitedict import SqliteDict + # local from . import data_folder # TODO cleanup settings for missing servers/channels on startup -#: Setting definitions settings = {} +"""Setting definitions""" class Setting(object): - "Setting class; represents an individual setting definition" + """Setting class; represents an individual setting definition""" # Setting values - _values = SqliteDict(f'{data_folder}settings.sqlite3', tablename='values', - autocommit=True) - - def __init__(self, name: str, default: str, validate: callable, - channel: typing.Optional[bool] = False, - description: typing.Optional[str] = None, - filter: 'SettingFilter' = None): + _values = SqliteDict( + f"{data_folder}settings.sqlite3", tablename="values", autocommit=True + ) + + def __init__( + self, + name: str, + default: str | None, + validate: typing.Callable, + channel: bool = False, + description: str | None = None, + filter: "SettingFilter" | None = None, + ): if name is None: - raise ValueError('Name must not be None') + raise ValueError("Name must not be None") - #: The setting's name self.name = name - #: Default value + """The setting's name""" + self.default = default - #: Validator function + """Default value""" + self.validate = validate - #: If this is a channel (and not a guild) setting + """Validator function""" + self.channel = channel - #: This setting's description + """If this is a channel (and not a guild) setting""" + self.description = description - #: The filter used to manipulate setting input/output + """This setting's description""" + self.filter = filter + """The filter used to manipulate setting input/output""" - def _ctxkey(self, ctx, channel: str = None): + def _ctxkey(self, ctx, channel: str | None = None): """ Get the key to use when storing/accessing the setting. @@ -91,15 +103,22 @@ def _ctxkey(self, ctx, channel: str = None): :rtype: str """ - key = str(ctx.guild['id'] - if isinstance(ctx.guild, dict) else ctx.guild.id) + key = str( + ctx.guild["id"] if isinstance(ctx.guild, dict) else ctx.guild.id + ) if self.channel: - key += f'#{ctx.channel.id if channel is None else channel}' + key += f"#{ctx.channel.id if channel is None else channel}" return key - def set(self, ctx, value: str, raw: bool = False, channel: str = None): + def set( + self, + ctx, + value: str, + raw: bool = False, + channel: str | None = None, + ): """ Change the setting's value. @@ -116,7 +135,12 @@ def set(self, ctx, value: str, raw: bool = False, channel: str = None): try: if not raw and self.filter is not None: - value = self.filter.in_(ctx, value) + filtered = self.filter.in_(ctx, value) + + if not filtered: + return False + + value = filtered except ValueError: return False @@ -132,7 +156,7 @@ def set(self, ctx, value: str, raw: bool = False, channel: str = None): return True - def get(self, ctx, raw: bool = False, channel: str = None): + def get(self, ctx, raw: bool = False, channel: str | None = None): """ Get the setting's value. @@ -144,9 +168,11 @@ def get(self, ctx, raw: bool = False, channel: str = None): """ key = self._ctxkey(ctx) - val = self._values[key][self.name] \ - if key in self._values and self.name in self._values[key] \ - else None + val = ( + self._values[key][self.name] + if key in self._values and self.name in self._values[key] + else None + ) if not raw and self.filter is not None: val = self.filter.out(ctx, val) @@ -154,10 +180,14 @@ def get(self, ctx, raw: bool = False, channel: str = None): return self.default if val is None else val -def register(name: str, default: str, validator: callable, - channel: typing.Optional[bool] = False, - description: typing.Optional[str] = None, - filter: typing.Optional['SettingFilter'] = None): +def register( + name: str, + default: typing.Any | None, + validator: typing.Callable, + channel: bool = False, + description: str | None = None, + filter: "SettingFilter" | None = None, +): """ Register a setting. @@ -171,7 +201,8 @@ def register(name: str, default: str, validator: callable, global settings if name in settings: - raise Exception(f'Setting already exists: {name}') + raise Exception(f"Setting already exists: {name}") - settings[name] = Setting(name, default, validator, channel, description, - filter=filter) + settings[name] = Setting( + name, default, validator, channel, description, filter=filter + ) diff --git a/aethersprite/webapp/__init__.py b/aethersprite/webapp/__init__.py index 67ea148..6e6dc06 100644 --- a/aethersprite/webapp/__init__.py +++ b/aethersprite/webapp/__init__.py @@ -1,36 +1,40 @@ -"Web application" +"""Web application""" # stdlib from importlib import import_module from inspect import ismodule + # 3rd party from flask import Flask from werkzeug.middleware.proxy_fix import ProxyFix + # local from .. import config, log -#: Flask application app = Flask(__name__) -app.config.update(config['webapp']['flask']) +"""Flask application""" -proxies = config.get('webapp', {}).get('proxies', None) +app.config.update(config["webapp"]["flask"]) +proxies = config.get("webapp", {}).get("proxies", None) if proxies is not None: app.wsgi_app = ProxyFix(app.wsgi_app, proxies) + def _load_ext(ext, package=None): mod = import_module(ext, package) - if hasattr(mod, 'META_EXTENSION') and mod.META_EXTENSION: + if hasattr(mod, "META_EXTENSION") and mod.META_EXTENSION: for child in mod._mods: - _load_ext(f'..{child}', ext) + _load_ext(f"..{child}", ext) - if not hasattr(mod, 'setup_webapp'): + if not hasattr(mod, "setup_webapp"): return - log.info(f'Web app setup: {mod.__name__}') + log.info(f"Web app setup: {mod.__name__}") mod.setup_webapp(app) + # probe extensions for web app hooks -for ext in config['bot']['extensions']: +for ext in config["bot"]["extensions"]: _load_ext(ext) diff --git a/aethersprite/webapp/__main__.py b/aethersprite/webapp/__main__.py index 45fb27d..a10ad01 100644 --- a/aethersprite/webapp/__main__.py +++ b/aethersprite/webapp/__main__.py @@ -1,7 +1,7 @@ -"Web application entry point" +"""Web application entry point""" # local from . import app -if __name__ == '__main__': - app.run(app.config['SERVER_HOST'], app.config['SERVER_PORT']) +if __name__ == "__main__": + app.run(app.config["SERVER_HOST"], app.config["SERVER_PORT"]) diff --git a/requirements/requirements.in b/requirements/requirements.in index ac1fb99..22f5283 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -1,5 +1,4 @@ colorlog -discord_argparse discord-pretty-help discord.py flask diff --git a/requirements/requirements.txt b/requirements/requirements.txt index c31184f..4e702e8 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -20,14 +20,10 @@ click==8.1.7 # via flask colorlog==6.7.0 # via -r requirements.in -discord-argparse==1.0.1 - # via -r requirements.in discord-pretty-help==2.0.7 # via -r requirements.in discord-py==2.3.2 - # via - # -r requirements.in - # discord-argparse + # via -r requirements.in flask==3.0.0 # via -r requirements.in frozenlist==1.4.0