Started implementing Universal User Identification (UUI)

- added database table "users"
- table allows for assigning individual users a UUID for universal processing
- will support account linking in the future

- fixed a bug with reporting Discord commands in logs
kami_dev
Kami 2025-02-05 00:33:02 +01:00
parent 8074fbbef4
commit aed3d24e33
4 changed files with 185 additions and 5 deletions

10
bots.py
View File

@ -14,7 +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
from modules.db import ensure_quotes_table, ensure_users_table
# Load environment variables
load_dotenv()
@ -56,7 +56,7 @@ def log(message, level="INFO", exec_info=False):
See 'config.json' for disabling/enabling logging levels
"""
from modules import utility
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"]
log_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"}
if level not in log_levels:
level = "INFO" # Default to INFO if an invalid level is provided
@ -99,7 +99,6 @@ async def main():
# Log initial start
log("--------------- BOT STARTUP ---------------")
# Before creating your DiscordBot/TwitchBot, initialize DB
db_conn = init_db_connection(config_data, log)
if not db_conn:
@ -110,6 +109,7 @@ async def main():
# auto-create the quotes table if it doesn't exist
try:
ensure_quotes_table(db_conn, log)
ensure_users_table(db_conn, log)
except Exception as e:
log(f"Critical: unable to ensure quotes table: {e}", "FATAL")
@ -132,6 +132,10 @@ async def main():
discord_task = asyncio.create_task(discord_bot.run(os.getenv("DISCORD_BOT_TOKEN")))
twitch_task = asyncio.create_task(twitch_bot.run())
from modules.utility import dev_func
dev_func_result = dev_func(db_conn, log)
log(f"dev_func output: {dev_func_result}")
await asyncio.gather(discord_task, twitch_task)
if __name__ == "__main__":

View File

@ -91,6 +91,7 @@ def setup(bot, db_conn=None, log=None):
######################
# Debug: Print that commands are being registered
try:
bot.log(f"Registering commands for Discord: {list(bot.commands.keys())}", "DEBUG")
command_names = [cmd.name for cmd in bot.commands] # Extract command names
bot.log(f"Registering commands for Discord: {command_names}", "DEBUG")
except Exception as e:
bot.log(f"An error occured while printing registered commands for Discord: {e}", "WARNING")

View File

@ -212,3 +212,165 @@ def ensure_quotes_table(db_conn, log_func):
raise RuntimeError(error_msg)
log_func("Successfully created table 'quotes'.")
#######################
# Ensure 'users' table
#######################
def ensure_users_table(db_conn, log_func):
"""
Checks if 'users' table exists. If not, creates it.
The 'users' table tracks user linkage across platforms:
- UUID: (PK) The universal ID for the user
- discord_user_id, discord_username, discord_user_display_name
- twitch_user_id, twitch_username, twitch_user_display_name
- datetime_linked (DATE/TIME of row creation)
- user_is_banned (BOOLEAN)
- user_is_bot (BOOLEAN)
This helps unify data for a single 'person' across Discord & Twitch.
"""
is_sqlite = "sqlite3" in str(type(db_conn)).lower()
# 1) Check existence
if is_sqlite:
check_sql = """
SELECT name
FROM sqlite_master
WHERE type='table'
AND name='users'
"""
else:
check_sql = """
SELECT table_name
FROM information_schema.tables
WHERE table_name = 'users'
AND table_schema = DATABASE()
"""
rows = run_db_operation(db_conn, "read", check_sql, log_func=log_func)
if rows and rows[0] and rows[0][0]:
log_func("Table 'users' already exists, skipping creation.", "DEBUG")
return
# 2) Table does NOT exist => create it
log_func("Table 'users' does not exist; creating now...")
if is_sqlite:
create_table_sql = """
CREATE TABLE users (
UUID TEXT PRIMARY KEY,
discord_user_id TEXT,
discord_username TEXT,
discord_user_display_name TEXT,
twitch_user_id TEXT,
twitch_username TEXT,
twitch_user_display_name TEXT,
datetime_linked TEXT DEFAULT CURRENT_TIMESTAMP,
user_is_banned BOOLEAN DEFAULT 0,
user_is_bot BOOLEAN DEFAULT 0
)
"""
else:
create_table_sql = """
CREATE TABLE users (
UUID VARCHAR(36) PRIMARY KEY,
discord_user_id VARCHAR(100),
discord_username VARCHAR(100),
discord_user_display_name VARCHAR(100),
twitch_user_id VARCHAR(100),
twitch_username VARCHAR(100),
twitch_user_display_name VARCHAR(100),
datetime_linked DATETIME DEFAULT CURRENT_TIMESTAMP,
user_is_banned BOOLEAN DEFAULT FALSE,
user_is_bot BOOLEAN DEFAULT FALSE
)
"""
result = run_db_operation(db_conn, "write", create_table_sql, log_func=log_func)
if result is None:
error_msg = "Failed to create 'users' table!"
log_func(error_msg, "ERROR")
raise RuntimeError(error_msg)
log_func("Successfully created table 'users'.")
########################
# Lookup user function
########################
def lookup_user(db_conn, log_func, identifier, identifier_type="discord_user_id"):
"""
Looks up a user in the 'users' table based on the given identifier_type:
- "uuid"
- "discord_user_id"
- "discord_username"
- "twitch_user_id"
- "twitch_username"
You can add more if needed.
Returns a dictionary with all columns:
{
"UUID": str,
"discord_user_id": str or None,
"discord_username": str or None,
"discord_user_display_name": str or None,
"twitch_user_id": str or None,
"twitch_username": str or None,
"twitch_user_display_name": str or None,
"datetime_linked": str (or datetime in MariaDB),
"user_is_banned": bool or int,
"user_is_bot": bool or int
}
If not found, returns None.
"""
valid_cols = ["uuid", "discord_user_id", "discord_username",
"twitch_user_id", "twitch_username"]
if identifier_type.lower() not in valid_cols:
if log_func:
log_func(f"lookup_user error: invalid identifier_type={identifier_type}", "WARNING")
return None
# Build the query
query = f"""
SELECT
UUID,
discord_user_id,
discord_username,
discord_user_display_name,
twitch_user_id,
twitch_username,
twitch_user_display_name,
datetime_linked,
user_is_banned,
user_is_bot
FROM users
WHERE {identifier_type} = ?
LIMIT 1
"""
rows = run_db_operation(db_conn, "read", query, params=(identifier,), log_func=log_func)
if not rows:
return None
# We have at least one row
row = rows[0] # single row
# Build a dictionary
user_data = {
"UUID": row[0],
"discord_user_id": row[1],
"discord_username": row[2],
"discord_user_display_name": row[3],
"twitch_user_id": row[4],
"twitch_username": row[5],
"twitch_user_display_name": row[6],
"datetime_linked": row[7],
"user_is_banned": row[8],
"user_is_bot": row[9],
}
return user_data

View File

@ -421,3 +421,16 @@ async def send_message(ctx, text):
Minimal helper to send a message to either Discord or Twitch.
"""
await ctx.send(text)
###############################################
# Development Test Function (called upon start)
###############################################
def dev_func(db_conn, log):
from modules.db import lookup_user
id = "203190147582394369"
id_type = "discord_user_id"
uui_info = lookup_user(db_conn, log, identifier=id, identifier_type=id_type)
if uui_info:
return list(uui_info.values())
else:
return f"User with identifier '{id}' ({id_type}) not found"