Massive UUI system and database overhaul
- Dynamic accounts association - Accounts can now be associated dynamically, allowing multiple accounts on the same platform to be associated to the same UUID. - UUI system now supports any platform, eg. YouTube, TikTok, Kick, Twitter, etc. - More robust user lookups with enhanced fault tolerance and allowance for NULL data. - Optimized database structure with two tables for user association; one for UUID and basic info, another for platform-specific details. - Enhanced logging functionality: logs now prefix the calling function. - Enhanced user lookup debug messages, allowing easy query inspection and data validation. - Other minor fixeskami_dev
parent
766c3ab690
commit
d0313a6a92
|
@ -93,17 +93,19 @@ class DiscordBot(commands.Bot):
|
||||||
else:
|
else:
|
||||||
guild_name = "DM"
|
guild_name = "DM"
|
||||||
channel_name = "Direct Message"
|
channel_name = "Direct Message"
|
||||||
|
|
||||||
globals.log(f"Message detected by '{message.author.name}' in '{guild_name}' - #'{channel_name}'", "DEBUG")
|
globals.log(f"Message detected by '{message.author.name}' in '{guild_name}' - #'{channel_name}'", "DEBUG")
|
||||||
#globals.log(f"Message body:\n{message}\nMessage content: {message.content}", "DEBUG") # Full message debug
|
globals.log(f"Message content: '{message.content}'", "DEBUG")
|
||||||
globals.log(f"Message content: '{message.content}'", "DEBUG") # Partial message debug (content only)
|
|
||||||
globals.log(f"Attempting UUI lookup on '{message.author.name}' ...", "DEBUG")
|
globals.log(f"Attempting UUI lookup on '{message.author.name}' ...", "DEBUG")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# If it's a bot message, ignore or pass user_is_bot=True
|
# If it's a bot message, ignore or pass user_is_bot=True
|
||||||
is_bot = message.author.bot
|
is_bot = message.author.bot
|
||||||
user_id = str(message.author.id)
|
user_id = str(message.author.id)
|
||||||
user_name = message.author.name # no discriminator
|
user_name = message.author.name # no discriminator
|
||||||
display_name = message.author.display_name
|
display_name = message.author.display_name
|
||||||
|
|
||||||
|
# Track user activity first
|
||||||
modules.utility.track_user_activity(
|
modules.utility.track_user_activity(
|
||||||
db_conn=self.db_conn,
|
db_conn=self.db_conn,
|
||||||
platform="discord",
|
platform="discord",
|
||||||
|
@ -113,33 +115,21 @@ class DiscordBot(commands.Bot):
|
||||||
user_is_bot=is_bot
|
user_is_bot=is_bot
|
||||||
)
|
)
|
||||||
|
|
||||||
user_data = lookup_user(db_conn=self.db_conn, identifier=user_id, identifier_type="discord_user_id")
|
# Let log_message() handle UUID lookup internally
|
||||||
user_uuid = user_data["UUID"] if user_data else "UNKNOWN"
|
platform_str = f"discord-{guild_name}"
|
||||||
globals.log(f"... UUI lookup complete", "DEBUG")
|
channel_str = channel_name
|
||||||
|
|
||||||
if user_uuid:
|
|
||||||
# The "platform" can be e.g. "discord" or you can store the server name
|
|
||||||
platform_str = f"discord-{guild_name}"
|
|
||||||
# The channel name can be message.channel.name or "DM" if it's a private channel
|
|
||||||
channel_str = channel_name
|
|
||||||
|
|
||||||
# If you have attachments, you could gather them as links.
|
attachments = ", ".join(a.url for a in message.attachments) if message.attachments else ""
|
||||||
try:
|
|
||||||
attachments = ", ".join(a.url for a in message.attachments) if message.attachments else ""
|
|
||||||
except Exception:
|
|
||||||
attachments = ""
|
|
||||||
|
|
||||||
log_message(
|
log_message(
|
||||||
db_conn=self.db_conn,
|
db_conn=self.db_conn,
|
||||||
user_uuid=user_uuid,
|
identifier=user_id,
|
||||||
message_content=message.content or "",
|
identifier_type="discord_user_id",
|
||||||
platform=platform_str,
|
message_content=message.content or "",
|
||||||
channel=channel_str,
|
platform=platform_str,
|
||||||
attachments=attachments,
|
channel=channel_str,
|
||||||
username=message.author.name
|
attachments=attachments
|
||||||
)
|
)
|
||||||
|
|
||||||
# PLACEHOLDER FOR FUTURE MESSAGE PROCESSING
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
globals.log(f"... UUI lookup failed: {e}", "WARNING")
|
globals.log(f"... UUI lookup failed: {e}", "WARNING")
|
||||||
pass
|
pass
|
||||||
|
@ -151,6 +141,7 @@ 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):
|
def load_bot_settings(self):
|
||||||
"""Loads bot activity settings from JSON file."""
|
"""Loads bot activity settings from JSON file."""
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -145,7 +145,10 @@ class TwitchBot(commands.Bot):
|
||||||
globals.log("Twitch token refreshed successfully. Restarting bot...")
|
globals.log("Twitch token refreshed successfully. Restarting bot...")
|
||||||
|
|
||||||
# Restart the TwitchIO connection
|
# Restart the TwitchIO connection
|
||||||
await self.close() # Close the old connection
|
try:
|
||||||
|
await self.close() # Close the old connection
|
||||||
|
except Exception as e:
|
||||||
|
globals.log(f"refresh_access_token() failed during close attempt: {e}", "WARNING")
|
||||||
await self.start() # Restart with the new token
|
await self.start() # Restart with the new token
|
||||||
|
|
||||||
return # Exit function after successful refresh
|
return # Exit function after successful refresh
|
||||||
|
|
10
bots.py
10
bots.py
|
@ -51,6 +51,7 @@ async def main():
|
||||||
"Bot events table": partial(db.ensure_bot_events_table, db_conn),
|
"Bot events table": partial(db.ensure_bot_events_table, db_conn),
|
||||||
"Quotes table": partial(db.ensure_quotes_table, db_conn),
|
"Quotes table": partial(db.ensure_quotes_table, db_conn),
|
||||||
"Users table": partial(db.ensure_users_table, db_conn),
|
"Users table": partial(db.ensure_users_table, db_conn),
|
||||||
|
"Platform_Mapping table": partial(db.ensure_platform_mapping_table, db_conn),
|
||||||
"Chatlog table": partial(db.ensure_chatlog_table, db_conn),
|
"Chatlog table": partial(db.ensure_chatlog_table, db_conn),
|
||||||
"Howls table": partial(db.ensure_userhowls_table, db_conn),
|
"Howls table": partial(db.ensure_userhowls_table, db_conn),
|
||||||
"Discord activity table": partial(db.ensure_discord_activity_table, db_conn),
|
"Discord activity table": partial(db.ensure_discord_activity_table, db_conn),
|
||||||
|
@ -69,7 +70,7 @@ async def main():
|
||||||
|
|
||||||
# Create both bots
|
# Create both bots
|
||||||
discord_bot = DiscordBot()
|
discord_bot = DiscordBot()
|
||||||
twitch_bot = TwitchBot()
|
#twitch_bot = TwitchBot()
|
||||||
|
|
||||||
# Log startup
|
# Log startup
|
||||||
utility.log_bot_startup(db_conn)
|
utility.log_bot_startup(db_conn)
|
||||||
|
@ -77,7 +78,7 @@ async def main():
|
||||||
# Provide DB connection to both bots
|
# Provide DB connection to both bots
|
||||||
try:
|
try:
|
||||||
discord_bot.set_db_connection(db_conn)
|
discord_bot.set_db_connection(db_conn)
|
||||||
twitch_bot.set_db_connection(db_conn)
|
#twitch_bot.set_db_connection(db_conn)
|
||||||
globals.log(f"Initialized database connection to both bots")
|
globals.log(f"Initialized database connection to both bots")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
globals.log(f"Unable to initialize database connection to one or both bots: {e}", "FATAL")
|
globals.log(f"Unable to initialize database connection to one or both bots: {e}", "FATAL")
|
||||||
|
@ -85,7 +86,7 @@ async def main():
|
||||||
globals.log("Starting Discord and Twitch bots...")
|
globals.log("Starting Discord and Twitch bots...")
|
||||||
|
|
||||||
discord_task = asyncio.create_task(discord_bot.run(os.getenv("DISCORD_BOT_TOKEN")))
|
discord_task = asyncio.create_task(discord_bot.run(os.getenv("DISCORD_BOT_TOKEN")))
|
||||||
twitch_task = asyncio.create_task(twitch_bot.run())
|
#twitch_task = asyncio.create_task(twitch_bot.run())
|
||||||
|
|
||||||
from modules.utility import dev_func
|
from modules.utility import dev_func
|
||||||
enable_dev_func = False
|
enable_dev_func = False
|
||||||
|
@ -93,7 +94,8 @@ async def main():
|
||||||
dev_func_result = dev_func(db_conn, enable_dev_func)
|
dev_func_result = dev_func(db_conn, enable_dev_func)
|
||||||
globals.log(f"dev_func output: {dev_func_result}")
|
globals.log(f"dev_func output: {dev_func_result}")
|
||||||
|
|
||||||
await asyncio.gather(discord_task, twitch_task)
|
#await asyncio.gather(discord_task, twitch_task)
|
||||||
|
await asyncio.gather(discord_task)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -87,14 +87,11 @@ def handle_howl_normal(ctx, platform, author_id, author_display_name) -> str:
|
||||||
utility.wfstl()
|
utility.wfstl()
|
||||||
db_conn = ctx.bot.db_conn
|
db_conn = ctx.bot.db_conn
|
||||||
|
|
||||||
# random logic
|
# Random logic for howl percentage
|
||||||
howl_val = random.randint(0, 100)
|
howl_val = random.randint(0, 100)
|
||||||
# round to nearest 10 except 0/100
|
rounded_val = 0 if howl_val == 0 else 100 if howl_val == 100 else (howl_val // 10) * 10
|
||||||
rounded_val = 0 if howl_val == 0 else \
|
|
||||||
100 if howl_val == 100 else \
|
|
||||||
(howl_val // 10) * 10
|
|
||||||
|
|
||||||
# dictionary-based reply
|
# Dictionary-based reply
|
||||||
reply = utility.get_random_reply(
|
reply = utility.get_random_reply(
|
||||||
"howl_replies",
|
"howl_replies",
|
||||||
str(rounded_val),
|
str(rounded_val),
|
||||||
|
@ -102,17 +99,22 @@ def handle_howl_normal(ctx, platform, author_id, author_display_name) -> str:
|
||||||
howl_percentage=howl_val
|
howl_percentage=howl_val
|
||||||
)
|
)
|
||||||
|
|
||||||
# find user in DB by ID
|
# Consistent UUID lookup
|
||||||
user_data = db.lookup_user(db_conn, identifier=author_id, identifier_type=platform)
|
user_data = db.lookup_user(db_conn, identifier=author_id, identifier_type=f"{platform}_user_id")
|
||||||
if user_data:
|
if user_data:
|
||||||
db.insert_howl(db_conn, user_data["UUID"], howl_val)
|
user_uuid = user_data["UUID"]
|
||||||
|
db.insert_howl(db_conn, user_uuid, howl_val)
|
||||||
else:
|
else:
|
||||||
globals.log(f"Could not find user by ID={author_id} on {platform}. Not storing howl.", "WARNING")
|
globals.log(f"Could not find user by ID={author_id} on {platform}. Not storing howl.", "WARNING")
|
||||||
|
|
||||||
utility.wfetl()
|
utility.wfetl()
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|
||||||
def handle_howl_stats(ctx, platform, target_name) -> str:
|
def handle_howl_stats(ctx, platform, target_name) -> str:
|
||||||
|
"""
|
||||||
|
Handles !howl stats subcommand for both community and individual users.
|
||||||
|
"""
|
||||||
utility.wfstl()
|
utility.wfstl()
|
||||||
db_conn = ctx.bot.db_conn
|
db_conn = ctx.bot.db_conn
|
||||||
|
|
||||||
|
@ -122,7 +124,7 @@ def handle_howl_stats(ctx, platform, target_name) -> str:
|
||||||
if not stats:
|
if not stats:
|
||||||
utility.wfetl()
|
utility.wfetl()
|
||||||
return "No howls have been recorded yet!"
|
return "No howls have been recorded yet!"
|
||||||
|
|
||||||
total_howls = stats["total_howls"]
|
total_howls = stats["total_howls"]
|
||||||
avg_howl = stats["average_howl"]
|
avg_howl = stats["average_howl"]
|
||||||
unique_users = stats["unique_users"]
|
unique_users = stats["unique_users"]
|
||||||
|
@ -137,12 +139,13 @@ def handle_howl_stats(ctx, platform, target_name) -> str:
|
||||||
f"0% Howls: {count_zero}, 100% Howls: {count_hundred}")
|
f"0% Howls: {count_zero}, 100% Howls: {count_hundred}")
|
||||||
|
|
||||||
# Otherwise, lookup a single user
|
# Otherwise, lookup a single user
|
||||||
user_data = lookup_user_by_name(db_conn, platform, target_name)
|
user_data = db.lookup_user(db_conn, identifier=target_name, identifier_type=f"{platform}_username")
|
||||||
if not user_data:
|
if not user_data:
|
||||||
utility.wfetl()
|
utility.wfetl()
|
||||||
return f"I don't know that user: {target_name}"
|
return f"I don't know that user: {target_name}"
|
||||||
|
|
||||||
stats = db.get_howl_stats(db_conn, user_data["UUID"])
|
user_uuid = user_data["UUID"]
|
||||||
|
stats = db.get_howl_stats(db_conn, user_uuid)
|
||||||
if not stats:
|
if not stats:
|
||||||
utility.wfetl()
|
utility.wfetl()
|
||||||
return f"{target_name} hasn't howled yet! (Try `!howl` to get started.)"
|
return f"{target_name} hasn't howled yet! (Try `!howl` to get started.)"
|
||||||
|
@ -156,12 +159,13 @@ def handle_howl_stats(ctx, platform, target_name) -> str:
|
||||||
f"(0% x{z}, 100% x{h})")
|
f"(0% x{z}, 100% x{h})")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def lookup_user_by_name(db_conn, platform, name_str):
|
def lookup_user_by_name(db_conn, platform, name_str):
|
||||||
"""
|
"""
|
||||||
Attempt to find a user by name on that platform, e.g. 'discord_username' or 'twitch_username'.
|
Consistent UUID resolution for usernames across platforms.
|
||||||
"""
|
"""
|
||||||
utility.wfstl()
|
utility.wfstl()
|
||||||
# same logic as before
|
|
||||||
if platform == "discord":
|
if platform == "discord":
|
||||||
ud = db.lookup_user(db_conn, name_str, "discord_user_display_name")
|
ud = db.lookup_user(db_conn, name_str, "discord_user_display_name")
|
||||||
if ud:
|
if ud:
|
||||||
|
@ -170,6 +174,7 @@ def lookup_user_by_name(db_conn, platform, name_str):
|
||||||
ud = db.lookup_user(db_conn, name_str, "discord_username")
|
ud = db.lookup_user(db_conn, name_str, "discord_username")
|
||||||
utility.wfetl()
|
utility.wfetl()
|
||||||
return ud
|
return ud
|
||||||
|
|
||||||
elif platform == "twitch":
|
elif platform == "twitch":
|
||||||
ud = db.lookup_user(db_conn, name_str, "twitch_user_display_name")
|
ud = db.lookup_user(db_conn, name_str, "twitch_user_display_name")
|
||||||
if ud:
|
if ud:
|
||||||
|
@ -178,12 +183,14 @@ def lookup_user_by_name(db_conn, platform, name_str):
|
||||||
ud = db.lookup_user(db_conn, name_str, "twitch_username")
|
ud = db.lookup_user(db_conn, name_str, "twitch_username")
|
||||||
utility.wfetl()
|
utility.wfetl()
|
||||||
return ud
|
return ud
|
||||||
|
|
||||||
else:
|
else:
|
||||||
globals.log(f"Unknown platform {platform} in lookup_user_by_name", "WARNING")
|
globals.log(f"Unknown platform '{platform}' in lookup_user_by_name", "WARNING")
|
||||||
utility.wfetl()
|
utility.wfetl()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def ping() -> str:
|
def ping() -> str:
|
||||||
"""
|
"""
|
||||||
Returns a dynamic, randomized uptime response.
|
Returns a dynamic, randomized uptime response.
|
||||||
|
@ -401,7 +408,7 @@ async def add_new_quote(db_conn, is_discord, ctx, quote_text, game_name: str = N
|
||||||
# Lookup UUID from users table
|
# Lookup UUID from users table
|
||||||
user_data = db.lookup_user(db_conn, identifier=user_id, identifier_type=f"{platform}_user_id")
|
user_data = db.lookup_user(db_conn, identifier=user_id, identifier_type=f"{platform}_user_id")
|
||||||
if not user_data:
|
if not user_data:
|
||||||
globals.log(f"ERROR: Could not find UUID for user {ctx.author.name} ({user_id}) on {platform}. Quote not saved.", "ERROR")
|
globals.log(f"Could not find UUID for user {ctx.author.name} ({user_id}) on {platform}. Quote not saved.", "ERROR")
|
||||||
utility.wfetl()
|
utility.wfetl()
|
||||||
return "Could not save quote. Your user data is missing from the system."
|
return "Could not save quote. Your user data is missing from the system."
|
||||||
|
|
||||||
|
@ -410,7 +417,7 @@ async def add_new_quote(db_conn, is_discord, ctx, quote_text, game_name: str = N
|
||||||
if is_discord or not game_name:
|
if is_discord or not game_name:
|
||||||
game_name = None
|
game_name = None
|
||||||
|
|
||||||
# Insert quote
|
# Insert quote using UUID for QUOTEE
|
||||||
insert_sql = """
|
insert_sql = """
|
||||||
INSERT INTO quotes (QUOTE_TEXT, QUOTEE, QUOTE_CHANNEL, QUOTE_DATETIME, QUOTE_GAME, QUOTE_REMOVED)
|
INSERT INTO quotes (QUOTE_TEXT, QUOTEE, QUOTE_CHANNEL, QUOTE_DATETIME, QUOTE_GAME, QUOTE_REMOVED)
|
||||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?, 0)
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?, 0)
|
||||||
|
@ -428,6 +435,7 @@ async def add_new_quote(db_conn, is_discord, ctx, quote_text, game_name: str = N
|
||||||
return "Failed to add quote."
|
return "Failed to add quote."
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def remove_quote(db_conn, is_discord: bool, ctx, quote_id_str):
|
async def remove_quote(db_conn, is_discord: bool, ctx, quote_id_str):
|
||||||
"""
|
"""
|
||||||
Mark quote #ID as removed (QUOTE_REMOVED=1) and record removal datetime.
|
Mark quote #ID as removed (QUOTE_REMOVED=1) and record removal datetime.
|
||||||
|
@ -443,7 +451,7 @@ async def remove_quote(db_conn, is_discord: bool, ctx, quote_id_str):
|
||||||
# Lookup UUID from users table
|
# Lookup UUID from users table
|
||||||
user_data = db.lookup_user(db_conn, identifier=user_id, identifier_type=f"{platform}_user_id")
|
user_data = db.lookup_user(db_conn, identifier=user_id, identifier_type=f"{platform}_user_id")
|
||||||
if not user_data:
|
if not user_data:
|
||||||
globals.log(f"ERROR: Could not find UUID for user {ctx.author.name} ({user_id}) on {platform}. Quote not removed.", "ERROR")
|
globals.log(f"Could not find UUID for user {ctx.author.name} ({user_id}) on {platform}. Quote not removed.", "ERROR")
|
||||||
utility.wfetl()
|
utility.wfetl()
|
||||||
return "Could not remove quote. Your user data is missing from the system."
|
return "Could not remove quote. Your user data is missing from the system."
|
||||||
|
|
||||||
|
@ -550,7 +558,7 @@ async def retrieve_specific_quote(db_conn, ctx, quote_id, is_discord):
|
||||||
# Lookup UUID from users table for the quoter
|
# Lookup UUID from users table for the quoter
|
||||||
user_data = db.lookup_user(db_conn, identifier=quotee, identifier_type="UUID")
|
user_data = db.lookup_user(db_conn, identifier=quotee, identifier_type="UUID")
|
||||||
if not user_data:
|
if not user_data:
|
||||||
globals.log(f"ERROR: Could not find platform name for quotee UUID {quotee}. Default to 'Unknown'", "ERROR")
|
globals.log(f"Could not find platform name for quotee UUID {quotee}. Default to 'Unknown'", "INFO")
|
||||||
quotee_display = "Unknown"
|
quotee_display = "Unknown"
|
||||||
else:
|
else:
|
||||||
quotee_display = user_data[f"{platform}_user_display_name"]
|
quotee_display = user_data[f"{platform}_user_display_name"]
|
||||||
|
@ -559,7 +567,7 @@ async def retrieve_specific_quote(db_conn, ctx, quote_id, is_discord):
|
||||||
# Lookup UUID for removed_by if removed
|
# Lookup UUID for removed_by if removed
|
||||||
removed_user_data = db.lookup_user(db_conn, identifier=quote_removed_by, identifier_type="UUID")
|
removed_user_data = db.lookup_user(db_conn, identifier=quote_removed_by, identifier_type="UUID")
|
||||||
if not removed_user_data:
|
if not removed_user_data:
|
||||||
globals.log(f"ERROR: Could not find platform name for remover UUID {quote_removed_by}. Default to 'Unknown'", "ERROR")
|
globals.log(f"Could not find platform name for remover UUID {quote_removed_by}. Default to 'Unknown'", "INFO")
|
||||||
quote_removed_by_display = "Unknown"
|
quote_removed_by_display = "Unknown"
|
||||||
else:
|
else:
|
||||||
quote_removed_by_display = removed_user_data[f"{platform}_user_display_name"]
|
quote_removed_by_display = removed_user_data[f"{platform}_user_display_name"]
|
||||||
|
@ -686,10 +694,11 @@ async def retrieve_quote_info(db_conn, ctx, quote_id, is_discord):
|
||||||
# Lookup display name for the quoter
|
# Lookup display name for the quoter
|
||||||
user_data = db.lookup_user(db_conn, identifier=quotee, identifier_type="UUID")
|
user_data = db.lookup_user(db_conn, identifier=quotee, identifier_type="UUID")
|
||||||
if not user_data:
|
if not user_data:
|
||||||
globals.log(f"ERROR: Could not find display name for quotee UUID {quotee}.", "ERROR")
|
globals.log(f"Could not find display name for quotee UUID {quotee}.", "INFO")
|
||||||
quotee_display = "Unknown"
|
quotee_display = "Unknown"
|
||||||
else:
|
else:
|
||||||
quotee_display = user_data.get(f"{platform}_user_display_name", "Unknown")
|
# Use display name or fallback to platform username
|
||||||
|
quotee_display = user_data.get(f"platform_display_name", user_data.get("platform_username", "Unknown"))
|
||||||
|
|
||||||
info_lines = []
|
info_lines = []
|
||||||
info_lines.append(f"Quote #{quote_number} was quoted by {quotee_display} on {quote_datetime}.")
|
info_lines.append(f"Quote #{quote_number} was quoted by {quotee_display} on {quote_datetime}.")
|
||||||
|
@ -703,10 +712,11 @@ async def retrieve_quote_info(db_conn, ctx, quote_id, is_discord):
|
||||||
if quote_removed_by:
|
if quote_removed_by:
|
||||||
removed_user_data = db.lookup_user(db_conn, identifier=quote_removed_by, identifier_type="UUID")
|
removed_user_data = db.lookup_user(db_conn, identifier=quote_removed_by, identifier_type="UUID")
|
||||||
if not removed_user_data:
|
if not removed_user_data:
|
||||||
globals.log(f"ERROR: Could not find display name for remover UUID {quote_removed_by}.", "ERROR")
|
globals.log(f"Could not find display name for remover UUID {quote_removed_by}.", "INFO")
|
||||||
quote_removed_by_display = "Unknown"
|
quote_removed_by_display = "Unknown"
|
||||||
else:
|
else:
|
||||||
quote_removed_by_display = removed_user_data.get(f"{platform}_user_display_name", "Unknown")
|
# Use display name or fallback to platform username
|
||||||
|
quote_removed_by_display = removed_user_data.get(f"platform_display_name", removed_user_data.get("platform_username", "Unknown"))
|
||||||
else:
|
else:
|
||||||
quote_removed_by_display = "Unknown"
|
quote_removed_by_display = "Unknown"
|
||||||
removed_info = f"Removed by {quote_removed_by_display}"
|
removed_info = f"Removed by {quote_removed_by_display}"
|
||||||
|
@ -784,10 +794,11 @@ async def search_quote(db_conn, keywords, is_discord, ctx):
|
||||||
# Lookup display name for quotee using UUID.
|
# Lookup display name for quotee using UUID.
|
||||||
user_data = db.lookup_user(db_conn, identifier=quotee, identifier_type="UUID")
|
user_data = db.lookup_user(db_conn, identifier=quotee, identifier_type="UUID")
|
||||||
if user_data:
|
if user_data:
|
||||||
quotee_display = user_data.get(f"{'discord' if is_discord else 'twitch'}_user_display_name", "")
|
# Use display name or fallback to platform username
|
||||||
|
quotee_display = user_data.get("platform_display_name", user_data.get("platform_username", "Unknown"))
|
||||||
else:
|
else:
|
||||||
quotee_display = ""
|
globals.log(f"Could not find display name for quotee UUID {quotee}.", "INFO")
|
||||||
|
quotee_display = "Unknown"
|
||||||
# For each keyword, check each field.
|
# For each keyword, check each field.
|
||||||
# Award 2 points for a whole word match and 1 point for a partial match.
|
# Award 2 points for a whole word match and 1 point for a partial match.
|
||||||
score_total = 0
|
score_total = 0
|
||||||
|
|
89
globals.py
89
globals.py
|
@ -3,6 +3,7 @@ import json
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import discord
|
import discord
|
||||||
|
import inspect
|
||||||
|
|
||||||
# Store the start time globally.
|
# Store the start time globally.
|
||||||
_bot_start_time = time.time()
|
_bot_start_time = time.time()
|
||||||
|
@ -60,7 +61,8 @@ def log(message: str, level="INFO", exec_info=False, linebreaks=False):
|
||||||
Log a message with the specified log level.
|
Log a message with the specified log level.
|
||||||
|
|
||||||
Capable of logging individual levels to the terminal and/or logfile separately.
|
Capable of logging individual levels to the terminal and/or logfile separately.
|
||||||
Can also append traceback information if needed, and is capable of preserving/removing linebreaks from log messages as needed.
|
Can also append traceback information if needed, and is capable of preserving/removing
|
||||||
|
linebreaks from log messages as needed. Also prepends the calling function name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
message (str): The message to log.
|
message (str): The message to log.
|
||||||
|
@ -83,58 +85,73 @@ def log(message: str, level="INFO", exec_info=False, linebreaks=False):
|
||||||
log("An error occured during processing", "ERROR", exec_info=True, linebreaks=False)
|
log("An error occured during processing", "ERROR", exec_info=True, linebreaks=False)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Initiate logfile
|
# Hard-coded options/settings (can be expanded as needed)
|
||||||
lfp = config_data["logging"]["logfile_path"] # Log File Path
|
default_level = "INFO" # Fallback log level
|
||||||
clfp = f"cur_{lfp}" # Current Log File Path
|
allowed_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"}
|
||||||
if not config_data["logging"]["terminal"]["log_to_terminal"] and not config_data["logging"]["file"]["log_to_file"]:
|
|
||||||
print(f"!!! WARNING !!! CONSOLE AND LOGFILE OUTPUT DISABLED !!!\n!!! NO LOGS WILL BE PROVIDED !!!")
|
|
||||||
|
|
||||||
|
# Ensure valid level
|
||||||
|
if level not in allowed_levels:
|
||||||
|
level = default_level
|
||||||
|
|
||||||
|
# Capture the calling function's name using inspect
|
||||||
|
try:
|
||||||
|
caller_frame = inspect.stack()[1]
|
||||||
|
caller_name = caller_frame.function
|
||||||
|
except Exception:
|
||||||
|
caller_name = "Unknown"
|
||||||
|
|
||||||
|
# Optionally remove linebreaks if not desired
|
||||||
|
if not linebreaks:
|
||||||
|
message = message.replace("\n", " ")
|
||||||
|
|
||||||
|
# Get current timestamp and uptime
|
||||||
|
elapsed = time.time() - get_bot_start_time() # Assuming this function is defined elsewhere
|
||||||
from modules import utility
|
from modules import utility
|
||||||
log_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"}
|
uptime_str, _ = utility.format_uptime(elapsed)
|
||||||
|
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
if level not in log_levels:
|
# Prepend dynamic details including the caller name
|
||||||
level = "INFO" # Default to INFO if an invalid level is provided
|
log_message = f"[{timestamp} - {uptime_str}] [{level}] [Func: {caller_name}] {message}"
|
||||||
|
|
||||||
|
# Append traceback if required or for error levels
|
||||||
|
if exec_info or level in {"ERROR", "CRITICAL", "FATAL"}:
|
||||||
|
log_message += f"\n{traceback.format_exc()}"
|
||||||
|
|
||||||
|
# Read logging settings from the configuration
|
||||||
|
lfp = config_data["logging"]["logfile_path"] # Log File Path
|
||||||
|
clfp = f"cur_{lfp}" # Current Log File Path
|
||||||
|
|
||||||
|
if not (config_data["logging"]["terminal"]["log_to_terminal"] or
|
||||||
|
config_data["logging"]["file"]["log_to_file"]):
|
||||||
|
print("!!! WARNING !!! CONSOLE AND LOGFILE OUTPUT DISABLED !!!\n"
|
||||||
|
"!!! NO LOGS WILL BE PROVIDED !!!")
|
||||||
|
|
||||||
|
# Check if this log level is enabled (or if it's a FATAL message which always prints)
|
||||||
if level in config_data["logging"]["log_levels"] or level == "FATAL":
|
if level in config_data["logging"]["log_levels"] or level == "FATAL":
|
||||||
elapsed = time.time() - get_bot_start_time()
|
# Terminal output
|
||||||
uptime_str, _ = utility.format_uptime(elapsed)
|
|
||||||
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
log_message = f"[{timestamp} - {uptime_str}] [{level}] {message}"
|
|
||||||
|
|
||||||
# Include traceback for certain error levels
|
|
||||||
if exec_info or level in ["ERROR", "CRITICAL", "FATAL"]:
|
|
||||||
log_message += f"\n{traceback.format_exc()}"
|
|
||||||
|
|
||||||
# Print to terminal if enabled
|
|
||||||
# 'FATAL' errors override settings
|
|
||||||
# Checks config file to see enabled/disabled logging levels
|
|
||||||
if config_data["logging"]["terminal"]["log_to_terminal"] or level == "FATAL":
|
if config_data["logging"]["terminal"]["log_to_terminal"] or level == "FATAL":
|
||||||
config_level_format = f"log_{level.lower()}"
|
config_level_format = f"log_{level.lower()}"
|
||||||
if config_data["logging"]["terminal"][config_level_format] or level == "FATAL":
|
if config_data["logging"]["terminal"].get(config_level_format, False) or level == "FATAL":
|
||||||
print(log_message)
|
print(log_message)
|
||||||
|
|
||||||
# Write to file if enabled
|
# File output
|
||||||
# 'FATAL' errors override settings
|
|
||||||
# Checks config file to see enabled/disabled logging levels
|
|
||||||
if config_data["logging"]["file"]["log_to_file"] or level == "FATAL":
|
if config_data["logging"]["file"]["log_to_file"] or level == "FATAL":
|
||||||
config_level_format = f"log_{level.lower()}"
|
config_level_format = f"log_{level.lower()}"
|
||||||
if config_data["logging"]["file"][config_level_format] or level == "FATAL":
|
if config_data["logging"]["file"].get(config_level_format, False) or level == "FATAL":
|
||||||
try:
|
try:
|
||||||
lfp = config_data["logging"]["logfile_path"]
|
with open(lfp, "a", encoding="utf-8") as logfile:
|
||||||
clfp = f"cur_{lfp}"
|
|
||||||
with open(lfp, "a", encoding="utf-8") as logfile: # Write to permanent logfile
|
|
||||||
logfile.write(f"{log_message}\n")
|
logfile.write(f"{log_message}\n")
|
||||||
logfile.flush() # Ensure it gets written immediately
|
logfile.flush()
|
||||||
with open(clfp, "a", encoding="utf-8") as c_logfile: # Write to this-run logfile
|
with open(clfp, "a", encoding="utf-8") as c_logfile:
|
||||||
c_logfile.write(f"{log_message}\n")
|
c_logfile.write(f"{log_message}\n")
|
||||||
c_logfile.flush() # Ensure it gets written immediately
|
c_logfile.flush()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[WARNING] Failed to write to logfile: {e}")
|
print(f"[WARNING] Failed to write to logfile: {e}")
|
||||||
|
|
||||||
# Handle fatal errors with shutdown
|
# Handle fatal errors with shutdown
|
||||||
if level == "FATAL":
|
if level == "FATAL":
|
||||||
print(f"!!! FATAL ERROR LOGGED, SHUTTING DOWN !!!")
|
print("!!! FATAL ERROR LOGGED, SHUTTING DOWN !!!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def reset_curlogfile():
|
def reset_curlogfile():
|
||||||
"""
|
"""
|
||||||
|
|
557
modules/db.py
557
modules/db.py
|
@ -78,7 +78,7 @@ def init_db_connection(config):
|
||||||
sqlite_path = db_settings.get("sqlite_path", "local_database.sqlite")
|
sqlite_path = db_settings.get("sqlite_path", "local_database.sqlite")
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(sqlite_path)
|
conn = sqlite3.connect(sqlite_path)
|
||||||
globals.log(f"Database connection established using local SQLite: {sqlite_path}")
|
globals.log(f"Database connection established using local SQLite: {sqlite_path}", "DEBUG")
|
||||||
return conn
|
return conn
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
globals.log(f"Could not open local SQLite database '{sqlite_path}': {e}", "WARNING")
|
globals.log(f"Could not open local SQLite database '{sqlite_path}': {e}", "WARNING")
|
||||||
|
@ -257,82 +257,126 @@ def ensure_quotes_table(db_conn):
|
||||||
|
|
||||||
def ensure_users_table(db_conn):
|
def ensure_users_table(db_conn):
|
||||||
"""
|
"""
|
||||||
Checks if 'users' table exists. If not, creates it.
|
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()
|
is_sqlite = "sqlite3" in str(type(db_conn)).lower()
|
||||||
|
|
||||||
# 1) Check existence
|
|
||||||
if is_sqlite:
|
if is_sqlite:
|
||||||
check_sql = """
|
check_sql = """
|
||||||
SELECT name
|
SELECT name
|
||||||
FROM sqlite_master
|
FROM sqlite_master
|
||||||
WHERE type='table'
|
WHERE type='table'
|
||||||
AND name='users'
|
AND name='Users'
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
check_sql = """
|
check_sql = """
|
||||||
SELECT table_name
|
SELECT table_name
|
||||||
FROM information_schema.tables
|
FROM information_schema.tables
|
||||||
WHERE table_name = 'users'
|
WHERE table_name = 'Users'
|
||||||
AND table_schema = DATABASE()
|
AND table_schema = DATABASE()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rows = run_db_operation(db_conn, "read", check_sql)
|
rows = run_db_operation(db_conn, "read", check_sql)
|
||||||
if rows and rows[0] and rows[0][0]:
|
if rows and rows[0] and rows[0][0]:
|
||||||
globals.log("Table 'users' already exists, skipping creation.", "DEBUG")
|
globals.log("Table 'Users' already exists, skipping creation.", "DEBUG")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2) Table does NOT exist => create it
|
globals.log("Table 'Users' does not exist; creating now...", "INFO")
|
||||||
globals.log("Table 'users' does not exist; creating now...")
|
|
||||||
|
|
||||||
if is_sqlite:
|
if is_sqlite:
|
||||||
create_table_sql = """
|
create_sql = """
|
||||||
CREATE TABLE users (
|
CREATE TABLE Users (
|
||||||
UUID TEXT PRIMARY KEY,
|
UUID TEXT PRIMARY KEY,
|
||||||
discord_user_id TEXT,
|
Unified_Username TEXT,
|
||||||
discord_username TEXT,
|
|
||||||
discord_user_display_name TEXT,
|
|
||||||
twitch_user_id TEXT,
|
|
||||||
twitch_username TEXT,
|
|
||||||
twitch_user_display_name TEXT,
|
|
||||||
datetime_linked TEXT,
|
datetime_linked TEXT,
|
||||||
user_is_banned BOOLEAN DEFAULT 0,
|
user_is_banned BOOLEAN DEFAULT 0,
|
||||||
user_is_bot BOOLEAN DEFAULT 0
|
user_is_bot BOOLEAN DEFAULT 0
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
create_table_sql = """
|
create_sql = """
|
||||||
CREATE TABLE users (
|
CREATE TABLE Users (
|
||||||
UUID VARCHAR(36) PRIMARY KEY,
|
UUID VARCHAR(36) PRIMARY KEY,
|
||||||
discord_user_id VARCHAR(100),
|
Unified_Username 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,
|
datetime_linked DATETIME,
|
||||||
user_is_banned BOOLEAN DEFAULT FALSE,
|
user_is_banned BOOLEAN DEFAULT FALSE,
|
||||||
user_is_bot BOOLEAN DEFAULT FALSE
|
user_is_bot BOOLEAN DEFAULT FALSE
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = run_db_operation(db_conn, "write", create_table_sql)
|
result = run_db_operation(db_conn, "write", create_sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
error_msg = "Failed to create 'users' table!"
|
error_msg = "Failed to create 'Users' table!"
|
||||||
globals.log(error_msg, "CRITICAL")
|
globals.log(error_msg, "CRITICAL")
|
||||||
raise RuntimeError(error_msg)
|
raise RuntimeError(error_msg)
|
||||||
|
|
||||||
globals.log("Successfully created table 'users'.")
|
globals.log("Successfully created table 'Users'.", "INFO")
|
||||||
|
|
||||||
|
#######################
|
||||||
|
# Ensure 'platform_mapping' table
|
||||||
|
#######################
|
||||||
|
|
||||||
|
def ensure_platform_mapping_table(db_conn):
|
||||||
|
"""
|
||||||
|
Ensures the 'Platform_Mapping' table exists.
|
||||||
|
This table maps platform-specific user IDs to the universal UUID.
|
||||||
|
"""
|
||||||
|
is_sqlite = "sqlite3" in str(type(db_conn)).lower()
|
||||||
|
|
||||||
|
if is_sqlite:
|
||||||
|
check_sql = """
|
||||||
|
SELECT name
|
||||||
|
FROM sqlite_master
|
||||||
|
WHERE type='table'
|
||||||
|
AND name='Platform_Mapping'
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
check_sql = """
|
||||||
|
SELECT table_name
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_name = 'Platform_Mapping'
|
||||||
|
AND table_schema = DATABASE()
|
||||||
|
"""
|
||||||
|
|
||||||
|
rows = run_db_operation(db_conn, "read", check_sql)
|
||||||
|
if rows and rows[0] and rows[0][0]:
|
||||||
|
globals.log("Table 'Platform_Mapping' already exists, skipping creation.", "DEBUG")
|
||||||
|
return
|
||||||
|
|
||||||
|
globals.log("Table 'Platform_Mapping' does not exist; creating now...", "INFO")
|
||||||
|
|
||||||
|
if is_sqlite:
|
||||||
|
create_sql = """
|
||||||
|
CREATE TABLE Platform_Mapping (
|
||||||
|
Platform_User_ID TEXT,
|
||||||
|
Platform_Type TEXT,
|
||||||
|
UUID TEXT,
|
||||||
|
Display_Name TEXT,
|
||||||
|
Username TEXT,
|
||||||
|
PRIMARY KEY (Platform_User_ID, Platform_Type),
|
||||||
|
FOREIGN KEY (UUID) REFERENCES Users(UUID) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
create_sql = """
|
||||||
|
CREATE TABLE Platform_Mapping (
|
||||||
|
Platform_User_ID VARCHAR(100),
|
||||||
|
Platform_Type ENUM('Discord', 'Twitch'),
|
||||||
|
UUID VARCHAR(36),
|
||||||
|
Display_Name VARCHAR(100),
|
||||||
|
Username VARCHAR(100),
|
||||||
|
PRIMARY KEY (Platform_User_ID, Platform_Type),
|
||||||
|
FOREIGN KEY (UUID) REFERENCES Users(UUID) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = run_db_operation(db_conn, "write", create_sql)
|
||||||
|
if result is None:
|
||||||
|
error_msg = "Failed to create 'Platform_Mapping' table!"
|
||||||
|
globals.log(error_msg, "CRITICAL")
|
||||||
|
raise RuntimeError(error_msg)
|
||||||
|
|
||||||
|
globals.log("Successfully created table 'Platform_Mapping'.", "INFO")
|
||||||
|
|
||||||
|
|
||||||
########################
|
########################
|
||||||
|
@ -341,141 +385,134 @@ def ensure_users_table(db_conn):
|
||||||
|
|
||||||
def lookup_user(db_conn, identifier: str, identifier_type: str, target_identifier: str = None):
|
def lookup_user(db_conn, identifier: str, identifier_type: str, target_identifier: str = None):
|
||||||
"""
|
"""
|
||||||
Looks up a user in the 'users' table based on the given identifier_type.
|
Looks up a user in the 'Users' table using 'Platform_Mapping' for platform-specific IDs or UUID.
|
||||||
|
|
||||||
|
identifier_type can be:
|
||||||
|
- "discord_user_id" or "discord"
|
||||||
|
- "twitch_user_id" or "twitch"
|
||||||
|
- "UUID" (to lookup by UUID directly)
|
||||||
|
|
||||||
The accepted identifier_type values are:
|
|
||||||
- "uuid"
|
|
||||||
- "discord_user_id" or alias "discord"
|
|
||||||
- "discord_username"
|
|
||||||
- "discord_user_display_name"
|
|
||||||
- "twitch_user_id" or alias "twitch"
|
|
||||||
- "twitch_username"
|
|
||||||
- "twitch_user_display_name"
|
|
||||||
|
|
||||||
Optionally, if target_identifier is provided (must be one of the accepted columns),
|
|
||||||
only that column's value will be returned instead of the full user record.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
If target_identifier is None: A dictionary with the following keys:
|
If target_identifier is None: A dictionary with the following keys:
|
||||||
{
|
{
|
||||||
"UUID": str,
|
"UUID": str,
|
||||||
"discord_user_id": str or None,
|
"Unified_Username": str,
|
||||||
"discord_username": str or None,
|
"datetime_linked": str,
|
||||||
"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 as stored in the database),
|
|
||||||
"user_is_banned": bool or int,
|
"user_is_banned": bool or int,
|
||||||
"user_is_bot": bool or int
|
"user_is_bot": bool or int,
|
||||||
|
"platform_user_id": str,
|
||||||
|
"platform_display_name": str,
|
||||||
|
"platform_username": str,
|
||||||
|
"platform_type": str
|
||||||
}
|
}
|
||||||
If target_identifier is provided: The value from the record corresponding to that column.
|
If target_identifier is provided: The value from the record corresponding to that column.
|
||||||
If the lookup fails or the parameters are invalid: None.
|
If the lookup fails or the parameters are invalid: None.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Define the valid columns for lookup and for target extraction.
|
# Debug: Log the inputs
|
||||||
valid_cols = [
|
globals.log(f"lookup_user() called with: identifier='{identifier}', identifier_type='{identifier_type}', target_identifier='{target_identifier}'", "DEBUG")
|
||||||
"uuid", "discord_user_id", "discord_username",
|
|
||||||
"twitch_user_id", "twitch_username", "discord",
|
|
||||||
"twitch", "discord_user_display_name",
|
|
||||||
"twitch_user_display_name"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Ensure the provided identifier_type is acceptable.
|
|
||||||
if identifier_type.lower() not in valid_cols:
|
|
||||||
if globals.log:
|
|
||||||
globals.log(f"lookup_user error: invalid identifier_type '{identifier_type}'", "WARNING")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Convert shorthand identifier types to their full column names.
|
# Define platform type and column mappings
|
||||||
if identifier_type.lower() == "discord":
|
platform_map = {
|
||||||
identifier_type = "discord_user_id"
|
"discord": "Discord",
|
||||||
elif identifier_type.lower() == "twitch":
|
"discord_user_id": "Discord",
|
||||||
identifier_type = "twitch_user_id"
|
"twitch": "Twitch",
|
||||||
|
"twitch_user_id": "Twitch"
|
||||||
|
}
|
||||||
|
|
||||||
# If a target_identifier is provided, validate that too.
|
# Handle UUID case separately
|
||||||
if target_identifier is not None:
|
if identifier_type.upper() == "UUID":
|
||||||
if target_identifier.lower() not in valid_cols:
|
query = """
|
||||||
if globals.log:
|
SELECT
|
||||||
globals.log(f"lookup_user error: invalid target_identifier '{target_identifier}'", "WARNING")
|
u.UUID,
|
||||||
|
u.Unified_Username,
|
||||||
|
u.datetime_linked,
|
||||||
|
u.user_is_banned,
|
||||||
|
u.user_is_bot,
|
||||||
|
pm.Platform_User_ID,
|
||||||
|
pm.Display_Name,
|
||||||
|
pm.Username,
|
||||||
|
pm.Platform_Type
|
||||||
|
FROM Users u
|
||||||
|
LEFT JOIN Platform_Mapping pm ON u.UUID = pm.UUID
|
||||||
|
WHERE u.UUID = ?
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
params = (identifier,)
|
||||||
|
else:
|
||||||
|
# Handle platform-specific lookups
|
||||||
|
if identifier_type.lower() not in platform_map:
|
||||||
|
globals.log(f"lookup_user error: invalid identifier_type '{identifier_type}'", "WARNING")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Build the query using the (now validated) identifier_type.
|
# Get the platform type (Discord or Twitch)
|
||||||
query = f"""
|
platform_type = platform_map[identifier_type.lower()]
|
||||||
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
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Execute the database operation. Adjust run_db_operation() as needed.
|
# Use platform_user_id to lookup the UUID
|
||||||
rows = run_db_operation(db_conn, "read", query, params=(identifier,))
|
query = """
|
||||||
|
SELECT
|
||||||
|
u.UUID,
|
||||||
|
u.Unified_Username,
|
||||||
|
u.datetime_linked,
|
||||||
|
u.user_is_banned,
|
||||||
|
u.user_is_bot,
|
||||||
|
pm.Platform_User_ID,
|
||||||
|
pm.Display_Name,
|
||||||
|
pm.Username,
|
||||||
|
pm.Platform_Type
|
||||||
|
FROM Users u
|
||||||
|
INNER JOIN Platform_Mapping pm ON u.UUID = pm.UUID
|
||||||
|
WHERE pm.Platform_Type = ? AND pm.Platform_User_ID = ?
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
params = (platform_type, identifier)
|
||||||
|
|
||||||
|
# Debug: Log the query and parameters
|
||||||
|
globals.log(f"lookup_user() executing query: {query} with params={params}", "DEBUG")
|
||||||
|
|
||||||
|
# Run the query
|
||||||
|
rows = run_db_operation(db_conn, "read", query, params)
|
||||||
|
|
||||||
|
# Debug: Log the result of the query
|
||||||
|
globals.log(f"lookup_user() query result: {rows}", "DEBUG")
|
||||||
|
|
||||||
|
# Handle no result case
|
||||||
if not rows:
|
if not rows:
|
||||||
if globals.log:
|
globals.log(f"lookup_user: No user found for {identifier_type}='{identifier}'", "WARNING")
|
||||||
globals.log(f"lookup_user: No user found for {identifier_type}='{identifier}'", "DEBUG")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Since we have a single row, convert it to a dictionary.
|
# Convert the row to a dictionary
|
||||||
row = rows[0]
|
row = rows[0]
|
||||||
user_data = {
|
user_data = {
|
||||||
"UUID": row[0],
|
"UUID": row[0], # Make UUID consistently uppercase
|
||||||
"discord_user_id": row[1],
|
"unified_username": row[1],
|
||||||
"discord_username": row[2] if row[2] else f"{row[5]} (Discord unlinked)",
|
"datetime_linked": row[2],
|
||||||
"discord_user_display_name": row[3] if row[3] else f"{row[6]} (Discord unlinked)",
|
"user_is_banned": row[3],
|
||||||
"twitch_user_id": row[4],
|
"user_is_bot": row[4],
|
||||||
"twitch_username": row[5] if row[5] else f"{row[2]} (Twitch unlinked)",
|
"platform_user_id": row[5],
|
||||||
"twitch_user_display_name": row[6] if row[6] else f"{row[3]} (Twitch unlinked)",
|
"platform_display_name": row[6],
|
||||||
"datetime_linked": row[7],
|
"platform_username": row[7],
|
||||||
"user_is_banned": row[8],
|
"platform_type": row[8]
|
||||||
"user_is_bot": row[9],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# If the caller requested a specific target column, return that value.
|
# Debug: Log the constructed user data
|
||||||
|
globals.log(f"lookup_user() constructed user_data: {user_data}", "DEBUG")
|
||||||
|
|
||||||
|
# If target_identifier is provided, return just that value
|
||||||
if target_identifier:
|
if target_identifier:
|
||||||
# Adjust for potential alias: if target_identifier is an alias,
|
target_identifier = target_identifier.upper() # Force uppercase for consistency
|
||||||
# translate it to the actual column name.
|
|
||||||
target_identifier = target_identifier.lower()
|
|
||||||
if target_identifier == "discord":
|
|
||||||
target_identifier = "discord_user_id"
|
|
||||||
elif target_identifier == "twitch":
|
|
||||||
target_identifier = "twitch_user_id"
|
|
||||||
|
|
||||||
# The key for "uuid" is stored as "UUID" in our dict.
|
|
||||||
if target_identifier == "uuid":
|
|
||||||
target_identifier = "UUID"
|
|
||||||
|
|
||||||
# If usernames are Null, default to that of the opposite platform
|
|
||||||
# if not user_data['discord_username']:
|
|
||||||
# user_data['discord_username'] = f"{user_data['twitch_username']} (Discord unlinked)"
|
|
||||||
# elif not user_data['twitch_username']:
|
|
||||||
# user_data['twitch_username'] = f"{user_data['discord_username']} (Twitch unlinked)"
|
|
||||||
|
|
||||||
# if not user_data['discord_user_display_name']:
|
|
||||||
# user_data['discord_user_display_name'] = f"{user_data['twitch_user_display_name']} (Discord unlinked)"
|
|
||||||
# elif not user_data['twitch_user_display_name']:
|
|
||||||
# user_data['twitch_user_display_name'] = f"{user_data['discord_user_display_name']} (Twitch unlinked)"
|
|
||||||
|
|
||||||
if target_identifier in user_data:
|
if target_identifier in user_data:
|
||||||
|
globals.log(f"lookup_user() returning target_identifier='{target_identifier}' with value='{user_data[target_identifier]}'", "DEBUG")
|
||||||
return user_data[target_identifier]
|
return user_data[target_identifier]
|
||||||
else:
|
else:
|
||||||
if globals.log:
|
globals.log(f"lookup_user error: target_identifier '{target_identifier}' not found in user_data. Available keys: {list(user_data.keys())}", "WARNING")
|
||||||
globals.log(f"lookup_user error: target_identifier '{target_identifier}' not present in user data", "WARNING")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Otherwise, return the full user record.
|
globals.log(f"lookup_user() returning full user_data: {user_data}", "DEBUG")
|
||||||
return user_data
|
return user_data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_chatlog_table(db_conn):
|
def ensure_chatlog_table(db_conn):
|
||||||
"""
|
"""
|
||||||
Checks if 'chat_log' table exists. If not, creates it.
|
Checks if 'chat_log' table exists. If not, creates it.
|
||||||
|
@ -555,17 +592,17 @@ def ensure_chatlog_table(db_conn):
|
||||||
globals.log("Successfully created table 'chat_log'.", "INFO")
|
globals.log("Successfully created table 'chat_log'.", "INFO")
|
||||||
|
|
||||||
|
|
||||||
def log_message(db_conn, user_uuid, message_content, platform, channel, attachments=None, username: str = "Unknown"):
|
def log_message(db_conn, identifier, identifier_type, message_content, platform, channel, attachments=None):
|
||||||
"""
|
"""
|
||||||
Inserts a row into 'chat_log' with the given fields.
|
Logs a message in 'chat_log' with UUID fetched using the new Platform_Mapping structure.
|
||||||
user_uuid: The user's UUID from the 'users' table (string).
|
"""
|
||||||
message_content: The text of the message.
|
# Get UUID using the updated lookup_user
|
||||||
platform: 'twitch' or discord server name, etc.
|
user_data = lookup_user(db_conn, identifier, identifier_type)
|
||||||
channel: The channel name (Twitch channel, or Discord channel).
|
if not user_data:
|
||||||
attachments: Optional string of hyperlinks if available.
|
globals.log(f"User not found for {identifier_type}='{identifier}'", "WARNING")
|
||||||
|
return
|
||||||
|
|
||||||
DATETIME will default to current timestamp in the DB.
|
user_uuid = user_data["UUID"]
|
||||||
"""
|
|
||||||
|
|
||||||
if attachments is None or not "https://" in attachments:
|
if attachments is None or not "https://" in attachments:
|
||||||
attachments = ""
|
attachments = ""
|
||||||
|
@ -577,14 +614,13 @@ def log_message(db_conn, user_uuid, message_content, platform, channel, attachme
|
||||||
PLATFORM,
|
PLATFORM,
|
||||||
CHANNEL,
|
CHANNEL,
|
||||||
ATTACHMENTS
|
ATTACHMENTS
|
||||||
)
|
) VALUES (?, ?, ?, ?, ?)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
|
||||||
"""
|
"""
|
||||||
params = (user_uuid, message_content, platform, channel, attachments)
|
params = (user_uuid, message_content, platform, channel, attachments)
|
||||||
rowcount = run_db_operation(db_conn, "write", insert_sql, params)
|
rowcount = run_db_operation(db_conn, "write", insert_sql, params)
|
||||||
|
|
||||||
if rowcount and rowcount > 0:
|
if rowcount and rowcount > 0:
|
||||||
globals.log(f"Logged message for UUID={user_uuid} ({username}) in 'chat_log'.", "DEBUG")
|
globals.log(f"Logged message for UUID={user_uuid}.", "DEBUG")
|
||||||
else:
|
else:
|
||||||
globals.log("Failed to log message in 'chat_log'.", "ERROR")
|
globals.log("Failed to log message in 'chat_log'.", "ERROR")
|
||||||
|
|
||||||
|
@ -791,36 +827,29 @@ def ensure_discord_activity_table(db_conn):
|
||||||
globals.log("Successfully created table 'discord_activity'.", "INFO")
|
globals.log("Successfully created table 'discord_activity'.", "INFO")
|
||||||
|
|
||||||
|
|
||||||
def log_discord_activity(db_conn, guild_id, user_uuid, action, voice_channel, action_detail=None):
|
def log_discord_activity(db_conn, guild_id, user_identifier, action, voice_channel, action_detail=None):
|
||||||
"""
|
"""
|
||||||
Logs Discord activities (playing games, listening to Spotify, streaming).
|
Logs Discord activities with duplicate detection to prevent redundant logs.
|
||||||
|
|
||||||
Duplicate detection:
|
|
||||||
- Fetch the last NUM_RECENT_ENTRIES events for this user & action.
|
|
||||||
- Normalize the ACTION_DETAIL values.
|
|
||||||
- If the most recent event(s) all match the new event's detail (i.e. no intervening non-matching event)
|
|
||||||
and the latest matching event was logged less than DUPLICATE_THRESHOLD ago, skip logging.
|
|
||||||
- This allows a "reset": if the user changes state (e.g. changes song or channel) and then reverts,
|
|
||||||
the new event is logged.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Resolve UUID using the new Platform_Mapping
|
||||||
|
user_data = lookup_user(db_conn, user_identifier, identifier_type="discord_user_id")
|
||||||
|
if not user_data:
|
||||||
|
globals.log(f"User not found for Discord ID: {user_identifier}", "WARNING")
|
||||||
|
return
|
||||||
|
|
||||||
|
user_uuid = user_data["UUID"]
|
||||||
|
|
||||||
|
# Prepare the voice_channel value (if it’s an object with a name, use that).
|
||||||
|
channel_val = voice_channel.name if (voice_channel and hasattr(voice_channel, "name")) else voice_channel
|
||||||
|
|
||||||
|
# Duplicate Detection Logic
|
||||||
def normalize_detail(detail):
|
def normalize_detail(detail):
|
||||||
"""Return a normalized version of the detail for comparison (or None if detail is None)."""
|
"""Normalize detail for comparison (lowercase, stripped of whitespace)."""
|
||||||
return detail.strip().lower() if detail else None
|
return detail.strip().lower() if detail else None
|
||||||
|
|
||||||
# How long to consider an event “fresh” enough to be considered a duplicate.
|
|
||||||
DUPLICATE_THRESHOLD = datetime.timedelta(minutes=5)
|
DUPLICATE_THRESHOLD = datetime.timedelta(minutes=5)
|
||||||
# How many recent events to check.
|
|
||||||
NUM_RECENT_ENTRIES = 5
|
NUM_RECENT_ENTRIES = 5
|
||||||
|
|
||||||
# Verify that the user exists in 'users' before proceeding.
|
|
||||||
user_check = run_db_operation(
|
|
||||||
db_conn, "read", "SELECT UUID FROM users WHERE UUID = ?", (user_uuid,)
|
|
||||||
)
|
|
||||||
if not user_check:
|
|
||||||
globals.log(f"WARNING: Attempted to log activity for non-existent UUID: {user_uuid}", "WARNING")
|
|
||||||
return # Prevent foreign key issues.
|
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
normalized_new = normalize_detail(action_detail)
|
normalized_new = normalize_detail(action_detail)
|
||||||
|
|
||||||
|
@ -832,15 +861,9 @@ def log_discord_activity(db_conn, guild_id, user_uuid, action, voice_channel, ac
|
||||||
ORDER BY DATETIME DESC
|
ORDER BY DATETIME DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
"""
|
"""
|
||||||
rows = run_db_operation(
|
rows = run_db_operation(db_conn, "read", query, params=(user_uuid, action, NUM_RECENT_ENTRIES))
|
||||||
db_conn, "read", query, params=(user_uuid, action, NUM_RECENT_ENTRIES)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Determine the timestamp of the most recent event that matches the new detail,
|
|
||||||
# and the most recent event that is different.
|
|
||||||
last_same = None # Timestamp of the most recent event matching normalized_new.
|
|
||||||
last_different = None # Timestamp of the most recent event with a different detail.
|
|
||||||
|
|
||||||
|
last_same, last_different = None, None
|
||||||
for row in rows:
|
for row in rows:
|
||||||
dt_str, detail = row
|
dt_str, detail = row
|
||||||
try:
|
try:
|
||||||
|
@ -850,41 +873,37 @@ def log_discord_activity(db_conn, guild_id, user_uuid, action, voice_channel, ac
|
||||||
continue
|
continue
|
||||||
normalized_existing = normalize_detail(detail)
|
normalized_existing = normalize_detail(detail)
|
||||||
if normalized_existing == normalized_new:
|
if normalized_existing == normalized_new:
|
||||||
# Record the most recent matching event.
|
|
||||||
if last_same is None or dt > last_same:
|
if last_same is None or dt > last_same:
|
||||||
last_same = dt
|
last_same = dt
|
||||||
else:
|
else:
|
||||||
# Record the most recent non-matching event.
|
|
||||||
if last_different is None or dt > last_different:
|
if last_different is None or dt > last_different:
|
||||||
last_different = dt
|
last_different = dt
|
||||||
|
|
||||||
# Decide whether to skip logging:
|
# Check duplicate conditions
|
||||||
# If there is a matching (same-detail) event, and either no different event exists OR the matching event
|
|
||||||
# is more recent than the last different event (i.e. the user's current state is still the same),
|
|
||||||
# then if that event is within the DUPLICATE_THRESHOLD, skip logging.
|
|
||||||
if last_same is not None:
|
if last_same is not None:
|
||||||
if (last_different is None) or (last_same > last_different):
|
if (last_different is None) or (last_same > last_different):
|
||||||
if now - last_same > DUPLICATE_THRESHOLD:
|
if now - last_same < DUPLICATE_THRESHOLD:
|
||||||
#log_func(f"Duplicate {action} event for user {user_uuid} (detail '{action_detail}') within threshold; skipping log.","DEBUG")
|
globals.log(f"Duplicate {action} event for {user_uuid} within threshold; skipping log.", "DEBUG")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Prepare the voice_channel value (if it’s an object with a name, use that).
|
# Insert the new event
|
||||||
channel_val = voice_channel.name if (voice_channel and hasattr(voice_channel, "name")) else voice_channel
|
insert_sql = """
|
||||||
|
|
||||||
# Insert the new event.
|
|
||||||
sql = """
|
|
||||||
INSERT INTO discord_activity (UUID, ACTION, GUILD_ID, VOICE_CHANNEL, ACTION_DETAIL)
|
INSERT INTO discord_activity (UUID, ACTION, GUILD_ID, VOICE_CHANNEL, ACTION_DETAIL)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
""" if "sqlite3" in str(type(db_conn)).lower() else """
|
||||||
|
INSERT INTO discord_activity (UUID, ACTION, GUILD_ID, VOICE_CHANNEL, ACTION_DETAIL)
|
||||||
|
VALUES (%s, %s, %s, %s, %s)
|
||||||
"""
|
"""
|
||||||
params = (user_uuid, action, guild_id, channel_val, action_detail)
|
params = (user_uuid, action, guild_id, channel_val, action_detail)
|
||||||
rowcount = run_db_operation(db_conn, "write", sql, params)
|
rowcount = run_db_operation(db_conn, "write", insert_sql, params)
|
||||||
|
|
||||||
if rowcount and rowcount > 0:
|
if rowcount and rowcount > 0:
|
||||||
detail_str = f" ({action_detail})" if action_detail else ""
|
detail_str = f" ({action_detail})" if action_detail else ""
|
||||||
globals.log(f"Logged Discord activity in Guild {guild_id}: {action}{detail_str}", "DEBUG")
|
globals.log(f"Logged Discord activity for UUID={user_uuid} in Guild {guild_id}: {action}{detail_str}", "DEBUG")
|
||||||
else:
|
else:
|
||||||
globals.log("Failed to log Discord activity.", "ERROR")
|
globals.log("Failed to log Discord activity.", "ERROR")
|
||||||
|
|
||||||
|
|
||||||
def ensure_bot_events_table(db_conn):
|
def ensure_bot_events_table(db_conn):
|
||||||
"""
|
"""
|
||||||
Ensures the 'bot_events' table exists, which logs major bot-related events.
|
Ensures the 'bot_events' table exists, which logs major bot-related events.
|
||||||
|
@ -1033,35 +1052,36 @@ def ensure_link_codes_table(db_conn):
|
||||||
|
|
||||||
def merge_uuid_data(db_conn, old_uuid, new_uuid):
|
def merge_uuid_data(db_conn, old_uuid, new_uuid):
|
||||||
"""
|
"""
|
||||||
Merges all records from the old UUID (Twitch account) into the new UUID (Discord account).
|
Merges data from old UUID to new UUID, updating references in Platform_Mapping.
|
||||||
This replaces all instances of the old UUID in all relevant tables with the new UUID,
|
|
||||||
ensuring that no data is lost in the linking process.
|
|
||||||
|
|
||||||
After merging, the old UUID entry is removed from the `users` table.
|
|
||||||
"""
|
"""
|
||||||
globals.log(f"Starting UUID merge: {old_uuid} -> {new_uuid}", "INFO")
|
globals.log(f"Merging UUID data: {old_uuid} -> {new_uuid}", "INFO")
|
||||||
|
|
||||||
|
# Update references in Platform_Mapping
|
||||||
|
update_mapping_sql = """
|
||||||
|
UPDATE Platform_Mapping SET UUID = ? WHERE UUID = ?
|
||||||
|
"""
|
||||||
|
run_db_operation(db_conn, "update", update_mapping_sql, (new_uuid, old_uuid))
|
||||||
|
|
||||||
tables_to_update = [
|
tables_to_update = [
|
||||||
"voice_activity_log",
|
|
||||||
"bot_events",
|
|
||||||
"chat_log",
|
"chat_log",
|
||||||
"user_howls",
|
"user_howls",
|
||||||
"quotes"
|
"discord_activity",
|
||||||
|
"community_events"
|
||||||
]
|
]
|
||||||
|
|
||||||
for table in tables_to_update:
|
for table in tables_to_update:
|
||||||
sql = f"UPDATE {table} SET UUID = ? WHERE UUID = ?"
|
sql = f"UPDATE {table} SET UUID = ? WHERE UUID = ?"
|
||||||
rowcount = run_db_operation(db_conn, "update", sql, (new_uuid, old_uuid))
|
rowcount = run_db_operation(db_conn, "update", sql, (new_uuid, old_uuid))
|
||||||
globals.log(f"Updated {rowcount} rows in {table} (transferring {old_uuid} -> {new_uuid})", "DEBUG")
|
globals.log(f"Updated {rowcount} rows in {table} (transferred {old_uuid} -> {new_uuid})", "DEBUG")
|
||||||
|
|
||||||
# Finally, delete the old UUID from the `users` table
|
# Finally, delete the old UUID from Users table
|
||||||
delete_sql = "DELETE FROM users WHERE UUID = ?"
|
delete_sql = "DELETE FROM Users WHERE UUID = ?"
|
||||||
rowcount = run_db_operation(db_conn, "write", delete_sql, (old_uuid,))
|
rowcount = run_db_operation(db_conn, "write", delete_sql, (old_uuid,))
|
||||||
|
globals.log(f"Deleted old UUID {old_uuid} from 'Users' table ({rowcount} rows affected)", "INFO")
|
||||||
globals.log(f"Deleted old UUID {old_uuid} from 'users' table ({rowcount} rows affected)", "INFO")
|
|
||||||
|
|
||||||
globals.log(f"UUID merge complete: {old_uuid} -> {new_uuid}", "INFO")
|
globals.log(f"UUID merge complete: {old_uuid} -> {new_uuid}", "INFO")
|
||||||
|
|
||||||
|
|
||||||
def ensure_community_events_table(db_conn):
|
def ensure_community_events_table(db_conn):
|
||||||
"""
|
"""
|
||||||
Checks if 'community_events' table exists. If not, attempts to create it.
|
Checks if 'community_events' table exists. If not, attempts to create it.
|
||||||
|
@ -1126,21 +1146,9 @@ def ensure_community_events_table(db_conn):
|
||||||
|
|
||||||
async def handle_community_event(db_conn, is_discord, ctx, args):
|
async def handle_community_event(db_conn, is_discord, ctx, args):
|
||||||
"""
|
"""
|
||||||
Handles community event commands.
|
Handles community event commands including add, info, list, and search.
|
||||||
|
|
||||||
Accepted subcommands (args[0] if provided):
|
|
||||||
- add <event_type> [event_details [|| event_extras]]
|
|
||||||
-> Logs a new event.
|
|
||||||
- info <event_id>
|
|
||||||
-> Retrieves detailed information for a given event.
|
|
||||||
- list [limit]
|
|
||||||
-> Lists recent events (default limit 5 if not specified).
|
|
||||||
- search <keyword...>
|
|
||||||
-> Searches events (by EVENT_TYPE or EVENT_DETAILS).
|
|
||||||
|
|
||||||
If no arguments are provided, defaults to listing recent events.
|
|
||||||
"""
|
"""
|
||||||
from modules import db # Assumes your db module is available
|
|
||||||
if len(args) == 0:
|
if len(args) == 0:
|
||||||
args = ["list"]
|
args = ["list"]
|
||||||
|
|
||||||
|
@ -1149,12 +1157,12 @@ async def handle_community_event(db_conn, is_discord, ctx, args):
|
||||||
if sub == "add":
|
if sub == "add":
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
return "Please provide the event type after 'add'."
|
return "Please provide the event type after 'add'."
|
||||||
|
|
||||||
event_type = args[1]
|
event_type = args[1]
|
||||||
# Concatenate remaining args as event details (if any)
|
|
||||||
event_details = " ".join(args[2:]).strip() if len(args) > 2 else None
|
event_details = " ".join(args[2:]).strip() if len(args) > 2 else None
|
||||||
|
|
||||||
# Optional: If you want to support extras, you might separate event_details and extras using "||"
|
|
||||||
event_extras = None
|
event_extras = None
|
||||||
|
|
||||||
|
# Support extras using "||" separator
|
||||||
if event_details and "||" in event_details:
|
if event_details and "||" in event_details:
|
||||||
parts = event_details.split("||", 1)
|
parts = event_details.split("||", 1)
|
||||||
event_details = parts[0].strip()
|
event_details = parts[0].strip()
|
||||||
|
@ -1162,28 +1170,28 @@ async def handle_community_event(db_conn, is_discord, ctx, args):
|
||||||
|
|
||||||
platform = "Discord" if is_discord else "Twitch"
|
platform = "Discord" if is_discord else "Twitch"
|
||||||
user_id = str(ctx.author.id)
|
user_id = str(ctx.author.id)
|
||||||
# Lookup user data to get UUID (similar to your quote logic)
|
|
||||||
user_data = db.lookup_user(db_conn, identifier=user_id, identifier_type=f"{platform.lower()}_user_id")
|
# Get UUID using lookup_user()
|
||||||
|
user_data = lookup_user(db_conn, identifier=user_id, identifier_type=f"{platform.lower()}_user_id")
|
||||||
if not user_data:
|
if not user_data:
|
||||||
globals.log(f"Could not find UUID for user {ctx.author.name} ({user_id}) on {platform}.", "ERROR")
|
globals.log(f"User not found: {ctx.author.name} ({user_id}) on {platform}", "ERROR")
|
||||||
return "Could not log event: user data missing."
|
return "Could not log event: user data missing."
|
||||||
|
|
||||||
user_uuid = user_data["UUID"]
|
user_uuid = user_data["UUID"]
|
||||||
|
|
||||||
# Insert new event. Use appropriate parameter placeholders.
|
# Insert new event. Adjust for SQLite or MariaDB.
|
||||||
if "sqlite3" in str(type(db_conn)).lower():
|
insert_sql = """
|
||||||
insert_sql = """
|
INSERT INTO community_events
|
||||||
INSERT INTO community_events
|
(EVENT_PLATFORM, EVENT_TYPE, EVENT_DETAILS, EVENT_USER, DATETIME, EVENT_EXTRAS)
|
||||||
(EVENT_PLATFORM, EVENT_TYPE, EVENT_DETAILS, EVENT_USER, DATETIME, EVENT_EXTRAS)
|
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, ?)
|
||||||
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, ?)
|
""" if "sqlite3" in str(type(db_conn)).lower() else """
|
||||||
"""
|
INSERT INTO community_events
|
||||||
else:
|
(EVENT_PLATFORM, EVENT_TYPE, EVENT_DETAILS, EVENT_USER, DATETIME, EVENT_EXTRAS)
|
||||||
insert_sql = """
|
VALUES (%s, %s, %s, %s, CURRENT_TIMESTAMP, %s)
|
||||||
INSERT INTO community_events
|
"""
|
||||||
(EVENT_PLATFORM, EVENT_TYPE, EVENT_DETAILS, EVENT_USER, DATETIME, EVENT_EXTRAS)
|
|
||||||
VALUES (%s, %s, %s, %s, CURRENT_TIMESTAMP, %s)
|
|
||||||
"""
|
|
||||||
params = (platform, event_type, event_details, user_uuid, event_extras)
|
params = (platform, event_type, event_details, user_uuid, event_extras)
|
||||||
result = db.run_db_operation(db_conn, "write", insert_sql, params)
|
result = run_db_operation(db_conn, "write", insert_sql, params)
|
||||||
|
|
||||||
if result is not None:
|
if result is not None:
|
||||||
globals.log(f"New event added: {event_type} by {ctx.author.name}", "DEBUG")
|
globals.log(f"New event added: {event_type} by {ctx.author.name}", "DEBUG")
|
||||||
return f"Successfully logged event: {event_type}"
|
return f"Successfully logged event: {event_type}"
|
||||||
|
@ -1197,13 +1205,11 @@ async def handle_community_event(db_conn, is_discord, ctx, args):
|
||||||
select_sql = "SELECT * FROM community_events WHERE EVENT_ID = ?"
|
select_sql = "SELECT * FROM community_events WHERE EVENT_ID = ?"
|
||||||
if "sqlite3" not in str(type(db_conn)).lower():
|
if "sqlite3" not in str(type(db_conn)).lower():
|
||||||
select_sql = "SELECT * FROM community_events WHERE EVENT_ID = %s"
|
select_sql = "SELECT * FROM community_events WHERE EVENT_ID = %s"
|
||||||
rows = db.run_db_operation(db_conn, "read", select_sql, (event_id,))
|
rows = run_db_operation(db_conn, "read", select_sql, (event_id,))
|
||||||
if not rows:
|
if not rows:
|
||||||
return f"No event found with ID {event_id}."
|
return f"No event found with ID {event_id}."
|
||||||
row = rows[0]
|
row = rows[0]
|
||||||
# row indices: 0: EVENT_ID, 1: EVENT_PLATFORM, 2: EVENT_TYPE, 3: EVENT_DETAILS,
|
return (
|
||||||
# 4: EVENT_USER, 5: DATETIME, 6: EVENT_EXTRAS
|
|
||||||
resp = (
|
|
||||||
f"Event #{row[0]}:\n"
|
f"Event #{row[0]}:\n"
|
||||||
f"Platform: {row[1]}\n"
|
f"Platform: {row[1]}\n"
|
||||||
f"Type: {row[2]}\n"
|
f"Type: {row[2]}\n"
|
||||||
|
@ -1212,47 +1218,6 @@ async def handle_community_event(db_conn, is_discord, ctx, args):
|
||||||
f"Datetime: {row[5]}\n"
|
f"Datetime: {row[5]}\n"
|
||||||
f"Extras: {row[6] or 'N/A'}"
|
f"Extras: {row[6] or 'N/A'}"
|
||||||
)
|
)
|
||||||
return resp
|
|
||||||
|
|
||||||
elif sub == "list":
|
|
||||||
# Optional limit argument (default 5)
|
|
||||||
limit = 5
|
|
||||||
if len(args) >= 2 and args[1].isdigit():
|
|
||||||
limit = int(args[1])
|
|
||||||
select_sql = f"SELECT * FROM community_events ORDER BY DATETIME DESC LIMIT {limit}"
|
|
||||||
rows = db.run_db_operation(db_conn, "read", select_sql)
|
|
||||||
if not rows:
|
|
||||||
return "No events logged yet."
|
|
||||||
resp_lines = []
|
|
||||||
for row in rows:
|
|
||||||
# Display basic info: ID, Type, Platform, Datetime
|
|
||||||
resp_lines.append(f"#{row[0]}: {row[2]} on {row[1]} at {row[5]}")
|
|
||||||
return "\n".join(resp_lines)
|
|
||||||
|
|
||||||
elif sub == "search":
|
|
||||||
if len(args) < 2:
|
|
||||||
return "Please provide keywords to search for."
|
|
||||||
keywords = " ".join(args[1:])
|
|
||||||
like_pattern = f"%{keywords}%"
|
|
||||||
search_sql = """
|
|
||||||
SELECT * FROM community_events
|
|
||||||
WHERE EVENT_TYPE LIKE ? OR EVENT_DETAILS LIKE ?
|
|
||||||
ORDER BY DATETIME DESC LIMIT 5
|
|
||||||
"""
|
|
||||||
if "sqlite3" not in str(type(db_conn)).lower():
|
|
||||||
search_sql = """
|
|
||||||
SELECT * FROM community_events
|
|
||||||
WHERE EVENT_TYPE LIKE %s OR EVENT_DETAILS LIKE %s
|
|
||||||
ORDER BY DATETIME DESC LIMIT 5
|
|
||||||
"""
|
|
||||||
rows = db.run_db_operation(db_conn, "read", search_sql, (like_pattern, like_pattern))
|
|
||||||
if not rows:
|
|
||||||
return "No matching events found."
|
|
||||||
resp_lines = []
|
|
||||||
for row in rows:
|
|
||||||
resp_lines.append(f"#{row[0]}: {row[2]} on {row[1]} at {row[5]}")
|
|
||||||
return "\n".join(resp_lines)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Unknown subcommand; default to listing recent events.
|
return await handle_community_event(db_conn, is_discord, ctx, ["list"])
|
||||||
return await handle_community_event_command(db_conn, is_discord, ctx, ["list"])
|
|
||||||
|
|
|
@ -723,14 +723,14 @@ def track_user_activity(
|
||||||
globals.log(f"... Returned {user_data}", "DEBUG")
|
globals.log(f"... Returned {user_data}", "DEBUG")
|
||||||
|
|
||||||
if platform.lower() == "discord":
|
if platform.lower() == "discord":
|
||||||
if user_data["discord_username"] != username:
|
if user_data["platform_username"] != username:
|
||||||
need_update = True
|
need_update = True
|
||||||
column_updates.append("discord_username = ?")
|
column_updates.append("platform_username = ?")
|
||||||
params.append(username)
|
params.append(username)
|
||||||
|
|
||||||
if user_data["discord_user_display_name"] != display_name:
|
if user_data["platform_display_name"] != display_name:
|
||||||
need_update = True
|
need_update = True
|
||||||
column_updates.append("discord_user_display_name = ?")
|
column_updates.append("platform_display_name = ?")
|
||||||
params.append(display_name)
|
params.append(display_name)
|
||||||
|
|
||||||
if user_data["user_is_bot"] != user_is_bot:
|
if user_data["user_is_bot"] != user_is_bot:
|
||||||
|
@ -752,14 +752,14 @@ def track_user_activity(
|
||||||
globals.log(f"Updated Discord user '{username}' (display '{display_name}') in 'users'.", "DEBUG")
|
globals.log(f"Updated Discord user '{username}' (display '{display_name}') in 'users'.", "DEBUG")
|
||||||
|
|
||||||
elif platform.lower() == "twitch":
|
elif platform.lower() == "twitch":
|
||||||
if user_data["twitch_username"] != username:
|
if user_data["platform_username"] != username:
|
||||||
need_update = True
|
need_update = True
|
||||||
column_updates.append("twitch_username = ?")
|
column_updates.append("platform_username = ?")
|
||||||
params.append(username)
|
params.append(username)
|
||||||
|
|
||||||
if user_data["twitch_user_display_name"] != display_name:
|
if user_data["platform_display_name"] != display_name:
|
||||||
need_update = True
|
need_update = True
|
||||||
column_updates.append("twitch_user_display_name = ?")
|
column_updates.append("platform_display_name = ?")
|
||||||
params.append(display_name)
|
params.append(display_name)
|
||||||
|
|
||||||
if user_data["user_is_bot"] != user_is_bot:
|
if user_data["user_is_bot"] != user_is_bot:
|
||||||
|
@ -789,8 +789,8 @@ def track_user_activity(
|
||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
UUID,
|
UUID,
|
||||||
discord_user_id,
|
discord_user_id,
|
||||||
discord_username,
|
platform_username,
|
||||||
discord_user_display_name,
|
platform_display_name,
|
||||||
user_is_bot
|
user_is_bot
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
@ -801,8 +801,8 @@ def track_user_activity(
|
||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
UUID,
|
UUID,
|
||||||
twitch_user_id,
|
twitch_user_id,
|
||||||
twitch_username,
|
platform_username,
|
||||||
twitch_user_display_name,
|
platform_display_name,
|
||||||
user_is_bot
|
user_is_bot
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
|
Loading…
Reference in New Issue