diff --git a/funguy.py b/funguy.py
index fa4aeaf..34c8d8f 100755
--- a/funguy.py
+++ b/funguy.py
@@ -20,7 +20,7 @@ from plugins.config import FunguyConfig
# Whitelist of allowed plugins to prevent arbitrary code execution
ALLOWED_PLUGINS = {'ai', 'config', 'cron', 'date', 'fortune', 'help', 'isup', 'karma',
'loadplugin', 'plugins', 'proxy', 'sd_text', 'stable-diffusion',
- 'xkcd', 'youtube-preview', 'youtube-search', 'weather'}
+ 'xkcd', 'youtube-preview', 'youtube-search', 'weather', 'urbandictionary'}
class FunguyBot:
"""
diff --git a/plugins/loadplugin.py b/plugins/loadplugin.py
index 592e609..a0b5bd5 100644
--- a/plugins/loadplugin.py
+++ b/plugins/loadplugin.py
@@ -56,7 +56,8 @@ async def load_plugin(plugin_name):
'xkcd': 'plugins.xkcd',
'youtube-preview': 'plugins.youtube-preview',
'youtube-search': 'plugins.youtube-search',
- 'weather': 'plugins.weather'
+ 'weather': 'plugins.weather',
+ 'urbandictionary': 'plugins.urbandictionary'
}
# Get the module path from the mapping
diff --git a/plugins/urbandictionary.py b/plugins/urbandictionary.py
new file mode 100644
index 0000000..08b364d
--- /dev/null
+++ b/plugins/urbandictionary.py
@@ -0,0 +1,193 @@
+"""
+This plugin provides a command to fetch definitions from Urban Dictionary.
+"""
+
+import logging
+import requests
+import simplematrixbotlib as botlib
+import html
+
+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.
+
+ 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('[', '').replace(']', '')
+ example = example.replace('[', '').replace(']', '')
+
+ # Escape any HTML that might be in the original text
+ term = html.escape(term)
+ author = html.escape(author)
+
+ # Build the message
+ header = f"📖 Urban Dictionary: {term}"
+ if index is not None and total is not None:
+ header += f" (Definition {index}/{total})"
+
+ message = f"""{header}
+Definition:
+{definition}
+"""
+ if example and example.strip():
+ message += f"""
+Example:
+{example}
+"""
+ message += f"""
+Author: {author} | 👍 {thumbs_up} 👎 {thumbs_down}
+View on Urban Dictionary
+"""
+
+ return message
+
+
+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()
+
+ 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']}")
+ return
+
+ # Case 2: One or more arguments - search for term
+ # Check if last argument is a number (definition index)
+ 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'"
+ )
+ 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()
+
+ 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}")
+ 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}]"
+ )
+ return
+
+ # Get the requested definition (convert to 0-based index)
+ entry = definitions[index - 1]
+
+ 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 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)