mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-09-01 16:48:26 +00:00
Enhance joke command with length handling and update configuration settings for jokes. Modify help command to support message context for help text retrieval.
This commit is contained in:
+6
-1
@@ -70,7 +70,7 @@ dm_flood_after = 2
|
||||
# Leave empty to use system timezone
|
||||
timezone =
|
||||
|
||||
# Joke command settings
|
||||
[Jokes]
|
||||
# Enable or disable the joke command
|
||||
# true: Joke command is available
|
||||
# false: Joke command is disabled
|
||||
@@ -87,6 +87,11 @@ seasonal_jokes = true
|
||||
# false: Dad joke command is disabled
|
||||
dadjoke_enabled = true
|
||||
|
||||
# Handle long jokes (over 130 characters)
|
||||
# false: Fetch new jokes until we get a short one
|
||||
# true: Split long jokes into multiple messages
|
||||
long_jokes = false
|
||||
|
||||
# Send startup advert when bot finishes initializing
|
||||
# false: No startup advert (default)
|
||||
# zero-hop: Send local broadcast advert
|
||||
|
||||
@@ -105,7 +105,7 @@ class CommandManager:
|
||||
# Check for help requests first (special handling)
|
||||
if content_lower.startswith('help '):
|
||||
command_name = content_lower[5:].strip() # Remove "help " prefix
|
||||
help_text = self.get_help_for_command(command_name)
|
||||
help_text = self.get_help_for_command(command_name, message)
|
||||
matches.append(('help', help_text))
|
||||
return matches
|
||||
elif content_lower == 'help':
|
||||
@@ -302,18 +302,30 @@ class CommandManager:
|
||||
self.logger.error(f"Failed to send channel message: {e}")
|
||||
return False
|
||||
|
||||
def get_help_for_command(self, command_name: str) -> str:
|
||||
def get_help_for_command(self, command_name: str, message: MeshMessage = None) -> str:
|
||||
"""Get help text for a specific command (LoRa-friendly compact format)"""
|
||||
# First, try to find a command by exact name
|
||||
command = self.commands.get(command_name)
|
||||
if command:
|
||||
return f"Help {command_name}: {command.get_help_text()}"
|
||||
# Try to pass message context to get_help_text if supported
|
||||
try:
|
||||
help_text = command.get_help_text(message)
|
||||
except TypeError:
|
||||
# Fallback for commands that don't accept message parameter
|
||||
help_text = command.get_help_text()
|
||||
return f"Help {command_name}: {help_text}"
|
||||
|
||||
# If not found, search through all commands and their keywords
|
||||
for cmd_name, cmd_instance in self.commands.items():
|
||||
# Check if the requested command name matches any of this command's keywords
|
||||
if hasattr(cmd_instance, 'keywords') and command_name in cmd_instance.keywords:
|
||||
return f"Help {command_name}: {cmd_instance.get_help_text()}"
|
||||
# Try to pass message context to get_help_text if supported
|
||||
try:
|
||||
help_text = cmd_instance.get_help_text(message)
|
||||
except TypeError:
|
||||
# Fallback for commands that don't accept message parameter
|
||||
help_text = cmd_instance.get_help_text()
|
||||
return f"Help {command_name}: {help_text}"
|
||||
|
||||
# If still not found, return unknown command message
|
||||
available_commands = []
|
||||
|
||||
@@ -34,7 +34,8 @@ class DadJokeCommand(BaseCommand):
|
||||
self.user_cooldowns = {} # user_id -> last_execution_time
|
||||
|
||||
# Load configuration
|
||||
self.dadjoke_enabled = bot.config.getboolean('Bot', 'dadjoke_enabled', fallback=True)
|
||||
self.dadjoke_enabled = bot.config.getboolean('Jokes', 'dadjoke_enabled', fallback=True)
|
||||
self.long_jokes = bot.config.getboolean('Jokes', 'long_jokes', fallback=False)
|
||||
|
||||
def get_help_text(self) -> str:
|
||||
return "Usage: dadjoke - Get a random dad joke"
|
||||
@@ -101,16 +102,15 @@ class DadJokeCommand(BaseCommand):
|
||||
# Record execution for this user
|
||||
self._record_execution(message.sender_id)
|
||||
|
||||
# Get dad joke from API
|
||||
joke_data = await self.get_dad_joke_from_api()
|
||||
# Get dad joke from API with length handling
|
||||
joke_data = await self.get_dad_joke_with_length_handling()
|
||||
|
||||
if joke_data is None:
|
||||
await self.send_response(message, "Sorry, couldn't fetch a dad joke right now. Try again later!")
|
||||
return True
|
||||
|
||||
# Format and send the joke
|
||||
joke_text = self.format_dad_joke(joke_data)
|
||||
await self.send_response(message, joke_text)
|
||||
# Format and send the joke(s)
|
||||
await self.send_dad_joke_with_length_handling(message, joke_data)
|
||||
|
||||
return True
|
||||
|
||||
@@ -161,6 +161,88 @@ class DadJokeCommand(BaseCommand):
|
||||
self.logger.error(f"Error fetching dad joke from API: {e}")
|
||||
return None
|
||||
|
||||
async def get_dad_joke_with_length_handling(self) -> Optional[Dict[str, Any]]:
|
||||
"""Get a dad joke from API with length handling based on configuration"""
|
||||
max_attempts = 5 # Prevent infinite loops
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
joke_data = await self.get_dad_joke_from_api()
|
||||
|
||||
if joke_data is None:
|
||||
return None
|
||||
|
||||
# Check joke length
|
||||
joke_text = self.format_dad_joke(joke_data)
|
||||
|
||||
if len(joke_text) <= 130:
|
||||
# Joke is short enough, return it
|
||||
return joke_data
|
||||
elif self.long_jokes:
|
||||
# Long jokes are enabled, return it for splitting
|
||||
return joke_data
|
||||
else:
|
||||
# Long jokes are disabled, try again
|
||||
self.logger.debug(f"Dad joke too long ({len(joke_text)} chars), fetching another...")
|
||||
continue
|
||||
|
||||
# If we've tried max_attempts times and still getting long jokes, return the last one
|
||||
self.logger.warning(f"Could not get short dad joke after {max_attempts} attempts")
|
||||
return joke_data
|
||||
|
||||
async def send_dad_joke_with_length_handling(self, message: MeshMessage, joke_data: Dict[str, Any]):
|
||||
"""Send dad joke with length handling - split if necessary"""
|
||||
joke_text = self.format_dad_joke(joke_data)
|
||||
|
||||
if len(joke_text) <= 130:
|
||||
# Joke is short enough, send as single message
|
||||
await self.send_response(message, joke_text)
|
||||
else:
|
||||
# Joke is too long, split it
|
||||
parts = self.split_dad_joke(joke_text)
|
||||
|
||||
if len(parts) == 2 and len(parts[0]) <= 130 and len(parts[1]) <= 130:
|
||||
# Can be split into two messages
|
||||
await self.send_response(message, parts[0])
|
||||
# Use conservative delay to avoid rate limiting (same as weather command)
|
||||
await asyncio.sleep(2.0)
|
||||
await self.send_response(message, parts[1])
|
||||
else:
|
||||
# Cannot be split properly, send as single message (user will see truncation)
|
||||
await self.send_response(message, joke_text)
|
||||
|
||||
def split_dad_joke(self, joke_text: str) -> list:
|
||||
"""Split a long dad joke at a logical point"""
|
||||
# Remove emoji for splitting
|
||||
clean_joke = joke_text[2:] if joke_text.startswith('🥸 ') else joke_text
|
||||
|
||||
# Try to split at common logical points
|
||||
split_points = [
|
||||
'. ', # Period followed by space
|
||||
'? ', # Question mark followed by space
|
||||
'! ', # Exclamation mark followed by space
|
||||
', ', # Comma followed by space
|
||||
]
|
||||
|
||||
for split_point in split_points:
|
||||
if split_point in clean_joke:
|
||||
parts = clean_joke.split(split_point, 1)
|
||||
if len(parts) == 2:
|
||||
# Add emoji back to both parts
|
||||
return [f"🥸 {parts[0]}{split_point}", f"🥸 {parts[1]}"]
|
||||
|
||||
# If no good split point found, split at middle
|
||||
mid_point = len(clean_joke) // 2
|
||||
# Find nearest space to avoid splitting words
|
||||
for i in range(mid_point, len(clean_joke)):
|
||||
if clean_joke[i] == ' ':
|
||||
mid_point = i
|
||||
break
|
||||
|
||||
part1 = clean_joke[:mid_point]
|
||||
part2 = clean_joke[mid_point + 1:]
|
||||
|
||||
return [f"🥸 {part1}", f"🥸 {part2}"]
|
||||
|
||||
def format_dad_joke(self, joke_data: Dict[str, Any]) -> str:
|
||||
"""Format the dad joke data into a readable string"""
|
||||
try:
|
||||
|
||||
@@ -27,7 +27,7 @@ class HelpCommand(BaseCommand):
|
||||
self.logger.debug("Help command executed (handled by keyword matching)")
|
||||
return True
|
||||
|
||||
def get_specific_help(self, command_name: str) -> str:
|
||||
def get_specific_help(self, command_name: str, message: MeshMessage = None) -> str:
|
||||
"""Get help text for a specific command"""
|
||||
# Map command aliases to their actual command names
|
||||
command_aliases = {
|
||||
@@ -46,7 +46,16 @@ class HelpCommand(BaseCommand):
|
||||
command = self.bot.command_manager.commands.get(normalized_name)
|
||||
|
||||
if command:
|
||||
return f"**Help for '{command_name}':**\n{command.get_help_text()}"
|
||||
# Pass message context to get_help_text if the method supports it
|
||||
if hasattr(command, 'get_help_text') and callable(getattr(command, 'get_help_text')):
|
||||
try:
|
||||
help_text = command.get_help_text(message)
|
||||
except TypeError:
|
||||
# Fallback for commands that don't accept message parameter
|
||||
help_text = command.get_help_text()
|
||||
else:
|
||||
help_text = "No help available"
|
||||
return f"**Help for '{command_name}':**\n{help_text}"
|
||||
else:
|
||||
return f"**Unknown command: '{command_name}'**\n\nAvailable commands:\n" + self.get_available_commands_list()
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ Provides clean, family-friendly jokes from the JokeAPI
|
||||
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
from .base_command import BaseCommand
|
||||
from ..models import MeshMessage
|
||||
|
||||
@@ -44,12 +46,21 @@ class JokeCommand(BaseCommand):
|
||||
self.user_cooldowns = {} # user_id -> last_execution_time
|
||||
|
||||
# Load configuration
|
||||
self.joke_enabled = bot.config.getboolean('Bot', 'joke_enabled', fallback=True)
|
||||
self.seasonal_jokes = bot.config.getboolean('Bot', 'seasonal_jokes', fallback=True)
|
||||
self.joke_enabled = bot.config.getboolean('Jokes', 'joke_enabled', fallback=True)
|
||||
self.seasonal_jokes = bot.config.getboolean('Jokes', 'seasonal_jokes', fallback=True)
|
||||
self.long_jokes = bot.config.getboolean('Jokes', 'long_jokes', fallback=False)
|
||||
|
||||
def get_help_text(self) -> str:
|
||||
categories = ", ".join(self.SUPPORTED_CATEGORIES.keys())
|
||||
return f"Usage: joke [category] - Get a random joke or from categories: {categories}"
|
||||
def get_help_text(self, message: MeshMessage = None) -> str:
|
||||
"""Get help text, excluding dark category if not in DM"""
|
||||
if message and not message.is_dm:
|
||||
# In public channel, exclude dark category
|
||||
categories = [cat for cat in self.SUPPORTED_CATEGORIES.keys() if cat != 'dark']
|
||||
categories_str = ", ".join(categories)
|
||||
return f"Usage: joke [category] - Get a random joke or from categories: {categories_str}"
|
||||
else:
|
||||
# In DM or no message context, show all categories
|
||||
categories = ", ".join(self.SUPPORTED_CATEGORIES.keys())
|
||||
return f"Usage: joke [category] - Get a random joke or from categories: {categories}"
|
||||
|
||||
def matches_keyword(self, message: MeshMessage) -> bool:
|
||||
"""Check if message starts with a joke keyword"""
|
||||
@@ -69,6 +80,10 @@ class JokeCommand(BaseCommand):
|
||||
if not self.joke_enabled:
|
||||
return False
|
||||
|
||||
# Check if this is a dark joke request - require DM
|
||||
if self.is_dark_joke_request(message) and not message.is_dm:
|
||||
return False
|
||||
|
||||
# Check if command requires DM and message is not DM
|
||||
if self.requires_dm and not message.is_dm:
|
||||
return False
|
||||
@@ -101,6 +116,20 @@ class JokeCommand(BaseCommand):
|
||||
|
||||
return 0
|
||||
|
||||
def is_dark_joke_request(self, message: MeshMessage) -> bool:
|
||||
"""Check if the message is requesting a dark joke"""
|
||||
content = message.content.strip()
|
||||
if content.startswith('!'):
|
||||
content = content[1:].strip()
|
||||
|
||||
# Parse the command to extract category
|
||||
parts = content.split()
|
||||
if len(parts) >= 2:
|
||||
category_input = parts[1].lower()
|
||||
return category_input == 'dark'
|
||||
|
||||
return False
|
||||
|
||||
def _record_execution(self, user_id: str):
|
||||
"""Record the execution time for a specific user"""
|
||||
import time
|
||||
@@ -150,8 +179,8 @@ class JokeCommand(BaseCommand):
|
||||
# Record execution for this user
|
||||
self._record_execution(message.sender_id)
|
||||
|
||||
# Get joke from API
|
||||
joke_data = await self.get_joke_from_api(category)
|
||||
# Get joke from API with length handling
|
||||
joke_data = await self.get_joke_with_length_handling(category)
|
||||
|
||||
if joke_data is None:
|
||||
if category and category.lower() in ['dark']:
|
||||
@@ -160,9 +189,8 @@ class JokeCommand(BaseCommand):
|
||||
await self.send_response(message, "Sorry, couldn't fetch a joke right now. Try again later!")
|
||||
return True
|
||||
|
||||
# Format and send the joke
|
||||
joke_text = self.format_joke(joke_data)
|
||||
await self.send_response(message, joke_text)
|
||||
# Format and send the joke(s)
|
||||
await self.send_joke_with_length_handling(message, joke_data)
|
||||
|
||||
return True
|
||||
|
||||
@@ -228,6 +256,90 @@ class JokeCommand(BaseCommand):
|
||||
self.logger.error(f"Error fetching joke from JokeAPI: {e}")
|
||||
return None
|
||||
|
||||
async def get_joke_with_length_handling(self, category: str = None) -> Optional[Dict[str, Any]]:
|
||||
"""Get a joke from API with length handling based on configuration"""
|
||||
max_attempts = 5 # Prevent infinite loops
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
joke_data = await self.get_joke_from_api(category)
|
||||
|
||||
if joke_data is None:
|
||||
return None
|
||||
|
||||
# Check joke length
|
||||
joke_text = self.format_joke(joke_data)
|
||||
|
||||
if len(joke_text) <= 130:
|
||||
# Joke is short enough, return it
|
||||
return joke_data
|
||||
elif self.long_jokes:
|
||||
# Long jokes are enabled, return it for splitting
|
||||
return joke_data
|
||||
else:
|
||||
# Long jokes are disabled, try again
|
||||
self.logger.debug(f"Joke too long ({len(joke_text)} chars), fetching another...")
|
||||
continue
|
||||
|
||||
# If we've tried max_attempts times and still getting long jokes, return the last one
|
||||
self.logger.warning(f"Could not get short joke after {max_attempts} attempts")
|
||||
return joke_data
|
||||
|
||||
async def send_joke_with_length_handling(self, message: MeshMessage, joke_data: Dict[str, Any]):
|
||||
"""Send joke with length handling - split if necessary"""
|
||||
joke_text = self.format_joke(joke_data)
|
||||
|
||||
if len(joke_text) <= 130:
|
||||
# Joke is short enough, send as single message
|
||||
await self.send_response(message, joke_text)
|
||||
else:
|
||||
# Joke is too long, split it
|
||||
parts = self.split_joke(joke_text)
|
||||
|
||||
if len(parts) == 2 and len(parts[0]) <= 130 and len(parts[1]) <= 130:
|
||||
# Can be split into two messages
|
||||
await self.send_response(message, parts[0])
|
||||
# Use conservative delay to avoid rate limiting (same as weather command)
|
||||
await asyncio.sleep(2.0)
|
||||
await self.send_response(message, parts[1])
|
||||
else:
|
||||
# Cannot be split properly, send as single message (user will see truncation)
|
||||
await self.send_response(message, joke_text)
|
||||
|
||||
def split_joke(self, joke_text: str) -> list:
|
||||
"""Split a long joke at a logical point"""
|
||||
# Remove emoji for splitting
|
||||
clean_joke = joke_text[2:] if joke_text.startswith('🎭 ') else joke_text
|
||||
|
||||
# Try to split at common logical points
|
||||
split_points = [
|
||||
'.\n\n', # Two-part jokes with double newline
|
||||
'.\n', # Single newline
|
||||
'. ', # Period followed by space
|
||||
'? ', # Question mark followed by space
|
||||
'! ', # Exclamation mark followed by space
|
||||
', ', # Comma followed by space
|
||||
]
|
||||
|
||||
for split_point in split_points:
|
||||
if split_point in clean_joke:
|
||||
parts = clean_joke.split(split_point, 1)
|
||||
if len(parts) == 2:
|
||||
# Add emoji back to both parts
|
||||
return [f"🎭 {parts[0]}{split_point}", f"🎭 {parts[1]}"]
|
||||
|
||||
# If no good split point found, split at middle
|
||||
mid_point = len(clean_joke) // 2
|
||||
# Find nearest space to avoid splitting words
|
||||
for i in range(mid_point, len(clean_joke)):
|
||||
if clean_joke[i] == ' ':
|
||||
mid_point = i
|
||||
break
|
||||
|
||||
part1 = clean_joke[:mid_point]
|
||||
part2 = clean_joke[mid_point + 1:]
|
||||
|
||||
return [f"🎭 {part1}", f"🎭 {part2}"]
|
||||
|
||||
def format_joke(self, joke_data: dict) -> str:
|
||||
"""Format the joke data into a readable string"""
|
||||
try:
|
||||
|
||||
+6
-1
@@ -169,7 +169,7 @@ dm_flood_after = 2
|
||||
# Leave empty to use system timezone
|
||||
timezone =
|
||||
|
||||
# Joke command settings
|
||||
[Jokes]
|
||||
# Enable or disable the joke command
|
||||
# true: Joke command is available
|
||||
# false: Joke command is disabled
|
||||
@@ -186,6 +186,11 @@ seasonal_jokes = true
|
||||
# false: Dad joke command is disabled
|
||||
dadjoke_enabled = true
|
||||
|
||||
# Handle long jokes (over 130 characters)
|
||||
# false: Fetch new jokes until we get a short one
|
||||
# true: Split long jokes into multiple messages
|
||||
long_jokes = false
|
||||
|
||||
# Send startup advert when bot finishes initializing
|
||||
# false: No startup advert (default)
|
||||
# zero-hop: Send local broadcast advert
|
||||
|
||||
Reference in New Issue
Block a user