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 tweakskami_dev
parent
78e24a4641
commit
766c3ab690
|
@ -1,9 +1,10 @@
|
||||||
# bot_discord.py
|
# bot_discord.py
|
||||||
import discord
|
import discord
|
||||||
from discord import app_commands
|
from discord import app_commands
|
||||||
from discord.ext import commands
|
from discord.ext import commands, tasks
|
||||||
import importlib
|
import importlib
|
||||||
import cmd_discord
|
import cmd_discord
|
||||||
|
import json
|
||||||
|
|
||||||
import globals
|
import globals
|
||||||
|
|
||||||
|
@ -150,6 +151,20 @@ class DiscordBot(commands.Bot):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
globals.log(f"Command processing failed: {e}", "ERROR")
|
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):
|
async def on_command(self, ctx):
|
||||||
"""Logs every command execution at DEBUG level."""
|
"""Logs every command execution at DEBUG level."""
|
||||||
|
@ -184,6 +199,13 @@ class DiscordBot(commands.Bot):
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
"""Runs when the bot successfully logs in."""
|
"""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
|
# Sync Slash Commands
|
||||||
try:
|
try:
|
||||||
# Sync slash commands globally
|
# Sync slash commands globally
|
||||||
|
@ -216,6 +238,73 @@ class DiscordBot(commands.Bot):
|
||||||
globals.log("Discord bot has lost connection!", "WARNING")
|
globals.log("Discord bot has lost connection!", "WARNING")
|
||||||
log_bot_event(self.db_conn, "DISCORD_DISCONNECTED", "Discord bot lost connection.")
|
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):
|
async def on_voice_state_update(self, member, before, after):
|
||||||
"""
|
"""
|
||||||
Tracks user joins, leaves, mutes, deafens, streams, and voice channel moves.
|
Tracks user joins, leaves, mutes, deafens, streams, and voice channel moves.
|
||||||
|
|
162
cmd_discord.py
162
cmd_discord.py
|
@ -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 <number>
|
|
||||||
-> Retrieves a specific quote by its ID.
|
|
||||||
- !quote add <quote text>
|
|
||||||
-> Adds a new quote and replies with its quote number.
|
|
||||||
- !quote remove <number>
|
|
||||||
-> Removes the specified quote.
|
|
||||||
- !quote restore <number>
|
|
||||||
-> Restores a previously removed quote.
|
|
||||||
- !quote info <number>
|
|
||||||
-> 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 <user>
|
|
||||||
-> 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 <command>
|
|
||||||
-> 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")
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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 <command>
|
||||||
|
-> 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)
|
|
@ -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 <user> -> Looks up howling stats for a user.
|
||||||
|
"""
|
||||||
|
result = cc.handle_howl_command(ctx)
|
||||||
|
await ctx.reply(result)
|
|
@ -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)
|
|
@ -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 <number>
|
||||||
|
-> Retrieves a specific quote by its ID.
|
||||||
|
- !quote add <quote text>
|
||||||
|
-> Adds a new quote and replies with its quote number.
|
||||||
|
- !quote remove <number>
|
||||||
|
-> Removes the specified quote.
|
||||||
|
- !quote restore <number>
|
||||||
|
-> Restores a previously removed quote.
|
||||||
|
- !quote info <number>
|
||||||
|
-> 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)
|
202
cmd_twitch.py
202
cmd_twitch.py
|
@ -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 <number>
|
|
||||||
# -> Retrieves the specific quote by ID.
|
|
||||||
# - !quote add <quote text>
|
|
||||||
# -> Adds a new quote and replies with its quote number.
|
|
||||||
# - !quote remove <number>
|
|
||||||
# -> Removes the specified quote.
|
|
||||||
# - !quote info <number>
|
|
||||||
# -> 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 <number>
|
|
||||||
-> Retrieves the specific quote by ID.
|
|
||||||
- !quote add <quote text>
|
|
||||||
-> Adds a new quote and replies with its quote number.
|
|
||||||
- !quote remove <number>
|
|
||||||
-> Removes the specified quote.
|
|
||||||
- `!quote restore <number>`
|
|
||||||
-> Restores a previously removed quote.
|
|
||||||
- !quote info <number>
|
|
||||||
-> 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")
|
|
|
@ -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)
|
|
@ -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 <keywords>
|
||||||
|
-> Displays a fun fact related to the given keywords.
|
||||||
|
"""
|
||||||
|
fact = cc.get_fun_fact(list(keywords))
|
||||||
|
await ctx.reply(fact)
|
|
@ -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)
|
|
@ -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 <command> -> 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)
|
|
@ -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 <user> -> Looks up howling stats for a user.
|
||||||
|
"""
|
||||||
|
if not await is_channel_live(bot):
|
||||||
|
response = cc.handle_howl_command(ctx)
|
||||||
|
await ctx.reply(response)
|
|
@ -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)
|
|
@ -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 <number>
|
||||||
|
-> Retrieves the specific quote by ID.
|
||||||
|
- !quote add <quote text>
|
||||||
|
-> Adds a new quote and replies with its quote number.
|
||||||
|
- !quote remove <number>
|
||||||
|
-> Removes the specified quote.
|
||||||
|
- !quote restore <number>
|
||||||
|
-> Restores a previously removed quote.
|
||||||
|
- !quote info <number>
|
||||||
|
-> 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)
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue