aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Joe Banks <[email protected]>2024-05-31 18:17:05 +0100
committerGravatar GitHub <[email protected]>2024-05-31 18:17:05 +0100
commitbe57dd9e4c2854a767de59edd399794d808b56df (patch)
treefa5de9582207c02c10c88cc1ca731fc23da37abf
parentSwap from custom trashcan emoji to :wastebasket: (diff)
parentAdd 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__.py1
-rw-r--r--arthur/apis/systems/lib9front.py100
-rw-r--r--arthur/exts/systems/__init__.py1
-rw-r--r--arthur/exts/systems/system_information.py80
-rw-r--r--pyproject.toml6
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