refactor: async I/O, input sanitisation, and shared utilities cleanup
This commit is contained in:
+67
-37
@@ -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 <number></code> – specific comic (e.g. <code>!xkcd 538</code>)</li>
|
||||
</ul>
|
||||
</details>
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user