Improved !help
- Ensured it fetches the correct commands and help configuration depending on platform. - Removed certain duplicate checks that intiated functions twice or repeated value asignments. - Added the @monitor_cmd flag to commands, allowing easy command runtime diagnostics, including execution time.kami_dev
parent
28d22da0c1
commit
5730840209
|
@ -17,15 +17,16 @@ class DiscordBot(commands.Bot):
|
|||
self.help_data = None # We'll set this later
|
||||
self.load_commands()
|
||||
|
||||
self_log = self.log
|
||||
|
||||
self.log("Discord bot initiated", "INFO")
|
||||
log_func(f"DiscordBot.commands type: {type(self.commands)}", "DEBUG")
|
||||
|
||||
def set_db_connection(self, db_conn):
|
||||
"""
|
||||
Store the DB connection in the bot so commands can use it.
|
||||
"""
|
||||
self.db_conn = db_conn
|
||||
try:
|
||||
modules.db.ensure_quotes_table(self.db_conn, self.log)
|
||||
except Exception as e:
|
||||
self.log(f"Critical: unable to ensure quotes table: {e}", "FATAL")
|
||||
|
||||
def load_commands(self):
|
||||
"""
|
||||
|
|
|
@ -29,6 +29,8 @@ class TwitchBot(commands.Bot):
|
|||
|
||||
self.log("Twitch bot initiated", "INFO")
|
||||
|
||||
log_func(f"TwitchBot._commands type: {type(self._commands)}", "DEBUG")
|
||||
|
||||
# 2) Then load commands
|
||||
self.load_commands()
|
||||
|
||||
|
@ -37,10 +39,6 @@ class TwitchBot(commands.Bot):
|
|||
Store the DB connection so that commands can use it.
|
||||
"""
|
||||
self.db_conn = db_conn
|
||||
try:
|
||||
modules.db.ensure_quotes_table(self.db_conn, self.log)
|
||||
except Exception as e:
|
||||
self.log(f"Critical: unable to ensure quotes table: {e}", "FATAL")
|
||||
|
||||
async def event_message(self, message):
|
||||
"""Logs and processes incoming Twitch messages."""
|
||||
|
@ -127,7 +125,6 @@ class TwitchBot(commands.Bot):
|
|||
Load all commands from cmd_twitch.py
|
||||
"""
|
||||
try:
|
||||
importlib.reload(cmd_twitch)
|
||||
cmd_twitch.setup(self)
|
||||
self.log("Twitch commands loaded successfully.", "INFO")
|
||||
|
||||
|
|
8
bots.py
8
bots.py
|
@ -14,6 +14,7 @@ from bot_discord import DiscordBot
|
|||
from bot_twitch import TwitchBot
|
||||
|
||||
from modules.db import init_db_connection, run_db_operation
|
||||
from modules.db import ensure_quotes_table
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
@ -79,6 +80,13 @@ async def main():
|
|||
log("Terminating bot due to no DB connection.", "FATAL")
|
||||
sys.exit(1)
|
||||
|
||||
# auto-create the quotes table if it doesn't exist
|
||||
try:
|
||||
ensure_quotes_table(db_conn, log)
|
||||
except Exception as e:
|
||||
log(f"Critical: unable to ensure quotes table: {e}", "FATAL")
|
||||
|
||||
|
||||
log("Initializing bots...", "INFO")
|
||||
|
||||
# Create both bots
|
||||
|
|
|
@ -1,39 +1,36 @@
|
|||
# cmd_discord.py
|
||||
from discord.ext import commands
|
||||
|
||||
from cmd_common import common_commands as cc
|
||||
from modules.permissions import has_permission
|
||||
from modules.utility import handle_help_command
|
||||
|
||||
from modules.utility import monitor_cmds
|
||||
|
||||
def setup(bot, db_conn=None, log=None):
|
||||
"""
|
||||
Attach commands to the Discord bot, store references to db/log.
|
||||
"""
|
||||
|
||||
# auto-create the quotes table if it doesn't exist
|
||||
if bot.db_conn and bot.log:
|
||||
cc.create_quotes_table(bot.db_conn, bot.log)
|
||||
|
||||
# Auto-create the quotes table if desired
|
||||
if db_conn and log:
|
||||
cc.create_quotes_table(db_conn, log)
|
||||
@bot.command(name="greet")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_greet(ctx):
|
||||
result = cc.greet(ctx.author.display_name, "Discord")
|
||||
await ctx.send(result)
|
||||
|
||||
@bot.command(name="ping")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_ping(ctx):
|
||||
result = cc.ping()
|
||||
await ctx.send(result)
|
||||
|
||||
@bot.command(name="howl")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_howl(ctx):
|
||||
"""Calls the shared !howl logic."""
|
||||
result = cc.howl(ctx.author.display_name)
|
||||
await ctx.send(result)
|
||||
|
||||
@bot.command(name="reload")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_reload(ctx):
|
||||
""" Dynamically reloads Discord commands. """
|
||||
try:
|
||||
|
@ -46,6 +43,7 @@ def setup(bot, db_conn=None, log=None):
|
|||
await ctx.send(f"Error reloading commands: {e}")
|
||||
|
||||
@bot.command(name="hi")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_hi(ctx):
|
||||
user_id = str(ctx.author.id)
|
||||
user_roles = [role.name.lower() for role in ctx.author.roles] # Normalize to lowercase
|
||||
|
@ -57,6 +55,7 @@ def setup(bot, db_conn=None, log=None):
|
|||
await ctx.send("Hello there!")
|
||||
|
||||
@bot.command(name="quote")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_quote(ctx, *args):
|
||||
"""
|
||||
!quote
|
||||
|
@ -78,9 +77,19 @@ def setup(bot, db_conn=None, log=None):
|
|||
)
|
||||
|
||||
@bot.command(name="help")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_help(ctx, cmd_name: str = None):
|
||||
"""
|
||||
e.g. !help
|
||||
!help quote
|
||||
"""
|
||||
await handle_help_command(ctx, cmd_name, bot, is_discord=True, log_func=bot.log)
|
||||
await handle_help_command(ctx, cmd_name, bot, is_discord=True, log_func=bot.log)
|
||||
|
||||
######################
|
||||
# The following log entry must be last in the file to verify commands loading as they should
|
||||
######################
|
||||
# Debug: Print that commands are being registered
|
||||
try:
|
||||
bot.log(f"Registering commands for Discord: {list(bot.commands.keys())}", "DEBUG")
|
||||
except Exception as e:
|
||||
bot.log(f"An error occured while printing registered commands for Discord: {e}", "WARNING")
|
|
@ -1,36 +1,37 @@
|
|||
# cmd_twitch.py
|
||||
|
||||
from twitchio.ext import commands
|
||||
|
||||
from cmd_common import common_commands as cc
|
||||
from modules.permissions import has_permission
|
||||
from modules.utility import handle_help_command
|
||||
from modules.utility import monitor_cmds
|
||||
|
||||
def setup(bot, db_conn=None, log=None):
|
||||
"""
|
||||
This function is called to load/attach commands to the `bot`.
|
||||
We also attach the db_conn and log so the commands can use them.
|
||||
"""
|
||||
|
||||
# auto-create the quotes table if it doesn't exist
|
||||
if bot.db_conn and bot.log:
|
||||
cc.create_quotes_table(bot.db_conn, bot.log)
|
||||
|
||||
@bot.command(name="greet")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_greet(ctx):
|
||||
result = cc.greet(ctx.author.display_name, "Twitch")
|
||||
await ctx.send(result)
|
||||
|
||||
@bot.command(name="ping")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_ping(ctx):
|
||||
result = cc.ping()
|
||||
await ctx.send(result)
|
||||
|
||||
@bot.command(name="howl")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_howl(ctx):
|
||||
result = cc.howl(ctx.author.display_name)
|
||||
await ctx.send(result)
|
||||
|
||||
@bot.command(name="hi")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_hi(ctx):
|
||||
user_id = str(ctx.author.id) # Twitch user ID
|
||||
user_roles = [role.lower() for role in ctx.author.badges.keys()] # "roles" from Twitch badges
|
||||
|
@ -41,6 +42,7 @@ def setup(bot, db_conn=None, log=None):
|
|||
await ctx.send("Hello there!")
|
||||
|
||||
@bot.command(name="quote")
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_quote(ctx: commands.Context):
|
||||
if not bot.db_conn:
|
||||
return await ctx.send("Database is unavailable, sorry.")
|
||||
|
@ -62,7 +64,17 @@ def setup(bot, db_conn=None, log=None):
|
|||
)
|
||||
|
||||
@bot.command(name="help")
|
||||
async def help_command(ctx):
|
||||
@monitor_cmds(bot.log)
|
||||
async def cmd_help(ctx):
|
||||
parts = ctx.message.content.strip().split()
|
||||
cmd_name = parts[1] if len(parts) > 1 else None
|
||||
await handle_help_command(ctx, cmd_name, bot, is_discord=False, log_func=bot.log)
|
||||
await handle_help_command(ctx, cmd_name, bot, is_discord=False, log_func=bot.log)
|
||||
|
||||
######################
|
||||
# The following log entry must be last in the file to verify commands loading as they should
|
||||
######################
|
||||
# Debug: Print that commands are being registered
|
||||
try:
|
||||
bot.log(f"Registering commands for Twitch: {list(bot.commands.keys())}", "DEBUG")
|
||||
except Exception as e:
|
||||
bot.log(f"An error occured while printing registered commands for Twitch: {e}", "WARNING")
|
|
@ -3,7 +3,10 @@
|
|||
"help": {
|
||||
"description": "Show information about available commands.",
|
||||
"subcommands": {},
|
||||
"examples": ["!help", "!help quote"]
|
||||
"examples": [
|
||||
"!help",
|
||||
"!help quote"
|
||||
]
|
||||
},
|
||||
"quote": {
|
||||
"description": "Manage quotes (add, remove, fetch).",
|
||||
|
@ -33,7 +36,9 @@
|
|||
"ping": {
|
||||
"description": "Check my uptime.",
|
||||
"subcommands": {},
|
||||
"examples": ["!ping"]
|
||||
"examples": [
|
||||
"!ping"
|
||||
]
|
||||
},
|
||||
"howl": {
|
||||
"description": "Attempt a howl, measured 0-100%.\n(*Adventure Command*)",
|
||||
|
|
|
@ -3,6 +3,8 @@ import os
|
|||
import random
|
||||
import json
|
||||
import re
|
||||
import functools
|
||||
|
||||
|
||||
try:
|
||||
# 'regex' on PyPI supports `\p{L}`, `\p{N}`, etc.
|
||||
|
@ -14,6 +16,36 @@ except ImportError:
|
|||
|
||||
DICTIONARY_PATH = "dictionary/" # Path to dictionary files
|
||||
|
||||
def monitor_cmds(log_func):
|
||||
"""
|
||||
Decorator that logs when a command starts and ends execution.
|
||||
"""
|
||||
def decorator(func):
|
||||
@functools.wraps(func) # Preserve function metadata
|
||||
async def wrapper(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
try:
|
||||
cmd_name = str(func.__name__).split("_")[1]
|
||||
log_func(f"Command '{cmd_name}' started execution.", "DEBUG")
|
||||
|
||||
# Await the actual function (since it's an async command)
|
||||
result = await func(*args, **kwargs)
|
||||
|
||||
end_time = time.time()
|
||||
cmd_duration = end_time - start_time
|
||||
cmd_duration = str(round(cmd_duration, 2))
|
||||
log_func(f"Command '{cmd_name}' finished execution after {cmd_duration}s.", "DEBUG")
|
||||
return result # Return the result of the command
|
||||
except Exception as e:
|
||||
end_time = time.time()
|
||||
cmd_duration = end_time - start_time
|
||||
cmd_duration = str(round(cmd_duration, 2))
|
||||
log_func(f"Command '{cmd_name}' FAILED while executing after {cmd_duration}s: {e}", "CRITICAL")
|
||||
|
||||
return wrapper # Return the wrapped function
|
||||
|
||||
return decorator # Return the decorator itself
|
||||
|
||||
def format_uptime(seconds: float) -> tuple[str, int]:
|
||||
"""
|
||||
Convert seconds into a human-readable string:
|
||||
|
@ -195,19 +227,29 @@ async def handle_help_command(ctx, command_name, bot, is_discord, log_func):
|
|||
|
||||
if not command_name:
|
||||
# User typed just "!help" => list all known commands from this bot
|
||||
loaded_cmds = get_loaded_commands(bot)
|
||||
loaded_cmds = get_loaded_commands(bot, log_func, is_discord)
|
||||
if not loaded_cmds:
|
||||
return await send_message(ctx, "I have no commands loaded.")
|
||||
else:
|
||||
short_list = ", ".join(loaded_cmds)
|
||||
# We can also mention "Use !help [command] for more info."
|
||||
return await send_message(
|
||||
ctx,
|
||||
f"I currently offer these commands: {short_list}\nUse '!help <command>' for details."
|
||||
)
|
||||
if is_discord:
|
||||
help_str = f"I currently offer these commands:"
|
||||
for cmd in loaded_cmds:
|
||||
help_str += f"\n- !{cmd}"
|
||||
help_str += f"\n*Use '!help <command>' for more details.*"
|
||||
return await send_message(
|
||||
ctx,
|
||||
help_str
|
||||
)
|
||||
else:
|
||||
short_list = ", ".join(loaded_cmds)
|
||||
# We can also mention "Use !help [command] for more info."
|
||||
return await send_message(
|
||||
ctx,
|
||||
f"I currently offer these commands:{short_list}. \nUse '!help <command>' for details."
|
||||
)
|
||||
|
||||
# 1) Check if the command is loaded
|
||||
loaded = (command_name in get_loaded_commands(bot))
|
||||
loaded = (command_name in get_loaded_commands(bot, log_func, is_discord))
|
||||
# 2) Check if it has help info in the JSON
|
||||
cmd_help = help_data["commands"].get(command_name, None)
|
||||
|
||||
|
@ -255,7 +297,7 @@ def initialize_help_data(bot, help_json_path, is_discord, log_func):
|
|||
bot.help_data = data
|
||||
|
||||
# Now cross-check the loaded commands vs. the data
|
||||
loaded_cmds = set(get_loaded_commands(bot))
|
||||
loaded_cmds = set(get_loaded_commands(bot, log_func, is_discord))
|
||||
if "commands" not in data:
|
||||
log_func(f"No 'commands' key in {help_json_path}, skipping checks.", "ERROR")
|
||||
return
|
||||
|
@ -273,20 +315,48 @@ def initialize_help_data(bot, help_json_path, is_discord, log_func):
|
|||
log_func(f"Command '{cmd}' is loaded on {('Discord' if is_discord else 'Twitch')} but no help info is provided in {help_json_path}.", "WARNING")
|
||||
|
||||
|
||||
def get_loaded_commands(bot):
|
||||
def get_loaded_commands(bot, log_func, is_discord):
|
||||
from discord.ext import commands as discord_commands
|
||||
from twitchio.ext import commands as twitch_commands
|
||||
|
||||
commands_list = []
|
||||
|
||||
# For Discord.py
|
||||
if hasattr(bot, "commands"):
|
||||
for c_obj in bot.commands:
|
||||
commands_list.append(c_obj.name)
|
||||
try:
|
||||
_bot_type = str(type(bot)).split("_")[1].split(".")[0]
|
||||
log_func(f"Currently processing commands for {_bot_type} ...", "DEBUG")
|
||||
except Exception as e:
|
||||
log_func(f"Unable to determine current bot type: {e}", "WARNING")
|
||||
|
||||
# For TwitchIO
|
||||
if hasattr(bot, "all_commands"):
|
||||
# each item is (command_name_str, command_object)
|
||||
for cmd_name, cmd_obj in bot.all_commands.items():
|
||||
commands_list.append(cmd_name)
|
||||
# For Discord
|
||||
if is_discord:
|
||||
#if isinstance(bot, discord_commands.Bot):
|
||||
try:
|
||||
# 'bot.commands' is a set of Command objects
|
||||
for cmd_obj in bot.commands:
|
||||
commands_list.append(cmd_obj.name)
|
||||
log_func(f"Discord commands body: {commands_list}", "DEBUG")
|
||||
except Exception as e:
|
||||
log_func(f"Error retrieving Discord commands: {e}", "ERROR")
|
||||
elif not is_discord:
|
||||
# For TwitchIO
|
||||
#if isinstance(bot.commands, set):
|
||||
try:
|
||||
#commands_attr = bot.commands
|
||||
#log_func(f"Twitch type(bot.commands) => {type(commands_attr)}", "DEBUG")
|
||||
|
||||
# 'bot.all_commands' is a dict: { command_name: Command(...) }
|
||||
#all_cmd_names = list(bot.all_commands.keys())
|
||||
#log_func(f"Twitch commands body: {all_cmd_names}", "DEBUG")
|
||||
#commands_list.extend(all_cmd_names)
|
||||
for cmd_obj in bot._commands:
|
||||
commands_list.append(cmd_obj)
|
||||
log_func(f"Twitch commands body: {commands_list}", "DEBUG")
|
||||
except Exception as e:
|
||||
log_func(f"Error retrieving Twitch commands: {e}", "ERROR")
|
||||
else:
|
||||
log_func(f"Unable to determine platform in 'get_loaded_commands()'!", "CRITICAL")
|
||||
|
||||
log_func(f"... Finished processing commands for {_bot_type} ...", "DEBUG")
|
||||
return sorted(commands_list)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue