Enhance appeal system with context
This commit is contained in:
parent
0a8be35c47
commit
2b36081b98
@ -1001,6 +1001,7 @@ class AIModerationCog(commands.Cog):
|
||||
action="The action you are appealing",
|
||||
reason="Explain why you believe the action was incorrect",
|
||||
guild_id="If using in DMs, provide the server ID",
|
||||
message_id="ID of the moderated message you are appealing (optional)",
|
||||
)
|
||||
async def appeal_submit(
|
||||
self,
|
||||
@ -1008,6 +1009,7 @@ class AIModerationCog(commands.Cog):
|
||||
action: str,
|
||||
reason: str,
|
||||
guild_id: int | None = None,
|
||||
message_id: int | None = None,
|
||||
):
|
||||
guild = interaction.guild or (
|
||||
self.bot.get_guild(guild_id) if guild_id else None
|
||||
@ -1026,10 +1028,33 @@ class AIModerationCog(commands.Cog):
|
||||
)
|
||||
return
|
||||
|
||||
ai_review = await self.run_appeal_ai(guild, interaction.user, action, reason)
|
||||
infractions = get_user_infraction_history(guild.id, interaction.user.id)
|
||||
target_infraction = None
|
||||
if message_id:
|
||||
for infr in infractions[::-1]:
|
||||
if infr.get("message_id") == message_id:
|
||||
target_infraction = infr
|
||||
break
|
||||
if not target_infraction and infractions:
|
||||
target_infraction = infractions[-1]
|
||||
|
||||
ai_review = await self.run_appeal_ai(
|
||||
guild,
|
||||
interaction.user,
|
||||
action,
|
||||
reason,
|
||||
target_infraction,
|
||||
)
|
||||
timestamp = datetime.datetime.utcnow().isoformat()
|
||||
ref = target_infraction.get("message_id") if target_infraction else None
|
||||
await add_user_appeal(
|
||||
guild.id, interaction.user.id, action, reason, timestamp, ai_review
|
||||
guild.id,
|
||||
interaction.user.id,
|
||||
action,
|
||||
reason,
|
||||
timestamp,
|
||||
ai_review,
|
||||
str(ref) if ref else None,
|
||||
)
|
||||
|
||||
embed = discord.Embed(title="New Appeal", color=discord.Color.blue())
|
||||
@ -1039,6 +1064,8 @@ class AIModerationCog(commands.Cog):
|
||||
inline=False,
|
||||
)
|
||||
embed.add_field(name="Action", value=action, inline=False)
|
||||
if ref:
|
||||
embed.add_field(name="Infraction", value=f"Message ID: {ref}", inline=False)
|
||||
embed.add_field(name="Appeal", value=reason, inline=False)
|
||||
embed.add_field(name="AI Review", value=ai_review[:1000], inline=False)
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
@ -1085,11 +1112,11 @@ class AIModerationCog(commands.Cog):
|
||||
summary = summary[:147] + "..."
|
||||
if len(ai_sum) > 150:
|
||||
ai_sum = ai_sum[:147] + "..."
|
||||
embed.add_field(
|
||||
name=f"Appeal #{i} - {ts}",
|
||||
value=f"Action: {appeal.get('action')}\nReason: {summary}\nAI: {ai_sum}",
|
||||
inline=False,
|
||||
)
|
||||
value = f"Action: {appeal.get('action')}\nReason: {summary}\nAI: {ai_sum}"
|
||||
ref = appeal.get("infraction_reference")
|
||||
if ref:
|
||||
value = f"Infraction: {ref}\n" + value
|
||||
embed.add_field(name=f"Appeal #{i} - {ts}", value=value, inline=False)
|
||||
await interaction.response.send_message(embed=embed, ephemeral=False)
|
||||
|
||||
@appeal_subgroup.command(
|
||||
@ -1121,7 +1148,7 @@ class AIModerationCog(commands.Cog):
|
||||
results: list[tuple[str, str]] = []
|
||||
for action, text in scenarios:
|
||||
result = await self.run_appeal_ai(
|
||||
interaction.guild, interaction.user, action, text
|
||||
interaction.guild, interaction.user, action, text, None
|
||||
)
|
||||
results.append((action, result))
|
||||
|
||||
@ -1820,6 +1847,7 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
message: discord.Message,
|
||||
ai_decision: dict,
|
||||
notify_mods_message: str = None,
|
||||
link_urls: list[str] | None = None,
|
||||
):
|
||||
"""
|
||||
Takes action based on the AI's violation decision.
|
||||
@ -2071,6 +2099,10 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
"BAN",
|
||||
reasoning,
|
||||
current_timestamp_iso,
|
||||
message.id,
|
||||
message.channel.id,
|
||||
message.content[:100] if message.content else "",
|
||||
[a.url for a in message.attachments] + (link_urls or []),
|
||||
)
|
||||
|
||||
elif action == "KICK":
|
||||
@ -2103,6 +2135,10 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
"KICK",
|
||||
reasoning,
|
||||
current_timestamp_iso,
|
||||
message.id,
|
||||
message.channel.id,
|
||||
message.content[:100] if message.content else "",
|
||||
[a.url for a in message.attachments] + (link_urls or []),
|
||||
)
|
||||
|
||||
elif action.startswith("TIMEOUT"):
|
||||
@ -2154,6 +2190,10 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
action,
|
||||
reasoning,
|
||||
current_timestamp_iso,
|
||||
message.id,
|
||||
message.channel.id,
|
||||
message.content[:100] if message.content else "",
|
||||
[a.url for a in message.attachments] + (link_urls or []),
|
||||
)
|
||||
else:
|
||||
action_taken_message = (
|
||||
@ -2221,6 +2261,10 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
"WARN",
|
||||
reasoning,
|
||||
current_timestamp_iso,
|
||||
message.id,
|
||||
message.channel.id,
|
||||
message.content[:100] if message.content else "",
|
||||
[a.url for a in message.attachments] + (link_urls or []),
|
||||
)
|
||||
|
||||
elif action == "NOTIFY_MODS":
|
||||
@ -2381,7 +2425,12 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
)
|
||||
|
||||
async def run_appeal_ai(
|
||||
self, guild: discord.Guild, member: discord.User, action: str, appeal_text: str
|
||||
self,
|
||||
guild: discord.Guild,
|
||||
member: discord.User,
|
||||
action: str,
|
||||
appeal_text: str,
|
||||
infraction: dict | None = None,
|
||||
) -> str:
|
||||
"""Run the appeal text through the higher tier AI model."""
|
||||
if not self.genai_client:
|
||||
@ -2396,10 +2445,28 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
"Return a short verdict (UPHOLD or OVERTURN) and your reasoning in plain text."
|
||||
)
|
||||
|
||||
context_lines = []
|
||||
if infraction:
|
||||
channel_name = guild.get_channel(infraction.get("channel_id", 0))
|
||||
channel_display = (
|
||||
channel_name.name if channel_name else str(infraction.get("channel_id"))
|
||||
)
|
||||
context_lines.append(f"Original Channel: {channel_display}")
|
||||
msg_content = infraction.get("message_content")
|
||||
if msg_content:
|
||||
context_lines.append(f"Message Snippet: {msg_content}")
|
||||
attachments = infraction.get("attachments")
|
||||
if attachments:
|
||||
context_lines.append(f"Attachments: {attachments}")
|
||||
reasoning = infraction.get("reasoning")
|
||||
if reasoning:
|
||||
context_lines.append(f"AI Reasoning: {reasoning}")
|
||||
|
||||
user_prompt = (
|
||||
f"Server Rules:\n{SERVER_RULES}\n\n"
|
||||
f"User History:\n{history_text}\n\n"
|
||||
f"Action Appealed: {action}\n"
|
||||
+ ("\n".join(context_lines) + "\n\n" if context_lines else "")
|
||||
+ f"Action Appealed: {action}\n"
|
||||
f"Appeal Text: {appeal_text}"
|
||||
)
|
||||
|
||||
@ -2439,6 +2506,7 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
if message.author.bot:
|
||||
print(f"Ignoring message {message.id} from bot.")
|
||||
return
|
||||
link_urls: list[str] = []
|
||||
embed_urls = [embed.url for embed in message.embeds if embed.url]
|
||||
link_urls = (
|
||||
self.extract_direct_attachment_urls(" ".join(embed_urls))
|
||||
@ -2613,7 +2681,9 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
if ai_decision.get("action") == "NOTIFY_MODS"
|
||||
else None
|
||||
)
|
||||
await self.handle_violation(message, ai_decision, notify_mods_message)
|
||||
await self.handle_violation(
|
||||
message, ai_decision, notify_mods_message, link_urls
|
||||
)
|
||||
else:
|
||||
# AI found no violation
|
||||
print(
|
||||
@ -2725,7 +2795,9 @@ CRITICAL: Do NOT output anything other than the required JSON response.
|
||||
results = []
|
||||
guild = interaction.guild
|
||||
for action, text in scenarios:
|
||||
review = await self.run_appeal_ai(guild, interaction.user, action, text)
|
||||
review = await self.run_appeal_ai(
|
||||
guild, interaction.user, action, text, None
|
||||
)
|
||||
results.append((action, text, review))
|
||||
|
||||
embed = discord.Embed(
|
||||
|
@ -128,6 +128,10 @@ async def add_user_infraction(
|
||||
action_taken: str,
|
||||
reasoning: str,
|
||||
timestamp: str,
|
||||
message_id: int | None = None,
|
||||
channel_id: int | None = None,
|
||||
message_content: str | None = None,
|
||||
attachments: list[str] | None = None,
|
||||
):
|
||||
key = f"{guild_id}_{user_id}"
|
||||
if key not in USER_INFRACTIONS:
|
||||
@ -139,6 +143,14 @@ async def add_user_infraction(
|
||||
"action_taken": action_taken,
|
||||
"reasoning": reasoning,
|
||||
}
|
||||
if message_id is not None:
|
||||
infraction_record["message_id"] = message_id
|
||||
if channel_id is not None:
|
||||
infraction_record["channel_id"] = channel_id
|
||||
if message_content is not None:
|
||||
infraction_record["message_content"] = message_content
|
||||
if attachments:
|
||||
infraction_record["attachments"] = attachments
|
||||
USER_INFRACTIONS[key].append(infraction_record)
|
||||
USER_INFRACTIONS[key] = USER_INFRACTIONS[key][-10:]
|
||||
await save_user_infractions()
|
||||
@ -156,6 +168,7 @@ async def add_user_appeal(
|
||||
appeal_text: str,
|
||||
timestamp: str,
|
||||
ai_review: str,
|
||||
infraction_reference: str | None = None,
|
||||
):
|
||||
key = f"{guild_id}_{user_id}"
|
||||
if key not in USER_APPEALS:
|
||||
@ -166,6 +179,7 @@ async def add_user_appeal(
|
||||
"action": action,
|
||||
"appeal_text": appeal_text,
|
||||
"ai_review": ai_review,
|
||||
"infraction_reference": infraction_reference,
|
||||
}
|
||||
USER_APPEALS[key].append(appeal_record)
|
||||
USER_APPEALS[key] = USER_APPEALS[key][-10:]
|
||||
|
Loading…
x
Reference in New Issue
Block a user