Fixed Twitch bug

- Fixed an issue where Twitch users were not registered correctly due to recent DB changes
- Log-level of unidentified users changed from warning to info
- Minor changes to `!customvc`
  - Renamed `bitrate` -> `audio_bitrate`
  - Added `video_bitrate`
  - Changes some help text
  - Fixed certain subcommands which were broken after recent changes
  - Added `autoname` -> Automatically names the voice chat according to game being played by the owner
    - *Needs addiitonal work to implement all features*
kami_dev
Kami 2025-03-07 02:16:47 +01:00
parent cce0f21ab0
commit d1faf7f214
3 changed files with 196 additions and 158 deletions

View File

@ -1,4 +1,17 @@
# cmd_discord/customvc.py
#
# TODO
# - Fix "allow" and "deny" subcommands not working (modifications not applied)
# - Fix "lock" subcommand not working (modifications not applied)
# - Add "video_bitrate" to subcommands list
# - Rename "bitrate" to "audio_bitrate"
# - Add automatic channel naming
# - Dynamic mode: displays the game currently (at any time) played by the owner, respecting conservative ratelimits
# - Static mode: names the channel according to pre-defined rules (used on creation if owner is not playing)
# - Add "autoname" to subcommands list
# - Sets the channel name to Dynamic Mode (see above)
# - Modify "settings" to display new information
import discord
from discord.ext import commands
@ -47,6 +60,8 @@ class CustomVCCog(commands.Cog):
"Subcommands:\n"
"- **name** <new_name> - rename\n"
" - `!customvc name my_custom_vc`\n"
"- **autoname** - enables automatic renaming based on game presence\n"
" - `!customvc autoname`\n"
"- **claim** - claim ownership\n"
" - `!customvc claim`\n"
"- **lock** - lock the channel\n"
@ -59,17 +74,23 @@ class CustomVCCog(commands.Cog):
" - `!customvc unlock`\n"
"- **users** <count> - set user limit\n"
" - `!customvc users 2`\n"
"- **bitrate** <kbps> - set channel bitrate\n"
" - `!customvc bitrate some_bitrate`\n"
"- **status** <status_str> - set a custom status **(TODO)**\n"
"- **audio_bitrate** <kbps> - set audio bitrate\n"
" - `!customvc audio_bitrate 64`\n"
"- **video_bitrate** <kbps> - set video bitrate\n"
" - `!customvc video_bitrate 2500`\n"
"- ~~**status** <status_str> - set a custom status~~\n"
"- **op** <user> - co-owner\n"
" - `!customvc op some_user`\n"
"- **settings** - show config\n"
"- **settings** - show channel settings\n"
" - `!customvc settings`\n"
)
await ctx.reply(msg)
else:
await self.bot.invoke(ctx) # This will ensure subcommands get processed.
try:
await self.bot.invoke(ctx) # This will ensure subcommands get processed.
globals.log(f"{ctx.author.name} executed Custom VC subcommand '{ctx.invoked_subcommand}' in {ctx.channel.name}", "DEBUG")
except Exception as e:
globals.log(f"'customvc {ctx.invoked_subcommand}' failed to execute: {e}", "ERROR")
# Subcommands:
@ -109,10 +130,14 @@ class CustomVCCog(commands.Cog):
await self.show_settings_logic(ctx)
@customvc.command(name="users")
async def set_users_limit(self, ctx):
async def set_users_limit(self, ctx, *, limit: int):
"""Assign a VC users limit"""
await self.set_user_limit_logic(ctx)
await self.set_user_limit_logic(ctx, limit)
@customvc.command(name="op")
async def op_user(self, ctx, *, user: str):
"""Make another user co-owner"""
await self.op_user_logic(ctx, user)
#
# Main voice update logic
@ -166,10 +191,12 @@ class CustomVCCog(commands.Cog):
# Announce in the new VC's ephemeral chat
if hasattr(new_vc, "send"):
await new_vc.send(
f"{member.mention}, your custom voice channel is ready! "
f"{member.name}, your custom voice channel is ready! "
"Type `!customvc` here for help with subcommands."
)
await self.update_channel_name(member, after.channel)
# 3) If user left a custom VC -> maybe its now empty
if before.channel and before.channel.id in self.CUSTOM_VC_INFO:
old_vc = before.channel
@ -304,9 +331,25 @@ class CustomVCCog(commands.Cog):
return await ctx.send("You are not in a custom voice channel.")
if not self.is_initiator_or_mod(ctx, vc.id):
return await ctx.send("No permission to rename.")
await vc.edit(name=new_name)
await vc.edit(name=new_name, reason=f'{ctx.author.name} renamed the channel.')
await ctx.send(f"Renamed channel to **{new_name}**.")
@customvc.command(name="autoname")
async def enable_autoname(self, ctx: commands.Context):
"""Enables automatic channel naming based on the owner's game."""
vc = self.get_custom_vc_for(ctx)
if not vc:
return await ctx.send("Not in a custom VC.")
if not self.is_initiator_or_mod(ctx, vc.id):
return await ctx.send("No permission to enable auto-naming.")
info = self.CUSTOM_VC_INFO[vc.id]
info["autoname"] = True
await self.update_channel_name(ctx.author, vc)
await ctx.send("Auto-naming enabled! Your channel will update based on the game you're playing.")
async def claim_channel_logic(self, ctx: commands.Context):
vc = self.get_custom_vc_for(ctx)
if not vc:
@ -316,9 +359,14 @@ class CustomVCCog(commands.Cog):
return await ctx.send("No memory for this channel?")
old = info["owner_id"]
# if author is owner
if old == ctx.author:
return await ctx.send("You're already the owner of this Custom VC!")
# if old owner is still inside
if any(m.id == old for m in vc.members):
return await ctx.send("The original owner is still here; you cannot claim.")
return await ctx.send(f"The current owner, {old.name}, is still here!.")
info["owner_id"] = ctx.author.id
await ctx.send("You are now the owner of this channel!")
@ -331,10 +379,11 @@ class CustomVCCog(commands.Cog):
info = self.CUSTOM_VC_INFO[vc.id]
info["locked"] = True
overwrites = vc.overwrites or {}
overwrites[ctx.guild.default_role] = discord.PermissionOverwrite(connect=False)
await vc.edit(overwrites=overwrites)
await ctx.send("Channel locked.")
# Use set_permissions() instead of modifying overwrites directly
await vc.set_permissions(ctx.guild.default_role, connect=False, reason=f"{ctx.author.name} locked the Custom VC")
await ctx.send("Channel locked. Only allowed users can join.")
async def allow_user_logic(self, ctx: commands.Context, user: str):
vc = self.get_custom_vc_for(ctx)
@ -346,11 +395,13 @@ class CustomVCCog(commands.Cog):
mem = await self.resolve_member(ctx, user)
if not mem:
return await ctx.send(f"Could not find user: {user}.")
info = self.CUSTOM_VC_INFO[vc.id]
info["allowed_ids"].add(mem.id)
overwrites = vc.overwrites or {}
overwrites[mem] = discord.PermissionOverwrite(connect=True)
await vc.edit(overwrites=overwrites)
# Set explicit permissions instead of overwriting
await vc.set_permissions(mem, connect=True, reason=f"{ctx.author.name} allowed {mem.display_name} into their Custom VC")
await ctx.send(f"Allowed **{mem.display_name}** to join.")
async def deny_user_logic(self, ctx: commands.Context, user: str):
@ -363,16 +414,17 @@ class CustomVCCog(commands.Cog):
mem = await self.resolve_member(ctx, user)
if not mem:
return await ctx.send(f"Could not find user: {user}.")
info = self.CUSTOM_VC_INFO[vc.id]
# check if they're mod or owner
if has_custom_vc_permission(mem, info["owner_id"]):
return await ctx.send("Cannot deny a moderator or the owner.")
info["denied_ids"].add(mem.id)
overwrites = vc.overwrites or {}
overwrites[mem] = discord.PermissionOverwrite(connect=False)
await vc.edit(overwrites=overwrites)
await ctx.send(f"Denied {mem.display_name} from connecting.")
# Explicitly deny permission
await vc.set_permissions(mem, connect=False, reason=f"{ctx.author.name} denied {mem.display_name} from their Custom VC")
await ctx.send(f"Denied **{mem.display_name}** from connecting.")
async def unlock_channel_logic(self, ctx: commands.Context):
vc = self.get_custom_vc_for(ctx)
@ -381,47 +433,69 @@ class CustomVCCog(commands.Cog):
if not self.is_initiator_or_mod(ctx, vc.id):
return await ctx.send("No permission to unlock.")
# Fetch category and its permissions
category = vc.category
if not category:
return await ctx.send("Error: Could not determine category permissions.")
# Copy category permissions
overwrites = category.overwrites.copy()
# Update stored channel info
info = self.CUSTOM_VC_INFO[vc.id]
info["locked"] = False
overwrites = vc.overwrites or {}
overwrites[ctx.guild.default_role] = discord.PermissionOverwrite(connect=True)
for d_id in info["denied_ids"]:
m = ctx.guild.get_member(d_id)
if m:
overwrites[m] = discord.PermissionOverwrite(connect=False)
await vc.edit(overwrites=overwrites)
await ctx.send("Unlocked the channel. Denied users still cannot join.")
await vc.edit(overwrites=overwrites, reason=f'{ctx.author.name} unlocked their Custom VC')
await ctx.send("Unlocked the channel. Permissions now match the category default.")
async def set_user_limit_logic(self, ctx: commands.Context, limit: int):
MIN = 2
MAX = 99
vc = self.get_custom_vc_for(ctx)
if not vc:
return await ctx.send("Not in a custom VC.")
if limit < 2:
return await ctx.send("Minimum limit is 2.")
if limit > 99:
return await ctx.send("Maximum limit is 99.")
if limit < MIN:
return await ctx.send(f"Minimum limit is {MIN}.")
if limit > MAX:
return await ctx.send(f"Maximum limit is {MAX}.")
if not self.is_initiator_or_mod(ctx, vc.id):
return await ctx.send("No permission to set user limit.")
info = self.CUSTOM_VC_INFO[vc.id]
info["user_limit"] = limit
await vc.edit(user_limit=limit)
await vc.edit(user_limit=limit, reason=f'{ctx.author.name} changed users limit to {limit} in their Custom VC')
await ctx.send(f"User limit set to {limit}.")
async def set_bitrate_logic(self, ctx: commands.Context, kbps: int):
@customvc.command(name="audio_bitrate")
async def set_audio_bitrate(self, ctx: commands.Context, kbps: int):
"""Sets the audio bitrate for a VC."""
vc = self.get_custom_vc_for(ctx)
if not vc:
return await ctx.send("Not in a custom VC.")
if not self.is_initiator_or_mod(ctx, vc.id):
return await ctx.send("No permission to set bitrate.")
return await ctx.send("No permission to set audio bitrate.")
info = self.CUSTOM_VC_INFO[vc.id]
info["bitrate"] = kbps
if kbps < 8 or kbps > 128:
return await ctx.send(f"Invalid bitrate of {kbps} kbps! Must be a number between `8` and `128`! Default is 64 kbps")
await vc.edit(bitrate=kbps * 1000)
await ctx.send(f"Bitrate set to {kbps} kbps.")
return await ctx.send(f"Invalid audio bitrate! Must be between 8 and 128 kbps.")
await vc.edit(bitrate=kbps * 1000, reason=f"{ctx.author.name} changed audio bitrate")
await ctx.send(f"Audio bitrate set to {kbps} kbps.")
@customvc.command(name="video_bitrate")
async def set_video_bitrate(self, ctx: commands.Context, kbps: int):
"""Sets the video bitrate for a VC."""
vc = self.get_custom_vc_for(ctx)
if not vc:
return await ctx.send("Not in a custom VC.")
if not self.is_initiator_or_mod(ctx, vc.id):
return await ctx.send("No permission to set video bitrate.")
if kbps < 100 or kbps > 8000:
return await ctx.send(f"Invalid video bitrate! Must be between 100 and 8000 kbps.")
await vc.edit(video_quality_mode=discord.VideoQualityMode.full, bitrate=kbps * 1000, reason=f"{ctx.author.name} changed video bitrate")
await ctx.send(f"Video bitrate set to {kbps} kbps.")
async def set_status_logic(self, ctx: commands.Context, status_str: str):
vc = self.get_custom_vc_for(ctx)
@ -434,6 +508,25 @@ class CustomVCCog(commands.Cog):
info["status"] = status_str
await ctx.send(f"Channel status set to: **{status_str}** (placeholder).")
async def update_channel_name(self, member: discord.Member, vc: discord.VoiceChannel):
"""Dynamically renames VC based on the user's game or predefined rules."""
if not vc or not member:
return
game_name = None
if member.activities:
for activity in member.activities:
if isinstance(activity, discord.Game):
game_name = activity.name
break
if game_name:
new_name = f"{game_name}"
else:
new_name = f"{member.display_name}'s Channel"
await vc.edit(name=new_name, reason="Automatic VC Naming")
async def op_user_logic(self, ctx: commands.Context, user: str):
vc = self.get_custom_vc_for(ctx)
if not vc:

