330 lines
11 KiB
Python
330 lines
11 KiB
Python
# 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) |