From 766c3ab69043d2d572e9ed7def980bc9c875a365 Mon Sep 17 00:00:00 2001 From: Kami Date: Sun, 23 Feb 2025 17:00:45 +0100 Subject: [PATCH] Commands restructuring - Reorganized commands into separate files for better fault tolerance and feature expandability - Added basic dynamic bot status - Needs additional work. Currently very basic, planned features include fully dynamic mode with automatic alerts and such. - Minor other tweaks --- bot_discord.py | 91 ++++++++++++- cmd_discord.py | 162 ----------------------- cmd_discord/__init__.py | 19 +++ cmd_discord/funfact.py | 14 ++ cmd_discord/help.py | 37 ++++++ cmd_discord/howl.py | 18 +++ cmd_discord/ping.py | 21 +++ cmd_discord/quote.py | 50 +++++++ cmd_twitch.py | 202 ----------------------------- cmd_twitch/__init__.py | 19 +++ cmd_twitch/funfact.py | 19 +++ cmd_twitch/getgame.py | 19 +++ cmd_twitch/help.py | 21 +++ cmd_twitch/howl.py | 21 +++ cmd_twitch/ping.py | 18 +++ cmd_twitch/quote.py | 46 +++++++ settings/discord_bot_settings.json | 26 ++++ 17 files changed, 438 insertions(+), 365 deletions(-) delete mode 100644 cmd_discord.py create mode 100644 cmd_discord/__init__.py create mode 100644 cmd_discord/funfact.py create mode 100644 cmd_discord/help.py create mode 100644 cmd_discord/howl.py create mode 100644 cmd_discord/ping.py create mode 100644 cmd_discord/quote.py delete mode 100644 cmd_twitch.py create mode 100644 cmd_twitch/__init__.py create mode 100644 cmd_twitch/funfact.py create mode 100644 cmd_twitch/getgame.py create mode 100644 cmd_twitch/help.py create mode 100644 cmd_twitch/howl.py create mode 100644 cmd_twitch/ping.py create mode 100644 cmd_twitch/quote.py create mode 100644 settings/discord_bot_settings.json diff --git a/bot_discord.py b/bot_discord.py index 0c26aa7..eec3a07 100644 --- a/bot_discord.py +++ b/bot_discord.py @@ -1,9 +1,10 @@ # bot_discord.py import discord from discord import app_commands -from discord.ext import commands +from discord.ext import commands, tasks import importlib import cmd_discord +import json import globals @@ -150,6 +151,20 @@ class DiscordBot(commands.Bot): except Exception as e: globals.log(f"Command processing failed: {e}", "ERROR") + def load_bot_settings(self): + """Loads bot activity settings from JSON file.""" + try: + with open("settings/discord_bot_settings.json", "r") as file: + return json.load(file) + except Exception as e: + self.log(f"Failed to load settings: {e}", "ERROR") + return { + "activity_mode": 0, + "static_activity": {"type": "Playing", "name": "with my commands!"}, + "rotating_activities": [], + "dynamic_activities": {}, + "rotation_interval": 600 + } async def on_command(self, ctx): """Logs every command execution at DEBUG level.""" @@ -184,6 +199,13 @@ class DiscordBot(commands.Bot): async def on_ready(self): """Runs when the bot successfully logs in.""" + + # Load activity settings + self.settings = self.load_bot_settings() + + # Set initial bot activity + await self.update_activity() + # Sync Slash Commands try: # Sync slash commands globally @@ -216,6 +238,73 @@ class DiscordBot(commands.Bot): globals.log("Discord bot has lost connection!", "WARNING") log_bot_event(self.db_conn, "DISCORD_DISCONNECTED", "Discord bot lost connection.") + async def update_activity(self): + """Sets the bot's activity based on settings.""" + mode = self.settings["activity_mode"] + + if mode == 0: + await self.change_presence(activity=None) + self.log("Activity disabled", "DEBUG") + + elif mode == 1: + # Set static activity + activity_data = self.settings["static_activity"] + activity = self.get_activity(activity_data["type"], activity_data["name"]) + await self.change_presence(activity=activity) + self.log(f"Static activity set: {activity_data['type']} {activity_data['name']}", "DEBUG") + + elif mode == 2: + # Start rotating activity task + if not self.change_rotating_activity.is_running(): + self.change_rotating_activity.start() + self.log("Rotating activity mode enabled", "DEBUG") + + elif mode == 3: + # Check for dynamic activity + await self.set_dynamic_activity() + + @tasks.loop(seconds=600) # Default to 10 minutes + async def change_rotating_activity(self): + """Rotates activities every set interval.""" + activities = self.settings["rotating_activities"] + if not activities: + return + + # Pick the next activity + activity_data = activities.pop(0) + activities.append(activity_data) # Move to the end of the list + + activity = self.get_activity(activity_data["type"], activity_data["name"]) + await self.change_presence(activity=activity) + self.log(f"Rotating activity: {activity_data['type']} {activity_data['name']}", "DEBUG") + + async def set_dynamic_activity(self): + """Sets a dynamic activity based on external conditions.""" + twitch_live = await modules.utility.is_channel_live(self) + + if twitch_live: + activity_data = self.settings["dynamic_activities"]["twitch_live"] + else: + # activity_data = self.settings["dynamic_activities"].get("default_idle", None) + if not self.change_rotating_activity.is_running(): + self.change_rotating_activity.start() + + if activity_data: + activity = self.get_activity(activity_data["type"], activity_data["name"], activity_data.get("url")) + await self.change_presence(activity=activity) + self.log(f"Dynamic activity set: {activity_data['type']} {activity_data['name']}", "DEBUG") + + def get_activity(self, activity_type, name, url=None): + """Returns a discord activity object based on type, including support for Custom Status.""" + activity_map = { + "Playing": discord.Game(name=name), + "Streaming": discord.Streaming(name=name, url=url or "https://twitch.tv/OokamiKunTV"), + "Listening": discord.Activity(type=discord.ActivityType.listening, name=name), + "Watching": discord.Activity(type=discord.ActivityType.watching, name=name), + "Custom": discord.CustomActivity(name=name) + } + return activity_map.get(activity_type, discord.Game(name="around in Discord")) + async def on_voice_state_update(self, member, before, after): """ Tracks user joins, leaves, mutes, deafens, streams, and voice channel moves. diff --git a/cmd_discord.py b/cmd_discord.py deleted file mode 100644 index 1c00909..0000000 --- a/cmd_discord.py +++ /dev/null @@ -1,162 +0,0 @@ -# cmd_discord.py -import discord -from discord.ext import commands -from typing import Optional -from discord import app_commands - -from cmd_common import common_commands as cc -from modules.permissions import has_permission -from modules.utility import handle_help_command -import globals -import os -import json -import random - -# Retrieve primary guild info if needed (for logging or other purposes) -primary_guild = globals.constants.primary_discord_guild() # e.g., {"object": discord.Object(id=1234567890), "id": 1234567890} - -def setup(bot): - """ - Attach commands to the Discord bot and register textual commands. - - This function loads configuration data, then registers several commands: - - sync_commands: (OWNER ONLY) Manually syncs the bot’s command tree. - - available: Lists commands available to the user. - - help: Provides detailed help information for commands. - - greet: Makes the bot greet the user. - - ping: Checks the bot's uptime and latency. - - howl: Makes the bot attempt a howl. - - hi: A development command to test permission handling. - - quote: Interact with the quotes system (multiple subcommands supported). - - All commands here are invoked with your text command prefix (e.g. "!") - """ - - config_data = globals.load_config_file() - - @bot.command(name='funfact', aliases=['fun-fact']) - async def funfact_command(ctx, *keywords): - # keywords is a tuple of strings from the command arguments. - fact = cc.get_fun_fact(list(keywords)) - # Reply to the invoking user. - await ctx.reply(fact) - - - # ------------------------------------------------------------------------- - # TEXTUAL COMMAND: quote - # ------------------------------------------------------------------------- - @bot.command(name="quote") - async def cmd_quote_text(ctx, *, arg_str: str = ""): - """ - Handle the '!quote' command with multiple subcommands. - - Usage: - - !quote - -> Retrieves a random (non-removed) quote. - - !quote - -> Retrieves a specific quote by its ID. - - !quote add - -> Adds a new quote and replies with its quote number. - - !quote remove - -> Removes the specified quote. - - !quote restore - -> Restores a previously removed quote. - - !quote info - -> Displays stored information about the quote. - - !quote search [keywords] - -> Searches for the best matching quote. - - !quote latest - -> Retrieves the latest (most recent) non-removed quote. - """ - if not globals.init_db_conn: - await ctx.reply("Database is unavailable, sorry.") - return - - args = arg_str.split() if arg_str else [] - globals.log(f"'quote' command initiated with arguments: {args}", "DEBUG") - result = await cc.handle_quote_command( - db_conn=globals.init_db_conn, - is_discord=True, - ctx=ctx, - args=args, - game_name=None - ) - globals.log(f"'quote' result: {result}", "DEBUG") - if hasattr(result, "to_dict"): - await ctx.reply(embed=result) - else: - await ctx.reply(result) - - - # ------------------------------------------------------------------------- - # TEXTUAL COMMAND: howl - # ------------------------------------------------------------------------- - @bot.command(name="howl") - async def cmd_howl_text(ctx): - """ - Handle the '!howl' command. - - Usage: - - !howl - -> Attempts a howl. - - !howl stat - -> Looks up howling stats for a user (defaults to self if not provided). - """ - result = cc.handle_howl_command(ctx) - await ctx.reply(result) - - - # ------------------------------------------------------------------------- - # TEXTUAL COMMAND: help - # ------------------------------------------------------------------------- - @bot.command(name="help") - async def cmd_help_text(ctx, *, command: str = ""): - """ - Get help information about commands. - - Usage: - - !help - -> Provides a list of all commands with brief descriptions. - - !help - -> Provides detailed help information for the specified command. - """ - result = await handle_help_command(ctx, command, bot, is_discord=True) - await ctx.reply(result) - - - # ------------------------------------------------------------------------- - # SLASH COMMAND: help - # ------------------------------------------------------------------------- - @bot.tree.command(name="help", description="Get information about commands", guild=primary_guild["object"]) - @app_commands.describe(command="The command to get help info about. Defaults to 'help'") - async def cmd_help_slash(interaction: discord.Interaction, command: Optional[str] = ""): - result = await handle_help_command(interaction, command, bot, is_discord=True) - await interaction.response.send_message(result) - - - # ------------------------------------------------------------------------- - # TEXTUAL COMMAND: ping - # ------------------------------------------------------------------------- - @bot.command(name="ping") - async def cmd_ping_text(ctx): - """ - Check the bot's uptime and latency. - - Usage: - - !ping - -> Returns the bot's uptime along with its latency in milliseconds. - """ - result = cc.ping() - latency = round(float(bot.latency) * 1000) - result += f" (*latency: {latency}ms*)" - await ctx.reply(result) - - - # ------------------------------------------------------------------------- - # Final logging: list registered commands. - # ------------------------------------------------------------------------- - try: - command_names = [cmd.name for cmd in bot.commands] # Extract command names - globals.log(f"Registering commands for Discord: {command_names}", "DEBUG") - except Exception as e: - globals.log(f"An error occurred while printing registered commands for Discord: {e}", "WARNING") diff --git a/cmd_discord/__init__.py b/cmd_discord/__init__.py new file mode 100644 index 0000000..737641e --- /dev/null +++ b/cmd_discord/__init__.py @@ -0,0 +1,19 @@ +# cmd_discord/__init__.py +import os +import importlib + +def setup(bot): + """ + Dynamically load all commands from the cmd_discord folder. + """ + # Get a list of all command files (excluding __init__.py) + command_files = [ + f.replace('.py', '') for f in os.listdir(os.path.dirname(__file__)) + if f.endswith('.py') and f != '__init__.py' + ] + + # Import and set up each command module + for command in command_files: + module = importlib.import_module(f".{command}", package='cmd_discord') + if hasattr(module, 'setup'): + module.setup(bot) diff --git a/cmd_discord/funfact.py b/cmd_discord/funfact.py new file mode 100644 index 0000000..2de2f35 --- /dev/null +++ b/cmd_discord/funfact.py @@ -0,0 +1,14 @@ +# cmd_discord/howl.py +from discord.ext import commands +import cmd_common.common_commands as cc + +def setup(bot): + """ + Registers the '!howl' command for Discord. + """ + @bot.command(name='funfact', aliases=['fun-fact']) + async def funfact_command(ctx, *keywords): + # keywords is a tuple of strings from the command arguments. + fact = cc.get_fun_fact(list(keywords)) + # Reply to the invoking user. + await ctx.reply(fact) \ No newline at end of file diff --git a/cmd_discord/help.py b/cmd_discord/help.py new file mode 100644 index 0000000..ee9730c --- /dev/null +++ b/cmd_discord/help.py @@ -0,0 +1,37 @@ +# cmd_discord/howl.py +from discord.ext import commands +import discord +from discord import app_commands +from typing import Optional +import cmd_common.common_commands as cc +import globals + +# Retrieve primary guild info if needed (for logging or other purposes) +primary_guild = globals.constants.primary_discord_guild() # e.g., {"object": discord.Object(id=1234567890), "id": 1234567890} + +def setup(bot): + """ + Registers the '!help' command for Discord. + """ + @bot.command(name="help") + async def cmd_help_text(ctx, *, command: str = ""): + """ + Get help information about commands. + + Usage: + - !help + -> Provides a list of all commands with brief descriptions. + - !help + -> Provides detailed help information for the specified command. + """ + result = await cc.handle_help_command(ctx, command, bot, is_discord=True) + await ctx.reply(result) + + # ------------------------------------------------------------------------- + # SLASH COMMAND: help + # ------------------------------------------------------------------------- + @bot.tree.command(name="help", description="Get information about commands", guild=primary_guild["object"]) + @app_commands.describe(command="The command to get help info about. Defaults to 'help'") + async def cmd_help_slash(interaction: discord.Interaction, command: Optional[str] = ""): + result = await cc.handle_help_command(interaction, command, bot, is_discord=True) + await interaction.response.send_message(result) \ No newline at end of file diff --git a/cmd_discord/howl.py b/cmd_discord/howl.py new file mode 100644 index 0000000..bd03461 --- /dev/null +++ b/cmd_discord/howl.py @@ -0,0 +1,18 @@ +# cmd_discord/howl.py +from discord.ext import commands +import cmd_common.common_commands as cc + +def setup(bot): + """ + Registers the '!howl' command for Discord. + """ + @bot.command(name="howl") + async def cmd_howl_text(ctx): + """ + Handle the '!howl' command. + Usage: + - !howl -> Attempts a howl. + - !howl stat -> Looks up howling stats for a user. + """ + result = cc.handle_howl_command(ctx) + await ctx.reply(result) diff --git a/cmd_discord/ping.py b/cmd_discord/ping.py new file mode 100644 index 0000000..73d0246 --- /dev/null +++ b/cmd_discord/ping.py @@ -0,0 +1,21 @@ +# cmd_discord/howl.py +from discord.ext import commands +import cmd_common.common_commands as cc + +def setup(bot): + """ + Registers the '!ping' command for Discord. + """ + @bot.command(name="ping") + async def cmd_ping_text(ctx): + """ + Check the bot's uptime and latency. + + Usage: + - !ping + -> Returns the bot's uptime along with its latency in milliseconds. + """ + result = cc.ping() + latency = round(float(bot.latency) * 1000) + result += f" (*latency: {latency}ms*)" + await ctx.reply(result) diff --git a/cmd_discord/quote.py b/cmd_discord/quote.py new file mode 100644 index 0000000..c9f55ca --- /dev/null +++ b/cmd_discord/quote.py @@ -0,0 +1,50 @@ +# cmd_discord/howl.py +from discord.ext import commands +import globals +import cmd_common.common_commands as cc + +def setup(bot): + """ + Registers the '!quote' command for Discord. + """ + @bot.command(name="quote") + async def cmd_quote_text(ctx, *, arg_str: str = ""): + """ + Handle the '!quote' command with multiple subcommands. + + Usage: + - !quote + -> Retrieves a random (non-removed) quote. + - !quote + -> Retrieves a specific quote by its ID. + - !quote add + -> Adds a new quote and replies with its quote number. + - !quote remove + -> Removes the specified quote. + - !quote restore + -> Restores a previously removed quote. + - !quote info + -> Displays stored information about the quote. + - !quote search [keywords] + -> Searches for the best matching quote. + - !quote latest + -> Retrieves the latest (most recent) non-removed quote. + """ + if not globals.init_db_conn: + await ctx.reply("Database is unavailable, sorry.") + return + + args = arg_str.split() if arg_str else [] + globals.log(f"'quote' command initiated with arguments: {args}", "DEBUG") + result = await cc.handle_quote_command( + db_conn=globals.init_db_conn, + is_discord=True, + ctx=ctx, + args=args, + game_name=None + ) + globals.log(f"'quote' result: {result}", "DEBUG") + if hasattr(result, "to_dict"): + await ctx.reply(embed=result) + else: + await ctx.reply(result) diff --git a/cmd_twitch.py b/cmd_twitch.py deleted file mode 100644 index 7332a70..0000000 --- a/cmd_twitch.py +++ /dev/null @@ -1,202 +0,0 @@ -# cmd_twitch.py - -from twitchio.ext import commands - -import globals - -from cmd_common import common_commands as cc -from modules.permissions import has_permission -from modules.utility import handle_help_command, is_channel_live, command_allowed_twitch, get_current_twitch_game - -def setup(bot, db_conn=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. - """ - - @bot.command(name='getgame') - @command_allowed_twitch - async def cmd_getgame(ctx: commands.Context): - channel_name = ctx.channel.name - game_name = await get_current_game(bot, channel_name) - await ctx.reply(game_name) - - @bot.command(name='funfact', aliases=['fun-fact']) - @command_allowed_twitch - async def cmd_funfact(ctx: commands.Context, *keywords: str): - # Convert keywords tuple to list and pass to get_fun_fact. - fact = cc.get_fun_fact(list(keywords)) - # Reply to the invoking user by prefixing their name. - await ctx.reply(fact) - - @bot.command(name="greet") - @command_allowed_twitch - async def cmd_greet(ctx: commands.Context): - if not await is_channel_live(): - result = cc.greet(ctx.author.display_name, "Twitch") - await ctx.reply(result) - - @bot.command(name="ping") - @command_allowed_twitch - async def cmd_ping(ctx: commands.Context): - if not await is_channel_live(): - result = cc.ping() - await ctx.reply(result) - - @bot.command(name="howl") - @command_allowed_twitch - async def cmd_howl(ctx: commands.Context): - if not await is_channel_live(): - response = cc.handle_howl_command(ctx) - await ctx.reply(response) - - @bot.command(name="hi") - @command_allowed_twitch - async def cmd_hi(ctx: commands.Context): - if not await is_channel_live(): - user_id = str(ctx.author.id) # Twitch user ID - user_roles = [role.lower() for role in ctx.author.badges.keys()] # "roles" from Twitch badges - - if not has_permission("hi", user_id, user_roles, "twitch"): - return await ctx.send("You don't have permission to use this command.") - - await ctx.reply("Hello there!") - - # @bot.command(name="acc_link") - # @monitor_cmds(bot.log) - # async def cmd_acc_link(ctx, link_code: str): - # """Handles the Twitch command to link accounts.""" - # from modules import db - # twitch_user_id = str(ctx.author.id) - # twitch_username = ctx.author.name - - # # Check if the link code exists - # result = db.run_db_operation( - # bot.db_conn, "read", - # "SELECT DISCORD_USER_ID FROM link_codes WHERE LINK_CODE = ?", (link_code,), - # bot.log - # ) - - # if not result: - # await ctx.send("Invalid or expired link code. Please try again.") - # return - - # discord_user_id = result[0][0] - - # # Store the Twitch user info in the users table - # db.run_db_operation( - # bot.db_conn, "update", - # "UPDATE users SET twitch_user_id = ?, twitch_username = ?, datetime_linked = CURRENT_TIMESTAMP WHERE discord_user_id = ?", - # (twitch_user_id, twitch_username, discord_user_id), bot.log - # ) - - # # Remove the used link code - # db.run_db_operation(bot.db_conn, "write", "DELETE FROM link_codes WHERE LINK_CODE = ?", (link_code,), bot.log) - - # # Notify the user - # await ctx.send(f"✅ Successfully linked Discord user **{discord_user_id}** with Twitch account **{twitch_username}**.") - - - # @bot.command(name="quote") - # async def cmd_quote(ctx: commands.Context): - # """ - # Handles the !quote command with multiple subcommands. - - # Usage: - # - !quote - # -> Retrieves a random (non-removed) quote. - # - !quote - # -> Retrieves the specific quote by ID. - # - !quote add - # -> Adds a new quote and replies with its quote number. - # - !quote remove - # -> Removes the specified quote. - # - !quote info - # -> Displays stored information about the quote (as an embed on Discord). - # - !quote search [keywords] - # -> Searches for the best matching quote based on the provided keywords. - # - !quote last/latest/newest - # -> Retrieves the latest (most recent) non-removed quote. - # """ - # if not bot.db_conn: - # return await ctx.send("Database is unavailable, sorry.") - - # parts = ctx.message.content.strip().split() - # args = parts[1:] if len(parts) > 1 else [] - - # def get_twitch_game_for_channel(chan_name): - # # Placeholder for your actual logic to fetch the current game - # return "SomeGame" - - # result = await cc.handle_quote_command( - # db_conn=bot.db_conn, - # is_discord=False, - # ctx=ctx, - # args=args, - # get_twitch_game_for_channel=get_twitch_game_for_channel - # ) - # await ctx.send(result) - - @bot.command(name="quote") - @command_allowed_twitch - async def cmd_quote(ctx: commands.Context): - """ - Handles the !quote command with multiple subcommands. - - Usage: - - !quote - -> Retrieves a random (non-removed) quote. - - !quote - -> Retrieves the specific quote by ID. - - !quote add - -> Adds a new quote and replies with its quote number. - - !quote remove - -> Removes the specified quote. - - `!quote restore ` - -> Restores a previously removed quote. - - !quote info - -> Displays stored information about the quote (as an embed on Discord). - - !quote search [keywords] - -> Searches for the best matching quote based on the provided keywords. - - !quote last/latest/newest - -> Retrieves the latest (most recent) non-removed quote. - """ - if not await is_channel_live(): - if not globals.init_db_conn: - await ctx.reply("Database is unavailable, sorry.") - return - - # Parse the arguments from the message text - args = ctx.message.content.strip().split() - args = args[1:] if args else [] - globals.log(f"'quote' command initiated with arguments: {args}", "DEBUG") - globals.log(f"'quote' command message content: {ctx.message.content}", "DEBUG") - - result = await cc.handle_quote_command( - db_conn=globals.init_db_conn, - is_discord=False, - ctx=ctx, - args=args, - game_name=await get_twitch_current_game(bot, ctx.channel.name) - ) - - globals.log(f"'quote' result: {result}", "DEBUG") - - await ctx.reply(result) - - @bot.command(name="help") - @command_allowed_twitch - async def cmd_help(ctx): - if not await is_channel_live(bot): - 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) - - ###################### - # 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: - globals.log(f"Registering commands for Twitch: {list(bot.commands.keys())}", "DEBUG") - except Exception as e: - globals.log(f"An error occured while printing registered commands for Twitch: {e}", "WARNING") \ No newline at end of file diff --git a/cmd_twitch/__init__.py b/cmd_twitch/__init__.py new file mode 100644 index 0000000..80ae195 --- /dev/null +++ b/cmd_twitch/__init__.py @@ -0,0 +1,19 @@ +# cmd_twitch/__init__.py +import os +import importlib + +def setup(bot): + """ + Dynamically load all commands from the cmd_twitch folder. + """ + # Get a list of all command files (excluding __init__.py) + command_files = [ + f.replace('.py', '') for f in os.listdir(os.path.dirname(__file__)) + if f.endswith('.py') and f != '__init__.py' + ] + + # Import and set up each command module + for command in command_files: + module = importlib.import_module(f".{command}", package='cmd_twitch') + if hasattr(module, 'setup'): + module.setup(bot) diff --git a/cmd_twitch/funfact.py b/cmd_twitch/funfact.py new file mode 100644 index 0000000..b5dbd4c --- /dev/null +++ b/cmd_twitch/funfact.py @@ -0,0 +1,19 @@ +# cmd_twitch/funfact.py +from twitchio.ext import commands +from cmd_common import common_commands as cc + +def setup(bot): + """ + Registers the '!funfact' command for Twitch. + """ + @bot.command(name='funfact', aliases=['fun-fact']) + async def cmd_funfact(ctx: commands.Context, *keywords: str): + """ + Handle the '!funfact' command. + + Usage: + - !funfact + -> Displays a fun fact related to the given keywords. + """ + fact = cc.get_fun_fact(list(keywords)) + await ctx.reply(fact) diff --git a/cmd_twitch/getgame.py b/cmd_twitch/getgame.py new file mode 100644 index 0000000..5da15d7 --- /dev/null +++ b/cmd_twitch/getgame.py @@ -0,0 +1,19 @@ +# cmd_twitch/getgame.py +from twitchio.ext import commands +from modules.utility import get_current_twitch_game + +def setup(bot): + """ + Registers the '!getgame' command for Twitch. + """ + @bot.command(name='getgame') + async def cmd_getgame(ctx: commands.Context): + """ + Retrieves the current game being played on Twitch. + + Usage: + - !getgame -> Shows the current game for the channel. + """ + channel_name = ctx.channel.name + game_name = await get_current_twitch_game(bot, channel_name) + await ctx.reply(game_name) diff --git a/cmd_twitch/help.py b/cmd_twitch/help.py new file mode 100644 index 0000000..ac62256 --- /dev/null +++ b/cmd_twitch/help.py @@ -0,0 +1,21 @@ +# cmd_twitch/help.py +from twitchio.ext import commands +from modules.utility import handle_help_command, is_channel_live + +def setup(bot): + """ + Registers the '!help' command for Twitch. + """ + @bot.command(name="help") + async def cmd_help(ctx): + """ + Displays help information for available commands. + + Usage: + - !help -> Lists all commands. + - !help -> Detailed help for a specific command. + """ + if not await is_channel_live(bot): + 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) diff --git a/cmd_twitch/howl.py b/cmd_twitch/howl.py new file mode 100644 index 0000000..ff773e4 --- /dev/null +++ b/cmd_twitch/howl.py @@ -0,0 +1,21 @@ +# cmd_twitch/howl.py +from twitchio.ext import commands +from cmd_common import common_commands as cc +from modules.utility import is_channel_live + +def setup(bot): + """ + Registers the '!howl' command for Twitch. + """ + @bot.command(name="howl") + async def cmd_howl(ctx: commands.Context): + """ + Handle the '!howl' command. + + Usage: + - !howl -> Attempts a howl. + - !howl stat -> Looks up howling stats for a user. + """ + if not await is_channel_live(bot): + response = cc.handle_howl_command(ctx) + await ctx.reply(response) diff --git a/cmd_twitch/ping.py b/cmd_twitch/ping.py new file mode 100644 index 0000000..79bc28a --- /dev/null +++ b/cmd_twitch/ping.py @@ -0,0 +1,18 @@ +# cmd_twitch/ping.py +from twitchio.ext import commands +from cmd_common import common_commands as cc + +def setup(bot): + """ + Registers the '!ping' command for Twitch. + """ + @bot.command(name="ping") + async def cmd_ping(ctx: commands.Context): + """ + Checks the bot's uptime and latency. + + Usage: + - !ping -> Returns the bot's uptime and latency. + """ + result = cc.ping() + await ctx.reply(result) diff --git a/cmd_twitch/quote.py b/cmd_twitch/quote.py new file mode 100644 index 0000000..c3bc246 --- /dev/null +++ b/cmd_twitch/quote.py @@ -0,0 +1,46 @@ +# cmd_twitch/quote.py +from twitchio.ext import commands +from cmd_common import common_commands as cc +from modules.utility import is_channel_live +import globals + +def setup(bot): + """ + Registers the '!quote' command for Twitch. + """ + @bot.command(name="quote") + async def cmd_quote(ctx: commands.Context): + """ + Handles the !quote command with multiple subcommands. + + Usage: + - !quote + -> Retrieves a random (non-removed) quote. + - !quote + -> Retrieves the specific quote by ID. + - !quote add + -> Adds a new quote and replies with its quote number. + - !quote remove + -> Removes the specified quote. + - !quote restore + -> Restores a previously removed quote. + - !quote info + -> Displays stored information about the quote. + - !quote search [keywords] + -> Searches for the best matching quote. + - !quote last/latest/newest + -> Retrieves the latest (most recent) non-removed quote. + """ + if not await is_channel_live(bot): + if not globals.init_db_conn: + await ctx.reply("Database is unavailable, sorry.") + return + + args = ctx.message.content.strip().split()[1:] + result = await cc.handle_quote_command( + db_conn=globals.init_db_conn, + is_discord=False, + ctx=ctx, + args=args + ) + await ctx.reply(result) diff --git a/settings/discord_bot_settings.json b/settings/discord_bot_settings.json new file mode 100644 index 0000000..e615e28 --- /dev/null +++ b/settings/discord_bot_settings.json @@ -0,0 +1,26 @@ +{ + "notes_on_activity_mode": { + "0": "Disable bot activites", + "1": "Static activity", + "2": "Rotating activity", + "3": "Dynamic activity" + }, + "activity_mode": 2, + "static_activity": { + "type": "Custom", + "name": "Listening for howls!" + }, + "rotating_activities": [ + { "type": "Listening", "name": "howls" }, + { "type": "Playing", "name": "with my commands" }, + { "type": "Watching", "name": "Twitch streams" }, + { "type": "Watching", "name": "Kami code" }, + { "type": "Custom", "name": "I AM EVOLVING!"}, + { "type": "Watching", "name": "the Elder do their thing"} + ], + "dynamic_activities": { + "twitch_live": { "type": "Streaming", "name": "OokamiKunTV", "url": "https://twitch.tv/OokamiKunTV" }, + "default_idle": { "type": "Playing", "name": "around in Discord" } + }, + "rotation_interval": 300 +}