diff options
-rw-r--r-- | arthur/__init__.py | 12 | ||||
-rw-r--r-- | arthur/__main__.py | 37 | ||||
-rw-r--r-- | arthur/bot.py | 50 | ||||
-rw-r--r-- | arthur/config.py | 3 | ||||
-rw-r--r-- | arthur/extensions.py | 15 | ||||
-rw-r--r-- | arthur/exts/__init__.py | 1 | ||||
-rw-r--r-- | arthur/exts/cloudflare/__init__.py | 1 | ||||
-rw-r--r-- | arthur/exts/error_handler/__init__.py | 1 | ||||
-rw-r--r-- | arthur/exts/fun/__init__.py | 1 | ||||
-rw-r--r-- | arthur/exts/kubernetes/__init__.py | 1 |
10 files changed, 65 insertions, 57 deletions
diff --git a/arthur/__init__.py b/arthur/__init__.py index a7efb0b..0d837f6 100644 --- a/arthur/__init__.py +++ b/arthur/__init__.py @@ -1,7 +1,19 @@ """King Arthur is Python Discord's DevOps utility bot.""" +import asyncio +import os from functools import partial +from typing import TYPE_CHECKING import loguru +if TYPE_CHECKING: + from arthur.bot import KingArthur + logger = loguru.logger.opt(colors=True) logger.opt = partial(logger.opt, colors=True) + +# On Windows, the selector event loop is required for aiodns. +if os.name == "nt": + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + +instance: "KingArthur" = None # Global Bot instance. diff --git a/arthur/__main__.py b/arthur/__main__.py index 7f95210..bc09f09 100644 --- a/arthur/__main__.py +++ b/arthur/__main__.py @@ -1,16 +1,37 @@ """Entrypoint for King Arthur.""" -from arthur import logger +import asyncio + +import aiohttp +import discord +from discord.ext import commands + +import arthur from arthur.bot import KingArthur from arthur.config import CONFIG -def start() -> None: - """Entrypoint for King Arthur.""" - arthur = KingArthur() +async def main() -> None: + """Entry async method for starting the bot.""" + intents = discord.Intents.default() + intents.message_content = True + intents.dm_typing = False + intents.dm_reactions = False + intents.invites = False + intents.webhooks = False + intents.integrations = False - arthur.run(CONFIG.token) + async with aiohttp.ClientSession() as session: + arthur.instance = KingArthur( + guild_id=CONFIG.guild_id, + http_session=session, + command_prefix=commands.when_mentioned_or(*CONFIG.prefixes), + allowed_roles=(CONFIG.devops_role,), + case_insensitive=True, + intents=intents, + ) + async with arthur.instance as bot: + await bot.start(CONFIG.token) -if __name__ == "__main__": - start() +with arthur.logger.catch(): + asyncio.run(main()) diff --git a/arthur/bot.py b/arthur/bot.py index 2bbb69c..2220db6 100644 --- a/arthur/bot.py +++ b/arthur/bot.py @@ -2,27 +2,20 @@ from pathlib import Path from typing import Any, Union +from botcore import BotBase +from botcore.utils import scheduling from discord import Interaction, Member, User from discord.ext import commands -from discord.ext.commands import Bot from kubernetes_asyncio import config -from arthur import logger +from arthur import exts, logger from arthur.config import CONFIG -from arthur.extensions import find_extensions -class KingArthur(Bot): +class KingArthur(BotBase): """Base bot class for King Arthur.""" def __init__(self, *args: list[Any], **kwargs: dict[str, Any]) -> None: - config = { - "command_prefix": commands.when_mentioned_or(*CONFIG.prefixes), - "case_insensitive": True, - } - - kwargs.update(config) - super().__init__(*args, **kwargs) self.add_check(self._is_devops) @@ -31,7 +24,10 @@ class KingArthur(Bot): def _is_devops(ctx: Union[commands.Context, Interaction]) -> bool: """Check all commands are executed by authorised personnel.""" if isinstance(ctx, Interaction): - return CONFIG.devops_role in [r.id for r in ctx.author.roles] + if isinstance(ctx.user, Member): + return CONFIG.devops_role in [r.id for r in ctx.user.roles] + else: + return False if ctx.command.name == "ed": return True @@ -41,37 +37,23 @@ class KingArthur(Bot): return CONFIG.devops_role in [r.id for r in ctx.author.roles] - async def on_ready(self) -> None: - """Initialise bot once connected and authorised with Discord.""" + async def setup_hook(self) -> None: + """Async initialisation method for discord.py.""" + await super().setup_hook() + # Authenticate with Kubernetes if (Path.home() / ".kube/config").exists(): await config.load_kube_config() else: config.load_incluster_config() - logger.info(f"Logged in <red>{self.user}</>") - # Start extension loading - - for path, extension in find_extensions(): - logger.info( - f"Loading extension <magenta>{path.stem}</> " f"from <magenta>{path.parent}</>" - ) - - try: - self.load_extension(extension) - except: # noqa: E722 - logger.exception( - f"Failed to load extension <magenta>{path.stem}</> " - f"from <magenta>{path.parent}</>", - ) - else: - logger.info( - f"Loaded extension <magenta>{path.stem}</> " f"from <magenta>{path.parent}</>" - ) + # This is not awaited to avoid a deadlock with any cogs that have + # wait_until_guild_available in their cog_load method. + scheduling.create_task(self.load_extensions(exts)) logger.info("Loading <red>jishaku</red>") - self.load_extension("jishaku") + await self.load_extension("jishaku") logger.info("Loaded <red>jishaku</red>") async def is_owner(self, user: Union[User, Member]) -> bool: diff --git a/arthur/config.py b/arthur/config.py index a17980f..1fcf11d 100644 --- a/arthur/config.py +++ b/arthur/config.py @@ -17,6 +17,9 @@ class Config(BaseSettings): # Token for authorising with the Cloudflare API cloudflare_token: str + # Guild id + guild_id: int = 267624335836053506 + class Config: # noqa: D106 env_file = ".env" env_prefix = "KING_ARTHUR_" diff --git a/arthur/extensions.py b/arthur/extensions.py deleted file mode 100644 index 2170d68..0000000 --- a/arthur/extensions.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Utilities for working with extensions.""" -from pathlib import Path -from typing import Generator - - -def find_extensions() -> Generator[tuple[Path, str], None, None]: - """Search the exts directory to find cogs to load.""" - for path in Path("arthur/exts").rglob("**/*.py"): - # Convert a path like "arthur/exts/foo/bar.py" to "arthur.exts.foo.bar" - yield path, path_to_module(path) - - -def path_to_module(path: Path) -> str: - """Convert a path like "arthur/exts/foo/bar.py" to "arthur.exts.foo.bar".""" - return str(path.parent.as_posix()).replace("/", ".") + f".{path.stem}" diff --git a/arthur/exts/__init__.py b/arthur/exts/__init__.py new file mode 100644 index 0000000..5428188 --- /dev/null +++ b/arthur/exts/__init__.py @@ -0,0 +1 @@ +"""Package of all extensions to load on bot startup.""" diff --git a/arthur/exts/cloudflare/__init__.py b/arthur/exts/cloudflare/__init__.py new file mode 100644 index 0000000..f2fbac3 --- /dev/null +++ b/arthur/exts/cloudflare/__init__.py @@ -0,0 +1 @@ +"""Extensions related to Cloudflare.""" diff --git a/arthur/exts/error_handler/__init__.py b/arthur/exts/error_handler/__init__.py new file mode 100644 index 0000000..af8bc5e --- /dev/null +++ b/arthur/exts/error_handler/__init__.py @@ -0,0 +1 @@ +"""Error handling extensions.""" diff --git a/arthur/exts/fun/__init__.py b/arthur/exts/fun/__init__.py new file mode 100644 index 0000000..9aada5d --- /dev/null +++ b/arthur/exts/fun/__init__.py @@ -0,0 +1 @@ +"""Extensions made just for fun.""" diff --git a/arthur/exts/kubernetes/__init__.py b/arthur/exts/kubernetes/__init__.py new file mode 100644 index 0000000..2825b98 --- /dev/null +++ b/arthur/exts/kubernetes/__init__.py @@ -0,0 +1 @@ +"""Extensions relates to Kubernetes.""" |