diff options
| author | 2024-05-31 18:17:05 +0100 | |
|---|---|---|
| committer | 2024-05-31 18:17:05 +0100 | |
| commit | be57dd9e4c2854a767de59edd399794d808b56df (patch) | |
| tree | fa5de9582207c02c10c88cc1ca731fc23da37abf | |
| parent | Swap from custom trashcan emoji to :wastebasket: (diff) | |
| parent | Add new System Information extension (diff) | |
Merge pull request #193 from python-discord/system-information-support
Add support for parsing system information
| -rw-r--r-- | arthur/apis/systems/__init__.py | 1 | ||||
| -rw-r--r-- | arthur/apis/systems/lib9front.py | 100 | ||||
| -rw-r--r-- | arthur/exts/systems/__init__.py | 1 | ||||
| -rw-r--r-- | arthur/exts/systems/system_information.py | 80 | ||||
| -rw-r--r-- | pyproject.toml | 6 |
5 files changed, 188 insertions, 0 deletions
diff --git a/arthur/apis/systems/__init__.py b/arthur/apis/systems/__init__.py new file mode 100644 index 0000000..302f80c --- /dev/null +++ b/arthur/apis/systems/__init__.py @@ -0,0 +1 @@ +"""Functionality for working with various operating systems.""" diff --git a/arthur/apis/systems/lib9front.py b/arthur/apis/systems/lib9front.py new file mode 100644 index 0000000..a527a6e --- /dev/null +++ b/arthur/apis/systems/lib9front.py @@ -0,0 +1,100 @@ +"""Library functions for working with 9front systems and file formats.""" + +import random + + +def generate_blog_comment(blogcom: str) -> str: + """ + Generate a blog comment out of the ``blogcom`` file contents that are passed in. + + The blogcom file can be retrieved at ``/lib/blogcom`` in 9front. + + This function is implemented according to the highest standards in + performance and security, and to be able to generate properly random texts, + it utilizes a 623-dimensionally equidistributed uniform pseudorandom number + generator as described by Makoto Matsumoto and Takuji Nishimura. In other + words, for our purposes, this function matches or even exceeds Python + Discord's security requirements. + """ + fragment = random.choice(blogcom.split("|\n")) + # Complete output buffer + out = [] + # Options of the current branch, of which one will be selected at random + options = [] + # Character buffer of the current choice + choice_buf = [] + # Whether we are in a {block|of|options} at the moment + in_block = False + + for char in fragment: + if char == "{": + in_block = True + elif in_block and char == "|": + options.append("".join(choice_buf)) + choice_buf.clear() + elif in_block and char == "}": + options.append("".join(choice_buf)) + choice_buf.clear() + out.append(random.choice(options)) + options.clear() + in_block = False + elif in_block: + choice_buf.append(char) + else: + out.append(char) + + return "".join(out) + + +def generate_buzzwords(bullshit: str) -> str: + """ + Generates buzzwords to describe a random product of the ``bullshit`` file contents that are passed in. + + The bullshit file can be retrieved at ``/lib/bullshit`` in 9front. + + This function underlies the same security guarantees as ``generate_blog_comment``. + """ + # line markers + # nothing -> word + # ^ -> start + # * -> protocol + # % -> suffix + # | -> adjectives + # $ -> end + words = [] + starters = [] + protocols = [] + suffixes = [] + adjectives = [] + endings = [] + + # Parsing + for line in bullshit.splitlines(): + if " " not in line: + words.append(line) + else: + word, qualifier = line.split() + if qualifier == "^": + starters.append(word) + elif qualifier == "*": + protocols.append(word) + elif qualifier == "%": + suffixes.append(word) + elif qualifier == "|": + adjectives.append(word) + elif qualifier == "$": + endings.append(word) + + # Generating + response = [] + for _ in range(random.randint(1, 2)): + response.append(random.choice(starters)) + for _ in range(random.randint(1, 2)): + response.append(random.choice(adjectives)) + for _ in range(random.randint(1, 2)): + response.append(random.choice(words) + random.choice(suffixes) * random.randint(0, 1)) + if random.random() > 0.5: + response.append("over " + random.choice(protocols)) + if random.random() > 0.3: + response.append(random.choice(endings)) + return " ".join(response) diff --git a/arthur/exts/systems/__init__.py b/arthur/exts/systems/__init__.py new file mode 100644 index 0000000..fa87b0c --- /dev/null +++ b/arthur/exts/systems/__init__.py @@ -0,0 +1 @@ +"""Extensions for fetching system information from our production 9front deployment.""" diff --git a/arthur/exts/systems/system_information.py b/arthur/exts/systems/system_information.py new file mode 100644 index 0000000..83e3daa --- /dev/null +++ b/arthur/exts/systems/system_information.py @@ -0,0 +1,80 @@ +"""Return system information on our production 9front infrastructure.""" + +import random +from datetime import datetime + +import aiohttp +from discord import Message +from discord.ext.commands import Cog +from loguru import logger + +from arthur.apis.systems import lib9front +from arthur.bot import KingArthur +from arthur.config import CONFIG + +BLOGCOM = "https://git.9front.org/plan9front/plan9front/HEAD/lib/blogcom/raw" +THRESHOLD = 0.01 +MIN_MINUTES = 30 +BLOG_ABOUT_IT_THRESHOLD = 1000 + + +class SystemInformation(Cog): + """Utilities for fetching system information from our 9front infrastructure.""" + + def __init__(self, bot: KingArthur) -> None: + self.bot = bot + self.cached_data = None + self.last_sent = None + + async def fetch_blogcom(self) -> str: + """Fetch the blogcom file from the upstream location, or return the cached copy.""" + if not self.cached_data: + async with aiohttp.ClientSession() as session, session.get(BLOGCOM) as resp: + self.cached_data = await resp.text() + + return self.cached_data + + @Cog.listener() + async def on_message(self, msg: Message) -> None: + """Handler for incoming messages, potentially returning system information.""" + if not msg.guild: + logger.trace("Ignoring DM") + return + + if msg.author.id == self.bot.user.id: + logger.trace("Ignoring own message") + return + + if msg.channel.id != CONFIG.devops_channel_id: + logger.trace("Ignoring message outside of DevOps channel") + return + + if self.last_sent: + if (datetime.utcnow() - self.last_sent).minutes < MIN_MINUTES: + logger.trace("Ignoring message as within cooldown") + return + + msg_thresh = THRESHOLD + + if CONFIG.devops_role not in (r.id for r in msg.author.roles): + logger.trace("Upping threshold due to non-DevOps member") + msg_thresh *= 10 + + if len(msg.content) > BLOG_ABOUT_IT_THRESHOLD: + msg_thresh += 0.10 + + if random.random() < msg_thresh: + logger.trace("Criteria hit, generating comment.") + + blogcom = await self.fetch_blogcom() + + comment = lib9front.generate_blog_comment(blogcom).strip() + + await msg.reply(f"{comment} :smile:") + + self.last_sent = datetime.utcnow() + + +async def setup(bot: KingArthur) -> None: + """Add cog to bot.""" + await bot.add_cog(SystemInformation(bot)) diff --git a/pyproject.toml b/pyproject.toml index b5653b8..86f769c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,11 +62,17 @@ ignore = [ "SIM102", "SIM108", "PD", "PLR0913", "PLR0917", "PLR6301", + "DTZ003", # Rules suggested to be ignored when using ruff format "COM812", "D206", "E111", "E114", "E117", "E501", "ISC001", "Q000", "Q001", "Q002", "Q003", "W191", ] +[tool.ruff.lint.per-file-ignores] +# McCabe is not supported on Linux systems +"arthur/apis/systems/*.py" = ["C901", "PLR0912", "PERF401", "PLR2004"] + + [tool.ruff.lint.isort] order-by-type = false case-sensitive = true |