# cmd_common/common_commands.py import random import time from modules import utility import globals from modules.db import run_db_operation def howl(username: str) -> str: """ Generates a howl response based on a random percentage. Uses a dictionary to allow flexible, randomized responses. """ howl_percentage = random.randint(0, 100) # Round percentage down to nearest 10 (except 0 and 100) rounded_percentage = 0 if howl_percentage == 0 else 100 if howl_percentage == 100 else (howl_percentage // 10) * 10 # Fetch a random response from the dictionary response = utility.get_random_reply("howl_replies", str(rounded_percentage), username=username, howl_percentage=howl_percentage) return response def ping() -> str: """ Returns a dynamic, randomized uptime response. """ # Use function to retrieve correct startup time and calculate uptime elapsed = time.time() - globals.get_bot_start_time() uptime_str, uptime_s = utility.format_uptime(elapsed) # Define threshold categories thresholds = [3600, 10800, 21600, 43200, 86400, 172800, 259200, 345600, 432000, 518400, 604800, 1209600, 2592000, 7776000, 15552000, 23328000, 31536000] # Find the highest matching threshold selected_threshold = max([t for t in thresholds if uptime_s >= t], default=3600) # Get a random response from the dictionary response = utility.get_random_reply("ping_replies", str(selected_threshold), uptime_str=uptime_str) return response def greet(target_display_name: str, platform_name: str) -> str: """ Returns a greeting string for the given user displayname on a given platform. """ return f"Hello {target_display_name}, welcome to {platform_name}!" ###################### # Quotes ###################### def create_quotes_table(db_conn, log_func): """ Creates the 'quotes' table if it does not exist, with the columns: ID, QUOTE_TEXT, QUOTEE, QUOTE_CHANNEL, QUOTE_DATETIME, QUOTE_GAME, QUOTE_REMOVED Uses a slightly different CREATE statement depending on MariaDB vs SQLite. """ if not db_conn: log_func("No database connection available to create quotes table!", "FATAL") return # Detect if this is SQLite or MariaDB db_name = str(type(db_conn)).lower() if 'sqlite3' in db_name: # SQLite create_table_sql = """ CREATE TABLE IF NOT EXISTS quotes ( ID INTEGER PRIMARY KEY AUTOINCREMENT, QUOTE_TEXT TEXT, QUOTEE TEXT, QUOTE_CHANNEL TEXT, QUOTE_DATETIME TEXT, QUOTE_GAME TEXT, QUOTE_REMOVED BOOLEAN DEFAULT 0 ) """ else: # Assume MariaDB # Adjust column types as appropriate for your setup create_table_sql = """ CREATE TABLE IF NOT EXISTS quotes ( ID INT PRIMARY KEY AUTO_INCREMENT, QUOTE_TEXT TEXT, QUOTEE VARCHAR(100), QUOTE_CHANNEL VARCHAR(100), QUOTE_DATETIME DATETIME DEFAULT CURRENT_TIMESTAMP, QUOTE_GAME VARCHAR(200), QUOTE_REMOVED BOOLEAN DEFAULT FALSE ) """ run_db_operation(db_conn, "write", create_table_sql, log_func=log_func) async def handle_quote_command(db_conn, log_func, is_discord: bool, ctx, args, get_twitch_game_for_channel=None): """ Core logic for !quote command, shared by both Discord and Twitch. - `db_conn`: your active DB connection - `log_func`: your log(...) function - `is_discord`: True if this command is being called from Discord, False if from Twitch - `ctx`: the context object (discord.py ctx or twitchio context) - `args`: a list of arguments (e.g. ["add", "some quote text..."] or ["remove", "3"] or ["2"] etc.) - `get_twitch_game_for_channel`: function(channel_name) -> str or None Behavior: 1) `!quote add some text here` -> Adds a new quote, stores channel=Discord or twitch channel name, game if twitch. 2) `!quote remove N` -> Mark quote #N as removed. 3) `!quote N` -> Retrieve quote #N, if not removed. 4) `!quote` (no args) -> Retrieve a random (not-removed) quote. """ # If no subcommand, treat as "random" if len(args) == 0: return await retrieve_random_quote(db_conn, log_func, is_discord, ctx) sub = args[0].lower() if sub == "add": # everything after "add" is the quote text quote_text = " ".join(args[1:]).strip() if not quote_text: return await send_message(ctx, "Please provide the quote text after 'add'.") await add_new_quote(db_conn, log_func, is_discord, ctx, quote_text, get_twitch_game_for_channel) elif sub == "remove": if len(args) < 2: return await send_message(ctx, "Please specify which quote ID to remove.") await remove_quote(db_conn, log_func, ctx, args[1]) else: # Possibly a quote ID if sub.isdigit(): quote_id = int(sub) await retrieve_specific_quote(db_conn, log_func, ctx, quote_id) else: # unrecognized subcommand => fallback to random await retrieve_random_quote(db_conn, log_func, is_discord, ctx) async def add_new_quote(db_conn, log_func, is_discord, ctx, quote_text, get_twitch_game_for_channel): """ Insert a new quote into the DB. QUOTEE = the user who typed the command QUOTE_CHANNEL = "Discord" or the twitch channel name QUOTE_GAME = The current game if from Twitch, None if from Discord QUOTE_REMOVED = false by default QUOTE_DATETIME = current date/time (or DB default) """ user_name = get_author_name(ctx, is_discord) channel_name = "Discord" if is_discord else get_channel_name(ctx) game_name = None if not is_discord and get_twitch_game_for_channel: # Attempt to get the current game from the Twitch API (placeholder function) game_name = get_twitch_game_for_channel(channel_name) # might return str or None # Insert quote insert_sql = """ INSERT INTO quotes (QUOTE_TEXT, QUOTEE, QUOTE_CHANNEL, QUOTE_DATETIME, QUOTE_GAME, QUOTE_REMOVED) VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?, 0) """ # For MariaDB, parameter placeholders are often %s, but if you set paramstyle='qmark', it can use ? as well. # Adjust if needed for your environment. params = (quote_text, user_name, channel_name, game_name) result = run_db_operation(db_conn, "write", insert_sql, params, log_func=log_func) if result is not None: await send_message(ctx, "Quote added successfully!") else: await send_message(ctx, "Failed to add quote.") async def remove_quote(db_conn, log_func, ctx, quote_id_str): """ Mark quote #ID as removed (QUOTE_REMOVED=1). """ if not quote_id_str.isdigit(): return await send_message(ctx, f"'{quote_id_str}' is not a valid quote ID.") quote_id = int(quote_id_str) remover_user = str(ctx.author.name) # Mark as removed update_sql = """ UPDATE quotes SET QUOTE_REMOVED = 1, QUOTE_REMOVED_BY = ? WHERE ID = ? AND QUOTE_REMOVED = 0 """ params = (remover_user, quote_id) rowcount = run_db_operation(db_conn, "update", update_sql, params, log_func=log_func) if rowcount and rowcount > 0: await send_message(ctx, f"Removed quote #{quote_id}.") else: await send_message(ctx, "Could not remove that quote (maybe it's already removed or doesn't exist).") async def retrieve_specific_quote(db_conn, log_func, ctx, quote_id): """ Retrieve a specific quote by ID, if not removed. If not found, or removed, inform user of the valid ID range (1 - {max_id}) If no quotes exist at all, say "No quotes are created yet." """ # First, see if we have any quotes at all max_id = get_max_quote_id(db_conn, log_func) if max_id < 1: return await send_message(ctx, "No quotes are created yet.") # Query for that specific quote select_sql = """ SELECT ID, QUOTE_TEXT, QUOTEE, QUOTE_CHANNEL, QUOTE_DATETIME, QUOTE_GAME, QUOTE_REMOVED, QUOTE_REMOVED_BY FROM quotes WHERE ID = ? """ rows = run_db_operation(db_conn, "read", select_sql, (quote_id,), log_func=log_func) if not rows: # no match return await send_message(ctx, f"I couldn't find that quote (1-{max_id}).") row = rows[0] quote_number = row[0] quote_text = row[1] quotee = row[2] quote_channel = row[3] quote_datetime = row[4] quote_game = row[5] quote_removed = row[6] quote_removed_by = row[7] if row[7] else "Unknown" if quote_removed == 1: # It's removed await send_message(ctx, f"Quote {quote_number}: [REMOVED by {quote_removed_by}]") else: # It's not removed await send_message(ctx, f"Quote {quote_number}: {quote_text}") async def retrieve_random_quote(db_conn, log_func, is_discord, ctx): """ Grab a random quote (QUOTE_REMOVED=0). If no quotes exist or all removed, respond with "No quotes are created yet." """ # First check if we have any quotes max_id = get_max_quote_id(db_conn, log_func) if max_id < 1: return await send_message(ctx, "No quotes are created yet.") # We have quotes, try selecting a random one from the not-removed set if is_sqlite(db_conn): random_sql = """ SELECT ID, QUOTE_TEXT FROM quotes WHERE QUOTE_REMOVED = 0 ORDER BY RANDOM() LIMIT 1 """ else: # MariaDB uses RAND() random_sql = """ SELECT ID, QUOTE_TEXT FROM quotes WHERE QUOTE_REMOVED = 0 ORDER BY RAND() LIMIT 1 """ rows = run_db_operation(db_conn, "read", random_sql, log_func=log_func) if not rows: return await send_message(ctx, "No quotes are created yet.") quote_number, quote_text = rows[0] await send_message(ctx, f"Quote {quote_number}: {quote_text}") def get_max_quote_id(db_conn, log_func): """ Return the highest ID in the quotes table, or 0 if empty. """ sql = "SELECT MAX(ID) FROM quotes" rows = run_db_operation(db_conn, "read", sql, log_func=log_func) if rows and rows[0] and rows[0][0] is not None: return rows[0][0] return 0 def is_sqlite(db_conn): return 'sqlite3' in str(type(db_conn)).lower() def get_author_name(ctx, is_discord): """ Return the name/username of the command author. For Discord, it's ctx.author.display_name (or ctx.author.name). For Twitch (twitchio), it's ctx.author.name. """ if is_discord: return str(ctx.author.display_name) else: return str(ctx.author.name) def get_channel_name(ctx): """ Return the channel name for Twitch. For example, ctx.channel.name in twitchio. """ # In twitchio, ctx.channel has .name return str(ctx.channel.name) async def send_message(ctx, text): """ Minimal helper to send a message to either Discord or Twitch. For discord.py: await ctx.send(text) For twitchio: await ctx.send(text) """ await ctx.send(text)