Infermatic output cleanup. Added dictionary and joke plugins. Updated Readme.
This commit is contained in:
@@ -106,6 +106,7 @@ The bot includes the following plugins:
|
|||||||
- **cron.py**: In‑process cron scheduler (room‑aware, no system crontab)
|
- **cron.py**: In‑process cron scheduler (room‑aware, no system crontab)
|
||||||
- **date.py**: Show current date and time
|
- **date.py**: Show current date and time
|
||||||
- **ddg.py**: DuckDuckGo search – collapsible results (ddgs library, no API key)
|
- **ddg.py**: DuckDuckGo search – collapsible results (ddgs library, no API key)
|
||||||
|
- **dictionary.py** Dictionary plugin to fetch word definitions from dict
|
||||||
- **dns.py**: DNS reconnaissance
|
- **dns.py**: DNS reconnaissance
|
||||||
- **dnsdumpster.py**: DNSDumpster domain reconnaissance
|
- **dnsdumpster.py**: DNSDumpster domain reconnaissance
|
||||||
- **exploitdb.py**: Exploit-DB search
|
- **exploitdb.py**: Exploit-DB search
|
||||||
@@ -118,6 +119,7 @@ The bot includes the following plugins:
|
|||||||
- **imdb.py**: IMDb lookup via OMDb API
|
- **imdb.py**: IMDb lookup via OMDb API
|
||||||
- **infermatic-text.py**: AI text generation via Infermatic API
|
- **infermatic-text.py**: AI text generation via Infermatic API
|
||||||
- **isup.py**: Check if a site is up
|
- **isup.py**: Check if a site is up
|
||||||
|
- ***joke.py**: Get a random joke from the joke APIs
|
||||||
- **karma.py**: Room karma tracking system (display names only, no Matrix IDs)
|
- **karma.py**: Room karma tracking system (display names only, no Matrix IDs)
|
||||||
- **lastfm.py**: Last.fm integration
|
- **lastfm.py**: Last.fm integration
|
||||||
- **loadplugin.py**: Load/unload plugins at runtime
|
- **loadplugin.py**: Load/unload plugins at runtime
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
"""
|
||||||
|
Plugin for looking up word definitions using the system's dict command.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
import simplematrixbotlib as botlib
|
||||||
|
|
||||||
|
async def handle_command(room, message, bot, prefix, config):
|
||||||
|
"""
|
||||||
|
Function to handle the !define 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("define"):
|
||||||
|
args = match.args()
|
||||||
|
if not args:
|
||||||
|
await bot.api.send_text_message(room.room_id, "Usage: !define <word>")
|
||||||
|
return
|
||||||
|
|
||||||
|
word = ' '.join(args)
|
||||||
|
logging.info(f"Looking up definition for: {word}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use the WordNet dictionary by default for concise definitions
|
||||||
|
result = subprocess.run(
|
||||||
|
['/usr/bin/dict', '-d', 'wn', word],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
# Clean up the output for better presentation
|
||||||
|
definition = result.stdout.strip()
|
||||||
|
# Remove extra newlines and clean up formatting
|
||||||
|
lines = [line for line in definition.split('\n') if line.strip()]
|
||||||
|
formatted_definition = '\n'.join(lines)
|
||||||
|
|
||||||
|
# Send as markdown for better formatting
|
||||||
|
await bot.api.send_markdown_message(room.room_id, f"**Definition of '{word}':**\n```\n{formatted_definition}\n```")
|
||||||
|
elif result.stderr and "No definitions found" in result.stderr:
|
||||||
|
# Try with all dictionaries if WordNet doesn't have it
|
||||||
|
result_all = subprocess.run(
|
||||||
|
['/usr/bin/dict', '-d', 'all', word],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if result_all.returncode == 0 and result_all.stdout.strip():
|
||||||
|
definition = result_all.stdout.strip()
|
||||||
|
lines = [line for line in definition.split('\n') if line.strip()]
|
||||||
|
# Limit output to prevent overly long messages
|
||||||
|
if len(lines) > 20:
|
||||||
|
formatted_definition = '\n'.join(lines[:20]) + f"\n... (truncated, {len(lines)} total lines)"
|
||||||
|
else:
|
||||||
|
formatted_definition = '\n'.join(lines)
|
||||||
|
|
||||||
|
await bot.api.send_markdown_message(room.room_id, f"**Definition of '{word}':**\n```\n{formatted_definition}\n```")
|
||||||
|
else:
|
||||||
|
await bot.api.send_text_message(room.room_id, f"No definition found for '{word}'")
|
||||||
|
else:
|
||||||
|
await bot.api.send_text_message(room.room_id, f"No definition found for '{word}'")
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
await bot.api.send_text_message(room.room_id, "Sorry, the dictionary lookup timed out. Please try again.")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error looking up definition: {e}")
|
||||||
|
await bot.api.send_text_message(room.room_id, f"Error looking up definition for '{word}': {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Look up word definitions using system dictionary"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!define</strong> – Word definitions</summary>
|
||||||
|
<p>Look up definitions of words using multiple dictionaries including WordNet, Webster's, and others.<br>
|
||||||
|
Usage: <code>!define <word></code></p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
+13
-13
@@ -9,6 +9,7 @@ import json
|
|||||||
import simplematrixbotlib as botlib
|
import simplematrixbotlib as botlib
|
||||||
from asyncio import Queue
|
from asyncio import Queue
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
import re
|
||||||
|
|
||||||
# Load environment variables from .env file in the parent directory
|
# Load environment variables from .env file in the parent directory
|
||||||
plugin_dir = os.path.dirname(os.path.abspath(__file__))
|
plugin_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
@@ -65,7 +66,7 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
try:
|
try:
|
||||||
# Extract options manually since argparse doesn't handle mixed positional/optional well
|
# Extract options manually since argparse doesn't handle mixed positional/optional well
|
||||||
temperature = 0.9
|
temperature = 0.9
|
||||||
max_tokens = 2048
|
max_tokens = 512
|
||||||
custom_model = None
|
custom_model = None
|
||||||
prompt_parts = []
|
prompt_parts = []
|
||||||
|
|
||||||
@@ -174,10 +175,11 @@ async def list_models(room, bot):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
await bot.api.send_text_message(room.room_id, f"❌ Unexpected error: {str(e)}")
|
await bot.api.send_text_message(room.room_id, f"❌ Unexpected error: {str(e)}")
|
||||||
|
|
||||||
|
import re # add at the top of the file
|
||||||
|
|
||||||
async def generate_text(room, bot, prompt, model, temperature, max_tokens):
|
async def generate_text(room, bot, prompt, model, temperature, max_tokens):
|
||||||
"""Generate text using the Infermatic AI API."""
|
"""Generate text using the Infermatic AI API."""
|
||||||
try:
|
try:
|
||||||
# Send initial processing message
|
|
||||||
await bot.api.send_text_message(room.room_id, f"📝 Generating text...")
|
await bot.api.send_text_message(room.room_id, f"📝 Generating text...")
|
||||||
|
|
||||||
url = f"{INFERMATIC_API_BASE}/chat/completions"
|
url = f"{INFERMATIC_API_BASE}/chat/completions"
|
||||||
@@ -205,14 +207,13 @@ async def generate_text(room, bot, prompt, model, temperature, max_tokens):
|
|||||||
await bot.api.send_text_message(room.room_id, "No response generated.")
|
await bot.api.send_text_message(room.room_id, "No response generated.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Format the output with collapsible sections
|
# ---- Clean up blank lines that break list rendering ----
|
||||||
output = f"<details><summary><strong>📝 Generated Text (Click to expand)</strong></summary>"
|
# Remove blank lines directly before a list item (number‐dot or hyphen).
|
||||||
output += f"<strong>Model:</strong> <code>{model}</code><br><br>"
|
generated_text = re.sub(r'\n\n(\d+\.)', r'\n\1', generated_text)
|
||||||
output += f"<strong>Prompt:</strong> {prompt}<br><br>"
|
generated_text = re.sub(r'\n\n(- )', r'\n\1', generated_text)
|
||||||
output += f"<strong>Response:</strong><br><br>"
|
|
||||||
output += f"{generated_text}"
|
|
||||||
output += f"</details>"
|
|
||||||
|
|
||||||
|
# Build a pure Markdown message (no HTML)
|
||||||
|
output = f"**Model:** `{model}`\n\n**Prompt:** {prompt}\n\n**Response:**\n\n{generated_text}"
|
||||||
await bot.api.send_markdown_message(room.room_id, output)
|
await bot.api.send_markdown_message(room.room_id, output)
|
||||||
|
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
@@ -227,7 +228,6 @@ async def generate_text(room, bot, prompt, model, temperature, max_tokens):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
await bot.api.send_text_message(room.room_id, f"❌ Error generating text: {str(e)}")
|
await bot.api.send_text_message(room.room_id, f"❌ Error generating text: {str(e)}")
|
||||||
finally:
|
finally:
|
||||||
# Process next queued command
|
|
||||||
if not command_queue.empty():
|
if not command_queue.empty():
|
||||||
next_command = await command_queue.get()
|
next_command = await command_queue.get()
|
||||||
await handle_command(*next_command)
|
await handle_command(*next_command)
|
||||||
@@ -237,9 +237,9 @@ async def generate_text(room, bot, prompt, model, temperature, max_tokens):
|
|||||||
# Plugin Metadata
|
# Plugin Metadata
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
__version__ = "1.0.0"
|
__version__ = "1.0.2"
|
||||||
__author__ = "Funguy Bot"
|
__author__ = "Funguy Bot"
|
||||||
__description__ = "AI text generation via Infermatic API"
|
__description__ = "AI text generation via Infermatic API (pure Markdown output)"
|
||||||
__help__ = """
|
__help__ = """
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>!text</strong> – AI text generation (Infermatic)</summary>
|
<summary><strong>!text</strong> – AI text generation (Infermatic)</summary>
|
||||||
@@ -248,7 +248,7 @@ __help__ = """
|
|||||||
<li><code>!text --list-models</code> – List available models</li>
|
<li><code>!text --list-models</code> – List available models</li>
|
||||||
<li><code>!text --use-model <model> <prompt></code> – Specific model</li>
|
<li><code>!text --use-model <model> <prompt></code> – Specific model</li>
|
||||||
<li><code>--temperature <0.0-1.0></code> – Set creativity (default 0.9)</li>
|
<li><code>--temperature <0.0-1.0></code> – Set creativity (default 0.9)</li>
|
||||||
<li><code>--max-tokens <number></code> – Max output length (default 2048)</li>
|
<li><code>--max-tokens <number></code> – Max output length (default 512)</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Requires <strong>INFERMATIC_API</strong> env var.</p>
|
<p>Requires <strong>INFERMATIC_API</strong> env var.</p>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
"""
|
||||||
|
Plugin for fetching jokes from the Official Joke API.
|
||||||
|
"""
|
||||||
|
# plugins/joke.py
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import simplematrixbotlib as botlib
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
async def handle_command(room, message, bot, prefix, config):
|
||||||
|
"""
|
||||||
|
Function to handle the !joke 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)
|
||||||
|
|
||||||
|
# Handle !joke command
|
||||||
|
if match.is_not_from_this_bot() and match.prefix() and match.command("joke"):
|
||||||
|
args = match.args()
|
||||||
|
|
||||||
|
# Check if user wants a specific category
|
||||||
|
category = "general"
|
||||||
|
if args:
|
||||||
|
category = args[0].lower()
|
||||||
|
if category not in ["general", "programming"]:
|
||||||
|
# If invalid category, use general
|
||||||
|
category = "general"
|
||||||
|
|
||||||
|
logging.info(f"Fetching {category} joke")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Fetch joke from API
|
||||||
|
if category == "programming":
|
||||||
|
url = "https://official-joke-api.appspot.com/jokes/programming/random"
|
||||||
|
else:
|
||||||
|
url = "https://official-joke-api.appspot.com/random_joke"
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
data = await response.json()
|
||||||
|
|
||||||
|
# Handle different response formats
|
||||||
|
if isinstance(data, list) and len(data) > 0:
|
||||||
|
joke = data[0]
|
||||||
|
elif isinstance(data, dict):
|
||||||
|
joke = data
|
||||||
|
else:
|
||||||
|
await bot.api.send_text_message(room.room_id, "Sorry, couldn't fetch a joke right now.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Extract joke parts
|
||||||
|
setup = joke.get("setup", "No setup available")
|
||||||
|
punchline = joke.get("punchline", "No punchline available")
|
||||||
|
|
||||||
|
# Send the joke with a delay for better effect
|
||||||
|
await bot.api.send_text_message(room.room_id, setup)
|
||||||
|
# Add a small delay before the punchline for comedic timing
|
||||||
|
import asyncio
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
await bot.api.send_text_message(room.room_id, f"... {punchline}")
|
||||||
|
else:
|
||||||
|
await bot.api.send_text_message(room.room_id, "Sorry, couldn't fetch a joke right now. Try again later.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error fetching joke: {e}")
|
||||||
|
await bot.api.send_text_message(room.room_id, f"Error fetching joke: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Get random jokes from the Official Joke API"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!joke</strong> – Random jokes</summary>
|
||||||
|
<p>Get random jokes from the Official Joke API.<br>
|
||||||
|
Usage: <code>!joke</code> for a general joke<br>
|
||||||
|
Usage: <code>!joke programming</code> for a programming joke</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
Reference in New Issue
Block a user