mirror of
https://gitlab.com/pancakes1234/wdiscordbotserver.git
synced 2025-06-16 07:14:21 -06:00
Refactor moderation settings commands and add AI model autocomplete
Splits the generic `modset` command into type-safe subcommands under a `modset_group` for improved usability and discoverability. Introduces loading of AI model names from `data/openrouter_models.json` to provide dynamic autocomplete suggestions for the `modsetmodel` command. Removes the `setup_hook` as commands are now registered automatically.
This commit is contained in:
parent
83e6456686
commit
1fcacc49c9
192
cogs/aimod.py
192
cogs/aimod.py
@ -140,6 +140,8 @@ class ModerationCog(commands.Cog):
|
||||
self.bot = bot
|
||||
# Create a persistent session for making API requests
|
||||
self.session = aiohttp.ClientSession()
|
||||
self.openrouter_models = []
|
||||
self._load_openrouter_models()
|
||||
print("ModerationCog Initialized.")
|
||||
# Check if the API key was successfully loaded from the environment variable
|
||||
if not OPENROUTER_API_KEY or OPENROUTER_API_KEY == "YOUR_OPENROUTER_API_KEY":
|
||||
@ -151,61 +153,115 @@ class ModerationCog(commands.Cog):
|
||||
else:
|
||||
print("Successfully loaded API key from AI_API_KEY environment variable.")
|
||||
|
||||
def _load_openrouter_models(self):
|
||||
"""Loads OpenRouter model data from the JSON file."""
|
||||
models_json_path = "data/openrouter_models.json" # Relative to bot's root
|
||||
try:
|
||||
if os.path.exists(models_json_path):
|
||||
with open(models_json_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
if "data" in data and isinstance(data["data"], list):
|
||||
for model_info in data["data"]:
|
||||
if isinstance(model_info, dict) and "id" in model_info and "name" in model_info:
|
||||
self.openrouter_models.append(
|
||||
{"id": model_info["id"], "name": model_info["name"]}
|
||||
)
|
||||
print(f"Successfully loaded {len(self.openrouter_models)} OpenRouter models for autocomplete.")
|
||||
else:
|
||||
print(f"Warning: {models_json_path} does not have the expected 'data' list structure.")
|
||||
else:
|
||||
print(f"Warning: {models_json_path} not found. AI_MODEL autocomplete will be empty.")
|
||||
except json.JSONDecodeError:
|
||||
print(f"Error: Could not decode JSON from {models_json_path}. AI_MODEL autocomplete will be empty.")
|
||||
except Exception as e:
|
||||
print(f"Error loading OpenRouter models from {models_json_path}: {e}. AI_MODEL autocomplete will be empty.")
|
||||
|
||||
|
||||
async def cog_unload(self):
|
||||
"""Clean up the session when the cog is unloaded."""
|
||||
await self.session.close()
|
||||
print("ModerationCog Unloaded, session closed.")
|
||||
|
||||
MOD_KEYS = [
|
||||
"MOD_LOG_CHANNEL_ID",
|
||||
"MODERATOR_ROLE_ID",
|
||||
"SUICIDAL_PING_ROLE_ID",
|
||||
"SUGGESTIONS_CHANNEL_ID",
|
||||
"NSFW_CHANNEL_IDS",
|
||||
"AI_MODEL",
|
||||
]
|
||||
# --- Moderation Configuration Command Group ---
|
||||
modset_group = app_commands.Group(name="modset", description="Configure moderation settings (admin only).")
|
||||
|
||||
async def modset_key_autocomplete(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
current: str
|
||||
):
|
||||
return [
|
||||
app_commands.Choice(name=k, value=k)
|
||||
for k in self.MOD_KEYS if current.lower() in k.lower()
|
||||
]
|
||||
@modset_group.command(name="log_channel", description="Set the moderation log channel.")
|
||||
@app_commands.describe(channel="The text channel to use for moderation logs.")
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
async def modset_log_channel(self, interaction: discord.Interaction, channel: discord.TextChannel):
|
||||
set_guild_config(interaction.guild.id, "MOD_LOG_CHANNEL_ID", channel.id)
|
||||
await interaction.response.send_message(f"Moderation log channel set to {channel.mention}.", ephemeral=False)
|
||||
|
||||
@app_commands.command(name="modset", description="Set a moderation config value for this guild (admin only).")
|
||||
@app_commands.describe(key="Config key", value="Value (int, comma-separated list, or string)")
|
||||
@app_commands.autocomplete(key=modset_key_autocomplete)
|
||||
async def modset(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
key: str,
|
||||
value: str
|
||||
):
|
||||
if not interaction.user.guild_permissions.administrator:
|
||||
await interaction.response.send_message("You must be an administrator to use this command.", ephemeral=False)
|
||||
return
|
||||
if key not in self.MOD_KEYS:
|
||||
await interaction.response.send_message(f"Invalid key. Choose from: {', '.join(self.MOD_KEYS)}", ephemeral=False)
|
||||
return
|
||||
@modset_group.command(name="suggestions_channel", description="Set the suggestions channel.")
|
||||
@app_commands.describe(channel="The text channel to use for suggestions.")
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
async def modset_suggestions_channel(self, interaction: discord.Interaction, channel: discord.TextChannel):
|
||||
set_guild_config(interaction.guild.id, "SUGGESTIONS_CHANNEL_ID", channel.id)
|
||||
await interaction.response.send_message(f"Suggestions channel set to {channel.mention}.", ephemeral=False)
|
||||
|
||||
@modset_group.command(name="moderator_role", description="Set the moderator role.")
|
||||
@app_commands.describe(role="The role that identifies moderators.")
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
async def modset_moderator_role(self, interaction: discord.Interaction, role: discord.Role):
|
||||
set_guild_config(interaction.guild.id, "MODERATOR_ROLE_ID", role.id)
|
||||
await interaction.response.send_message(f"Moderator role set to {role.mention}.", ephemeral=False)
|
||||
|
||||
@modset_group.command(name="suicidal_ping_role", description="Set the role to ping for suicidal content.")
|
||||
@app_commands.describe(role="The role to ping for urgent suicidal content alerts.")
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
async def modset_suicidal_ping_role(self, interaction: discord.Interaction, role: discord.Role):
|
||||
set_guild_config(interaction.guild.id, "SUICIDAL_PING_ROLE_ID", role.id)
|
||||
await interaction.response.send_message(f"Suicidal content ping role set to {role.mention}.", ephemeral=False)
|
||||
|
||||
@modset_group.command(name="add_nsfw_channel", description="Add a channel to the list of NSFW channels.")
|
||||
@app_commands.describe(channel="The text channel to mark as NSFW for the bot.")
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
async def modset_add_nsfw_channel(self, interaction: discord.Interaction, channel: discord.TextChannel):
|
||||
guild_id = interaction.guild.id
|
||||
# Try to parse value as int, list of ints, or fallback to string
|
||||
parsed_value = value
|
||||
if "," in value:
|
||||
try:
|
||||
parsed_value = [int(v.strip()) for v in value.split(",")]
|
||||
except Exception:
|
||||
parsed_value = [v.strip() for v in value.split(",")]
|
||||
nsfw_channels: list[int] = get_guild_config(guild_id, "NSFW_CHANNEL_IDS", [])
|
||||
if channel.id not in nsfw_channels:
|
||||
nsfw_channels.append(channel.id)
|
||||
set_guild_config(guild_id, "NSFW_CHANNEL_IDS", nsfw_channels)
|
||||
await interaction.response.send_message(f"{channel.mention} added to NSFW channels list.", ephemeral=False)
|
||||
else:
|
||||
try:
|
||||
parsed_value = int(value)
|
||||
except Exception:
|
||||
pass
|
||||
set_guild_config(guild_id, key, parsed_value)
|
||||
await interaction.response.send_message(f"Set `{key}` to `{parsed_value}` for this guild.", ephemeral=False)
|
||||
await interaction.response.send_message(f"{channel.mention} is already in the NSFW channels list.", ephemeral=True)
|
||||
|
||||
@modset_group.command(name="remove_nsfw_channel", description="Remove a channel from the list of NSFW channels.")
|
||||
@app_commands.describe(channel="The text channel to remove from the NSFW list.")
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
async def modset_remove_nsfw_channel(self, interaction: discord.Interaction, channel: discord.TextChannel):
|
||||
guild_id = interaction.guild.id
|
||||
nsfw_channels: list[int] = get_guild_config(guild_id, "NSFW_CHANNEL_IDS", [])
|
||||
if channel.id in nsfw_channels:
|
||||
nsfw_channels.remove(channel.id)
|
||||
set_guild_config(guild_id, "NSFW_CHANNEL_IDS", nsfw_channels)
|
||||
await interaction.response.send_message(f"{channel.mention} removed from NSFW channels list.", ephemeral=False)
|
||||
else:
|
||||
await interaction.response.send_message(f"{channel.mention} is not in the NSFW channels list.", ephemeral=True)
|
||||
|
||||
@modset_group.command(name="list_nsfw_channels", description="List currently configured NSFW channels.")
|
||||
@app_commands.checks.has_permissions(administrator=True)
|
||||
async def modset_list_nsfw_channels(self, interaction: discord.Interaction):
|
||||
guild_id = interaction.guild.id
|
||||
nsfw_channel_ids: list[int] = get_guild_config(guild_id, "NSFW_CHANNEL_IDS", [])
|
||||
if not nsfw_channel_ids:
|
||||
await interaction.response.send_message("No NSFW channels are currently configured.", ephemeral=False)
|
||||
return
|
||||
|
||||
channel_mentions = []
|
||||
for channel_id in nsfw_channel_ids:
|
||||
channel_obj = interaction.guild.get_channel(channel_id)
|
||||
if channel_obj:
|
||||
channel_mentions.append(channel_obj.mention)
|
||||
else:
|
||||
channel_mentions.append(f"ID:{channel_id} (not found)")
|
||||
|
||||
await interaction.response.send_message(f"Configured NSFW channels:\n- " + "\n- ".join(channel_mentions), ephemeral=False)
|
||||
|
||||
# Note: The @app_commands.command(name="modenable", ...) and other commands like
|
||||
# viewinfractions, clearinfractions, modsetmodel, modgetmodel remain as top-level commands
|
||||
# as they were not part of the original "modset" generic command structure.
|
||||
# If these also need to be grouped, that would be a separate consideration.
|
||||
|
||||
@app_commands.command(name="modenable", description="Enable or disable moderation for this guild (admin only).")
|
||||
@app_commands.describe(enabled="Enable moderation (true/false)")
|
||||
@ -311,6 +367,30 @@ class ModerationCog(commands.Cog):
|
||||
|
||||
await interaction.response.send_message(f"AI moderation model updated to `{model}` for this guild.", ephemeral=False)
|
||||
|
||||
@modsetmodel.autocomplete('model')
|
||||
async def modsetmodel_autocomplete(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
current: str
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
print(f"[DEBUG] modsetmodel_autocomplete triggered. Current input: '{current}'")
|
||||
if not self.openrouter_models:
|
||||
print("[DEBUG] modsetmodel_autocomplete: openrouter_models list is empty or not loaded.")
|
||||
return [app_commands.Choice(name="⚠️ Models not loaded", value="")]
|
||||
|
||||
print(f"[DEBUG] modsetmodel_autocomplete: Filtering {len(self.openrouter_models)} models with current: '{current}'")
|
||||
filtered_models = [
|
||||
m for m in self.openrouter_models
|
||||
if current.lower() in m["name"].lower() or current.lower() in m["id"].lower()
|
||||
][:25]
|
||||
|
||||
choices_to_return = [
|
||||
app_commands.Choice(name=m["name"][:100], value=m["id"][:100]) # Truncate name/value
|
||||
for m in filtered_models
|
||||
]
|
||||
print(f"[DEBUG] modsetmodel_autocomplete returning {len(choices_to_return)} choices: {choices_to_return[:5]}")
|
||||
return choices_to_return
|
||||
|
||||
@app_commands.command(name="modgetmodel", description="View the current AI model used for moderation.")
|
||||
async def modgetmodel(self, interaction: discord.Interaction):
|
||||
# Get the model from guild config, fall back to global default
|
||||
@ -329,13 +409,15 @@ class ModerationCog(commands.Cog):
|
||||
|
||||
await interaction.response.send_message(embed=embed, ephemeral=False)
|
||||
|
||||
async def setup_hook(self):
|
||||
self.bot.tree.add_command(self.modset)
|
||||
self.bot.tree.add_command(self.modenable)
|
||||
self.bot.tree.add_command(self.viewinfractions)
|
||||
self.bot.tree.add_command(self.clearinfractions)
|
||||
self.bot.tree.add_command(self.modsetmodel)
|
||||
self.bot.tree.add_command(self.modgetmodel)
|
||||
# Removed setup_hook as commands defined with @app_commands.command in a Cog
|
||||
# are typically automatically registered when the cog is added.
|
||||
# async def setup_hook(self):
|
||||
# self.bot.tree.add_command(self.modset)
|
||||
# self.bot.tree.add_command(self.modenable)
|
||||
# self.bot.tree.add_command(self.viewinfractions)
|
||||
# self.bot.tree.add_command(self.clearinfractions)
|
||||
# self.bot.tree.add_command(self.modsetmodel)
|
||||
# self.bot.tree.add_command(self.modgetmodel)
|
||||
|
||||
async def query_openrouter(self, message: discord.Message, message_content: str, user_history: str):
|
||||
"""
|
||||
@ -551,6 +633,9 @@ Now, analyze the provided message content based on the rules and instructions gi
|
||||
|
||||
current_timestamp_iso = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||
|
||||
# Get the model from guild config, fall back to global default (moved up from line 609)
|
||||
model_used = get_guild_config(guild_id, "AI_MODEL", OPENROUTER_MODEL)
|
||||
|
||||
# --- Transmit action info over HTTP POST ---
|
||||
try:
|
||||
mod_log_api_secret = os.getenv("MOD_LOG_API_SECRET")
|
||||
@ -605,8 +690,7 @@ Now, analyze the provided message content based on the rules and instructions gi
|
||||
# Log message content and attachments for audit purposes
|
||||
msg_content = message.content if message.content else "*No text content*"
|
||||
notification_embed.add_field(name="Message Content", value=msg_content[:1024], inline=False)
|
||||
# Get the model from guild config, fall back to global default
|
||||
model_used = get_guild_config(guild_id, "AI_MODEL", OPENROUTER_MODEL)
|
||||
# Use the model_used variable that was defined earlier
|
||||
notification_embed.set_footer(text=f"AI Model: {model_used}")
|
||||
notification_embed.timestamp = discord.utils.utcnow() # Using discord.utils.utcnow() which is still supported
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user