View File

@ -481,7 +481,7 @@ def lookup_user(db_conn, identifier: str, identifier_type: str, target_identifie
# Handle no result case
if not rows:
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}'", "INFO")
return None
# Convert the row to a dictionary

View File

@ -666,6 +666,10 @@ async def send_message(ctx, text):
"""
await ctx.send(text)
import uuid
from modules.db import run_db_operation, lookup_user
import globals
def track_user_activity(
db_conn,
platform: str,
@ -675,145 +679,86 @@ def track_user_activity(
user_is_bot: bool = False
):
"""
Create or update a user record in the database for a given platform.
Tracks or updates a user in the database.
This function checks whether a user with the specified user ID exists in the 'users'
table for the provided platform (either "discord" or "twitch"). If a matching record is found,
the function compares the provided username, display name, and bot status with the stored values,
updating the record if any discrepancies are detected. If no record exists, a new user record is
created with a generated UUID.
This function:
- Checks if the user already exists in Platform_Mapping.
- If found, updates username/display_name if changed.
- If not found, adds a new user and platform mapping entry.
Args:
db_conn: The active database connection used to perform database operations.
platform (str): The platform to which the user belongs. Expected values are "discord" or "twitch".
user_id (str): The unique identifier of the user on the given platform.
username (str): The raw username of the user (for Discord, this excludes the discriminator).
display_name (str): The display name of the user.
user_is_bot (bool, optional): Indicates whether the user is a bot on the platform.
Defaults to False.
db_conn: Active database connection.
platform (str): The platform of the user ("discord" or "twitch").
user_id (str): The unique user identifier on the platform.
username (str): The platform-specific username.
display_name (str): The platform-specific display name.
user_is_bot (bool, optional): Indicates if the user is a bot. Defaults to False.
Returns:
None
Side Effects:
- Logs debugging and error messages via the global logger.
- Updates an existing user record if discrepancies are found.
- Inserts a new user record if no existing record is found.
"""
globals.log(f"UUI Lookup for: {username} - {user_id} ({platform.lower()}) ...", "DEBUG")
# Decide which column we use for the ID lookup ("discord_user_id" or "twitch_user_id")
if platform.lower() in ("discord", "twitch"):
identifier_type = f"{platform.lower()}_user_id"
else:
platform = platform.lower()
valid_platforms = {"discord", "twitch"}
if platform not in valid_platforms:
globals.log(f"Unknown platform '{platform}' in track_user_activity!", "WARNING")
return
# 1) Try to find an existing user row.
user_data = lookup_user(db_conn, identifier=user_id, identifier_type=identifier_type)
# Look up user by platform-specific ID in Platform_Mapping
user_data = lookup_user(db_conn, identifier=user_id, identifier_type=f"{platform}_user_id")
if user_data:
# Found an existing row for that user ID on this platform.
# Check if the username or display_name is different and update if necessary.
# Existing user found, update info if necessary
user_uuid = user_data["UUID"]
need_update = False
column_updates = []
params = []
globals.log(f"... Returned {user_data}", "DEBUG")
if user_data["platform_username"] != username:
need_update = True
column_updates.append("Username = ?")
params.append(username)
if platform.lower() == "discord":
if user_data["platform_username"] != username:
need_update = True
column_updates.append("platform_username = ?")
params.append(username)
if user_data["platform_display_name"] != display_name:
need_update = True
column_updates.append("Display_Name = ?")
params.append(display_name)
if user_data["platform_display_name"] != display_name:
need_update = True
column_updates.append("platform_display_name = ?")
params.append(display_name)
if need_update:
update_sql = f"""
UPDATE Platform_Mapping
SET {", ".join(column_updates)}
WHERE Platform_User_ID = ? AND Platform_Type = ?
"""
params.extend([user_id, platform.capitalize()])
rowcount = run_db_operation(db_conn, "update", update_sql, params)
if user_data["user_is_bot"] != user_is_bot:
need_update = True
column_updates.append("user_is_bot = ?")
params.append(int(user_is_bot))
if rowcount and rowcount > 0:
globals.log(f"Updated {platform.capitalize()} user '{username}' (display '{display_name}') in Platform_Mapping.", "DEBUG")
return
if need_update:
set_clause = ", ".join(column_updates)
update_sql = f"""
UPDATE users
SET {set_clause}
WHERE discord_user_id = ?
"""
params.append(user_id)
# If user was not found in Platform_Mapping, check Users table
user_uuid = str(uuid.uuid4())
insert_user_sql = """
INSERT INTO Users (UUID, Unified_Username, user_is_banned, user_is_bot)
VALUES (?, ?, 0, ?)
"""
run_db_operation(db_conn, "write", insert_user_sql, (user_uuid, username, int(user_is_bot)))
rowcount = run_db_operation(db_conn, "update", update_sql, params=params)
if rowcount and rowcount > 0:
globals.log(f"Updated Discord user '{username}' (display '{display_name}') in 'users'.", "DEBUG")
elif platform.lower() == "twitch":
if user_data["platform_username"] != username:
need_update = True
column_updates.append("platform_username = ?")
params.append(username)
if user_data["platform_display_name"] != display_name:
need_update = True
column_updates.append("platform_display_name = ?")
params.append(display_name)
if user_data["user_is_bot"] != user_is_bot:
need_update = True
column_updates.append("user_is_bot = ?")
params.append(int(user_is_bot))
if need_update:
set_clause = ", ".join(column_updates)
update_sql = f"""
UPDATE users
SET {set_clause}
WHERE twitch_user_id = ?
"""
params.append(user_id)
rowcount = run_db_operation(db_conn, "update", update_sql, params=params)
if rowcount and rowcount > 0:
globals.log(f"Updated Twitch user '{username}' (display '{display_name}') in 'users'.", "DEBUG")
# Insert into Platform_Mapping
insert_mapping_sql = """
INSERT INTO Platform_Mapping (Platform_User_ID, Platform_Type, UUID, Display_Name, Username)
VALUES (?, ?, ?, ?, ?)
"""
params = (user_id, platform.capitalize(), user_uuid, display_name, username)
rowcount = run_db_operation(db_conn, "write", insert_mapping_sql, params)
if rowcount and rowcount > 0:
globals.log(f"Created new user entry for {platform} user '{username}' (display '{display_name}') with UUID={user_uuid}.", "DEBUG")
else:
# 2) No row found => create a new user row.
new_uuid = str(uuid.uuid4())
globals.log(f"Failed to create user entry for {platform} user '{username}'.", "ERROR")
if platform.lower() == "discord":
insert_sql = """
INSERT INTO users (
UUID,
discord_user_id,
platform_username,
platform_display_name,
user_is_bot
)
VALUES (?, ?, ?, ?, ?)
"""
params = (new_uuid, user_id, username, display_name, int(user_is_bot))
else: # platform is "twitch"
insert_sql = """
INSERT INTO users (
UUID,
twitch_user_id,
platform_username,
platform_display_name,
user_is_bot
)
VALUES (?, ?, ?, ?, ?)
"""
params = (new_uuid, user_id, username, display_name, int(user_is_bot))
rowcount = run_db_operation(db_conn, "write", insert_sql, params)
if rowcount and rowcount > 0:
globals.log(f"Created new user row for {platform} user '{username}' (display '{display_name}') with UUID={new_uuid}.", "DEBUG")
else:
globals.log(f"Failed to create new user row for {platform} user '{username}'", "ERROR")
from modules.db import log_bot_event