refactor: async I/O, input sanitisation, and shared utilities cleanup

This commit is contained in:
2026-05-08 22:59:31 -05:00
parent 52a9621d50
commit f822d6a450
21 changed files with 1351 additions and 2709 deletions
+67 -37
View File
@@ -1,60 +1,90 @@
"""
Provides a command to fetch random xkcd comic
Provides a command to fetch random or specific xkcd comics.
Usage: !xkcd -> random comic
!xkcd <number> -> comic #<number> (e.g. !xkcd 538)
"""
import requests
import aiohttp
import tempfile
import random
import os
import simplematrixbotlib as botlib
# Define the XKCD API URL
XKCD_API_URL = "https://xkcd.com/info.0.json"
XKCD_LATEST_URL = "https://xkcd.com/info.0.json"
XKCD_COMIC_URL = "https://xkcd.com/{}/info.0.json"
async def handle_command(room, message, bot, prefix, config):
match = botlib.MessageMatch(room, message, bot, prefix)
if match.prefix() and match.command("xkcd"):
# Fetch the latest comic number from XKCD API
try:
response = requests.get(XKCD_API_URL, timeout=10)
response.raise_for_status() # Raise an exception for non-200 status codes
latest_comic_num = response.json()["num"]
# Choose a random comic number
random_comic_num = random.randint(1, latest_comic_num)
# Fetch the random comic data
random_comic_url = f"https://xkcd.com/{random_comic_num}/info.0.json"
comic_response = requests.get(random_comic_url, timeout=10)
comic_response.raise_for_status()
comic_data = comic_response.json()
if not (match.prefix() and match.command("xkcd")):
return
args = match.args()
try:
async with aiohttp.ClientSession() as session:
# Get latest comic number
async with session.get(XKCD_LATEST_URL, timeout=10) as resp:
resp.raise_for_status()
latest_data = await resp.json()
latest_num = latest_data["num"]
# Determine target comic number
if args and args[0].isdigit():
requested_num = int(args[0])
if requested_num < 1 or requested_num > latest_num:
await bot.api.send_text_message(
room.room_id,
f"❌ Comic #{requested_num} doesn't exist. Valid range: 1 {latest_num}."
)
return
comic_num = requested_num
else:
comic_num = random.randint(1, latest_num)
# Fetch the comic data
comic_url = XKCD_COMIC_URL.format(comic_num)
async with session.get(comic_url, timeout=10) as resp:
resp.raise_for_status()
comic_data = await resp.json()
image_url = comic_data["img"]
title = comic_data.get("safe_title", comic_data.get("title", "xkcd"))
alt = comic_data.get("alt", "")
# Download the image
image_response = requests.get(image_url, timeout=10)
image_response.raise_for_status()
async with session.get(image_url, timeout=10) as img_resp:
img_resp.raise_for_status()
image_data = await img_resp.read()
# Use secure temporary file
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file:
image_path = temp_file.name
temp_file.write(image_response.content)
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
tmp.write(image_data)
img_path = tmp.name
# Send the image to the room
await bot.api.send_image_message(room_id=room.room_id, image_filepath=image_path)
# Send image
await bot.api.send_image_message(room_id=room.room_id, image_filepath=img_path)
# Clean up temp file
import os
os.remove(image_path)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error fetching XKCD comic: {str(e)}")
# Send comic info as text (optional but helpful)
info = f"**#{comic_num} {title}**"
if alt:
info += f"\n*{alt}*"
await bot.api.send_markdown_message(room.room_id, info)
os.remove(img_path)
# ---------------------------------------------------------------------------
# Plugin Metadata
# ---------------------------------------------------------------------------
except aiohttp.ClientError as e:
await bot.api.send_text_message(room.room_id, f"❌ Network error fetching xkcd: {e}")
except Exception as e:
await bot.api.send_text_message(room.room_id, f"❌ Error: {str(e)}")
__version__ = "1.0.0"
__version__ = "1.1.0"
__author__ = "Funguy Bot"
__description__ = "Random XKCD comic"
__description__ = "Fetch random or specific xkcd comics"
__help__ = """
<details>
<summary><strong>!xkcd</strong> Random XKCD comic</summary>
<p>Posts a random XKCD comic image.</p>
<summary><strong>!xkcd</strong> xkcd comics</summary>
<ul>
<li><code>!xkcd</code> random comic</li>
<li><code>!xkcd &lt;number&gt;</code> specific comic (e.g. <code>!xkcd 538</code>)</li>
</ul>
</details>
"""