diff options
| -rw-r--r-- | arthur/config.py | 5 | ||||
| -rw-r--r-- | arthur/exts/fun/devops_rules.py | 108 |
2 files changed, 113 insertions, 0 deletions
diff --git a/arthur/config.py b/arthur/config.py index 1fcf11d..d754696 100644 --- a/arthur/config.py +++ b/arthur/config.py @@ -1,4 +1,6 @@ """Utilities for interacting with the config for King Arthur.""" +from typing import Optional + from pydantic import BaseSettings @@ -20,6 +22,9 @@ class Config(BaseSettings): # Guild id guild_id: int = 267624335836053506 + # Token for authorising with the Notion API + notion_api_token: Optional[str] = None + class Config: # noqa: D106 env_file = ".env" env_prefix = "KING_ARTHUR_" diff --git a/arthur/exts/fun/devops_rules.py b/arthur/exts/fun/devops_rules.py new file mode 100644 index 0000000..9c231d8 --- /dev/null +++ b/arthur/exts/fun/devops_rules.py @@ -0,0 +1,108 @@ +"""The rules all devops members must follow.""" + +from typing import TypedDict + +import discord +from discord.ext.commands import Cog, Context, Greedy, command + +from arthur.bot import KingArthur, logger +from arthur.config import CONFIG + +NOTION_API_BASE_URL = "https://api.notion.com/v1" +DEVOPS_RULES_PAGE_CONTENT = ( + f"{NOTION_API_BASE_URL}/blocks/149bc48f-6f79-47af-add8-036f11d4e9a7/children" +) +DISCORD_MARKDOWN_LOOKUP = { + "bold": "**{}**", + "italic": "_{}_", + "strikethrough": "~~{}~~", + "underline": "__{}__", +} + + +class NotionAnnotations(TypedDict): + """The markdown annotations attached to a block of text in Notion.""" + + bold: bool + italic: bool + strikethrough: bool + underline: bool + + +class NotionRichText(TypedDict): + """A block of text with markdown annotations attached.""" + + plain_text: str + annotations: NotionAnnotations + + +def notion_block_to_discord_markdown(block: list[NotionRichText]) -> str: + """Convert the given notion API "block" into Discord markdown text.""" + block_string_parts = [] + for rich_text_part in block: + block_string_part = rich_text_part["plain_text"] + for annotation, enabled in rich_text_part["annotations"].items(): + if enabled and annotation in DISCORD_MARKDOWN_LOOKUP: + block_string_part = DISCORD_MARKDOWN_LOOKUP[annotation].format(block_string_part) + block_string_parts.append(block_string_part) + return "".join(block_string_parts) + + +class Rules(Cog): + """The rules all devops members must follow.""" + + def __init__(self, bot: KingArthur) -> None: + self.bot = bot + self.rules: dict + + async def cog_load(self) -> None: + """Fetch Devops rules from notion of cog load.""" + headers = { + "Authorization": f"Bearer {CONFIG.notion_api_token}", + "accept": "application/json", + "Notion-Version": "2022-06-28", + } + async with self.bot.http_session.get(DEVOPS_RULES_PAGE_CONTENT, headers=headers) as resp: + resp.raise_for_status() + page_content = await resp.json() + + self.rules = { + i: notion_block_to_discord_markdown(block["numbered_list_item"]["rich_text"]) + for i, block in enumerate(page_content["results"], 1) + if block.get("type") == "numbered_list_item" + } + + @command(name="rules", aliases=("rule",)) + async def get_rules(self, ctx: Context, rules: Greedy[int]) -> None: + """List the requested rule(s), or all of them if not defined.""" + if rules: + output_rules = set(rules) & set(self.rules.keys()) + else: + output_rules = self.rules.keys() + + if not output_rules: + await ctx.send(f":x: Rule{'s'[:len(rules)^1]} not found.") + return + + output = "\n".join( + f"{key}: {value}" + for key, value in self.rules.items() + if key in output_rules + ) + await ctx.send(embed=discord.Embed( + title=f"Rule{'s'[:len(output_rules)^1]}", + description=output, + colour=discord.Colour.og_blurple(), + url="https://www.notion.so/pythondiscord/Rules-149bc48f6f7947afadd8036f11d4e9a7", + )) + + +async def setup(bot: KingArthur) -> None: + """Add cog to bot.""" + if not CONFIG.notion_api_token: + logger.info( + f"Not loading {__name__} as env var " + f"{CONFIG.Config.env_prefix}NOTION_API_TOKEN is not set." + ) + return + await bot.add_cog(Rules(bot)) |