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
+45 -165
View File
@@ -1,212 +1,92 @@
"""
This plugin provides a command to fetch definitions from Urban Dictionary.
Urban Dictionary definitions.
"""
import logging
import requests
import aiohttp
import simplematrixbotlib as botlib
import html
from plugins.common import html_escape
URBAN_API_URL = "https://api.urbandictionary.com/v0/define"
RANDOM_API_URL = "https://api.urbandictionary.com/v0/random"
def format_definition(term, definition, example, author, thumbs_up, thumbs_down, permalink, index=None, total=None):
"""
Format an Urban Dictionary definition for display.
safe_term = html_escape(term)
safe_author = html_escape(author)
# definition and example may contain [word] markup, we'll just escape all HTML
definition = html.escape(definition).replace('[', '<strong>').replace(']', '</strong>')
example = html.escape(example).replace('[', '<em>').replace(']', '</em>')
Args:
term (str): The term being defined.
definition (str): The definition text.
example (str): Example usage.
author (str): Author of the definition.
thumbs_up (int): Number of upvotes.
thumbs_down (int): Number of downvotes.
permalink (str): URL to the definition.
index (int, optional): Current definition index.
total (int, optional): Total number of definitions.
Returns:
str: Formatted HTML message.
"""
# Clean up the text - Urban Dictionary uses [brackets] for links
definition = definition.replace('[', '<strong>').replace(']', '</strong>')
example = example.replace('[', '<em>').replace(']', '</em>')
# Escape any HTML that might be in the original text
term = html.escape(term)
author = html.escape(author)
# Build the message
header = f"<strong>📖 Urban Dictionary: {term}</strong>"
if index is not None and total is not None:
header = f"<strong>📖 Urban Dictionary: {safe_term}</strong>"
if index and total:
header += f" (Definition {index}/{total})"
message = f"""{header}
<strong>Definition:</strong>
{definition}
"""
if example and example.strip():
message += f"""
<strong>Example:</strong>
<em>{example}</em>
"""
message += f"""
<strong>Author:</strong> {author} | 👍 {thumbs_up} 👎 {thumbs_down}
<a href="{permalink}">View on Urban Dictionary</a>
"""
return message
msg = f"""{header}
<strong>Definition:</strong><br>{definition}<br>"""
if example.strip():
msg += f"""<strong>Example:</strong><br><em>{example}</em><br>"""
msg += f"""<strong>Author:</strong> {safe_author} | 👍 {thumbs_up} 👎 {thumbs_down}<br>
<a href="{permalink}">View on Urban Dictionary</a>"""
return msg
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !ud (Urban Dictionary) command.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot object.
prefix (str): The command prefix.
config (dict): Configuration parameters.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("ud"):
logging.info("Received !ud command")
args = match.args()
try:
# Case 1: No arguments - get random definition
if len(args) == 0:
logging.info("Fetching random Urban Dictionary definition")
response = requests.get(RANDOM_API_URL, timeout=10)
response.raise_for_status()
data = response.json()
# random
async with aiohttp.ClientSession() as session:
async with session.get(RANDOM_API_URL, timeout=10) as resp:
resp.raise_for_status()
data = await resp.json()
if not data.get('list'):
await bot.api.send_text_message(room.room_id, "No random definition found.")
return
# Get first random entry
entry = data['list'][0]
formatted = format_definition(
term=entry['word'],
definition=entry['definition'],
example=entry.get('example', ''),
author=entry['author'],
thumbs_up=entry['thumbs_up'],
thumbs_down=entry['thumbs_down'],
permalink=entry['permalink']
)
await bot.api.send_markdown_message(room.room_id, formatted)
logging.info(f"Sent random definition: {entry['word']}")
msg = format_definition(entry['word'], entry['definition'], entry.get('example',''),
entry['author'], entry['thumbs_up'], entry['thumbs_down'],
entry['permalink'])
await bot.api.send_markdown_message(room.room_id, msg)
return
# Case 2: One or more arguments - search for term
# Check if last argument is a number (definition index)
# Search
index = None
search_term = ' '.join(args)
if args[-1].isdigit():
index = int(args[-1])
search_term = ' '.join(args[:-1])
if not search_term:
await bot.api.send_text_message(
room.room_id,
"Usage: !ud [term] [index]\nExamples:\n !ud - random definition\n !ud yeet - first definition of 'yeet'\n !ud yeet 2 - second definition of 'yeet'"
)
await bot.api.send_text_message(room.room_id, "Usage: !ud [term] [index]")
return
logging.info(f"Searching Urban Dictionary for: {search_term}")
params = {'term': search_term}
response = requests.get(URBAN_API_URL, params=params, timeout=10)
response.raise_for_status()
data = response.json()
async with aiohttp.ClientSession() as session:
async with session.get(URBAN_API_URL, params={'term': search_term}, timeout=10) as resp:
resp.raise_for_status()
data = await resp.json()
definitions = data.get('list', [])
if not definitions:
await bot.api.send_text_message(
room.room_id,
f"No definition found for '{search_term}'"
)
logging.info(f"No definition found for: {search_term}")
await bot.api.send_text_message(room.room_id, f"No definition for '{html_escape(search_term)}'")
return
total = len(definitions)
# If no index specified, use first definition
if index is None:
index = 1
# Validate index
if index < 1 or index > total:
await bot.api.send_text_message(
room.room_id,
f"Invalid index. '{search_term}' has {total} definition(s). Use !ud {search_term} [1-{total}]"
)
await bot.api.send_text_message(room.room_id, f"Index out of range (1-{total})")
return
# Get the requested definition (convert to 0-based index)
entry = definitions[index - 1]
msg = format_definition(entry['word'], entry['definition'], entry.get('example',''),
entry['author'], entry['thumbs_up'], entry['thumbs_down'],
entry['permalink'], index, total)
await bot.api.send_markdown_message(room.room_id, msg)
formatted = format_definition(
term=entry['word'],
definition=entry['definition'],
example=entry.get('example', ''),
author=entry['author'],
thumbs_up=entry['thumbs_up'],
thumbs_down=entry['thumbs_down'],
permalink=entry['permalink'],
index=index,
total=total
)
await bot.api.send_markdown_message(room.room_id, formatted)
logging.info(f"Sent definition {index}/{total} for: {search_term}")
except requests.exceptions.Timeout:
await bot.api.send_text_message(
room.room_id,
"Request timed out. Urban Dictionary may be slow or unavailable."
)
logging.error("Urban Dictionary API timeout")
except requests.exceptions.RequestException as e:
await bot.api.send_text_message(
room.room_id,
f"Error fetching from Urban Dictionary: {e}"
)
logging.error(f"Error fetching from Urban Dictionary: {e}")
except aiohttp.ClientError as e:
await bot.api.send_text_message(room.room_id, f"Error: {e}")
except Exception as e:
await bot.api.send_text_message(
room.room_id,
"An error occurred while processing the Urban Dictionary request."
)
logging.error(f"Unexpected error in Urban Dictionary plugin: {e}", exc_info=True)
await bot.api.send_text_message(room.room_id, f"Error: {str(e)}")
# ---------------------------------------------------------------------------
# Plugin Metadata
# ---------------------------------------------------------------------------
__version__ = "1.0.0"
__version__ = "1.0.1"
__author__ = "Funguy Bot"
__description__ = "Urban Dictionary definitions"
__help__ = """
<details>
<summary><strong>!ud</strong> Urban Dictionary</summary>
<ul>
<li><code>!ud</code> Random definition</li>
<li><code>!ud &lt;term&gt;</code> Top definition</li>
<li><code>!ud &lt;term&gt; &lt;index&gt;</code> Nth definition</li>
</ul>
</details>
"""
__description__ = "Urban Dictionary definitions (async)"
__help__ = """<details><summary><strong>!ud</strong> Urban Dictionary</summary>
<ul><li><code>!ud</code> random, <code>!ud &lt;term&gt;</code> top, <code>!ud &lt;term&gt; &lt;index&gt;</code></li></ul></details>"""