0.3.9.4.a1
Added image/video proof to pirate reports as a requirement. This is displayed in-line for moderators in the review message
This commit is contained in:
parent
7b5bcff6ac
commit
b74002e69f
2
bot.py
2
bot.py
@ -9,7 +9,7 @@ from modules.common.boot_notice import post_boot_notice
|
||||
|
||||
# Version consists of:
|
||||
# Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesn’t trigger auto update)
|
||||
VERSION = "0.3.9.3.a4"
|
||||
VERSION = "0.3.9.4.a1"
|
||||
|
||||
# ---------- Env loading ----------
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import urlparse
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord import app_commands
|
||||
@ -21,6 +22,11 @@ CROSS = '❌'
|
||||
|
||||
_ACCT_RE = re.compile(r'.+#\d{5}$')
|
||||
|
||||
DISCORD_MEDIA_HOSTS = {"cdn.discordapp.com", "media.discordapp.net"}
|
||||
MEDIA_EXTS_IMAGE = {".png", ".jpg", ".jpeg", ".gif", ".webp"}
|
||||
MEDIA_EXTS_VIDEO = {".mp4", ".webm", ".mov"}
|
||||
MEDIA_EXTS_ALL = MEDIA_EXTS_IMAGE | MEDIA_EXTS_VIDEO
|
||||
|
||||
def _acct_ok(s: str) -> bool:
|
||||
return bool(_ACCT_RE.fullmatch(s.strip()))
|
||||
|
||||
@ -35,6 +41,51 @@ def _parse_bool(s: str) -> bool:
|
||||
return False
|
||||
raise ValueError("Please enter yes or no")
|
||||
|
||||
def _classify_discord_media(url: str):
|
||||
"""
|
||||
Return ('image'|'video', normalized_url) if valid Discord CDN media; else (None, reason).
|
||||
We only accept direct CDN links so media can render inline without leaving the channel.
|
||||
"""
|
||||
try:
|
||||
u = url.strip()
|
||||
if not u:
|
||||
return (None, "Empty URL.")
|
||||
pr = urlparse(u)
|
||||
if pr.scheme not in ("http", "https"):
|
||||
return (None, "URL must start with http:// or https://")
|
||||
host = pr.netloc.lower()
|
||||
if host not in DISCORD_MEDIA_HOSTS:
|
||||
return (None, "URL must be a **Discord media** link (cdn.discordapp.com or media.discordapp.net).")
|
||||
path = pr.path or ""
|
||||
dot = path.rfind(".")
|
||||
if dot == -1:
|
||||
return (None, "URL must end with a known media file extension.")
|
||||
ext = path[dot:].lower()
|
||||
if ext not in MEDIA_EXTS_ALL:
|
||||
return (None, f"Unsupported media type `{ext}`. Allowed: images {sorted(MEDIA_EXTS_IMAGE)}, videos {sorted(MEDIA_EXTS_VIDEO)}.")
|
||||
kind = "image" if ext in MEDIA_EXTS_IMAGE else "video"
|
||||
return (kind, u)
|
||||
except Exception:
|
||||
return (None, "Invalid URL format.")
|
||||
|
||||
def _make_report_embed(title: str, color: discord.Color, report_dict: dict, include_status: bool = False):
|
||||
e = discord.Embed(title=title, color=color, timestamp=datetime.utcnow())
|
||||
e.add_field(name="Character", value=report_dict['character_name'], inline=False)
|
||||
e.add_field(name="Account", value=report_dict['account_name'], inline=False)
|
||||
e.add_field(name="Submitted by", value=f"<@{report_dict['submitter_id']}>", inline=False)
|
||||
if include_status:
|
||||
e.add_field(
|
||||
name="Status",
|
||||
value=report_dict.get('status_line', 'Pending'),
|
||||
inline=False
|
||||
)
|
||||
# If proof is an image, show it in the embed (videos are posted as message content)
|
||||
if report_dict.get('proof_type') == 'image':
|
||||
e.set_image(url=report_dict.get('proof_url', ''))
|
||||
# A small hint footer
|
||||
e.set_footer(text="Proof is included in this message.")
|
||||
return e
|
||||
|
||||
# --------------------- Modals ----------------------
|
||||
|
||||
class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
|
||||
@ -54,9 +105,16 @@ class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
|
||||
max_length=64,
|
||||
required=True
|
||||
)
|
||||
self.proof_url = discord.ui.TextInput(
|
||||
label="Proof (Discord media URL)",
|
||||
placeholder="Direct link to image/video from Discord (CDN)",
|
||||
required=True,
|
||||
max_length=300
|
||||
)
|
||||
|
||||
self.add_item(self.character_name)
|
||||
self.add_item(self.account_name)
|
||||
self.add_item(self.proof_url)
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
if not interaction.guild:
|
||||
@ -64,6 +122,7 @@ class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
|
||||
|
||||
char = self.character_name.value.strip()
|
||||
acct = self.account_name.value.strip()
|
||||
proof_raw = self.proof_url.value.strip()
|
||||
|
||||
if not _acct_ok(acct):
|
||||
return await interaction.response.send_message(
|
||||
@ -71,6 +130,10 @@ class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
proof_type, proof_val = _classify_discord_media(proof_raw)
|
||||
if proof_type is None:
|
||||
return await interaction.response.send_message(f"❌ Invalid proof link: {proof_val}", ephemeral=True)
|
||||
|
||||
dm = self.cog.bot.data_manager
|
||||
char_l = char.lower()
|
||||
acct_l = acct.lower()
|
||||
@ -97,18 +160,24 @@ class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
|
||||
print("[pirate_report] ack send failed:", repr(e))
|
||||
ack = None
|
||||
|
||||
# Send to mod channel with ✅/❌
|
||||
# Send to mod channel with ✅/❌, including inline media
|
||||
mod_ch = interaction.guild.get_channel(self.cog.mod_channel)
|
||||
if not mod_ch:
|
||||
return await interaction.response.send_message("❌ Mod channel not configured.", ephemeral=True)
|
||||
|
||||
report_payload = {
|
||||
'character_name': char,
|
||||
'account_name': acct,
|
||||
'submitter_id': interaction.user.id,
|
||||
'proof_url': proof_val,
|
||||
'proof_type': proof_type,
|
||||
}
|
||||
|
||||
try:
|
||||
mod_msg = await mod_ch.send(
|
||||
f"🚩 **Pirate Report**\n"
|
||||
f"**Character:** {char}\n"
|
||||
f"**Account:** {acct}\n"
|
||||
f"**Submitted by:** {interaction.user.mention}"
|
||||
)
|
||||
embed = _make_report_embed("🚩 Pirate Report", discord.Color.orange(), report_payload, include_status=False)
|
||||
# Video must be in message content for inline player; images live in the embed
|
||||
content = proof_val if proof_type == 'video' else None
|
||||
mod_msg = await mod_ch.send(content=content, embed=embed)
|
||||
await mod_msg.add_reaction(CHECK)
|
||||
await mod_msg.add_reaction(CROSS)
|
||||
except Exception as e:
|
||||
@ -124,7 +193,9 @@ class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
|
||||
'origin_channel_id': interaction.channel.id if interaction.channel else 0,
|
||||
'ack_message_id': ack.id if ack else 0,
|
||||
'status': 'pending',
|
||||
'ts': now
|
||||
'ts': now,
|
||||
'proof_url': proof_val,
|
||||
'proof_type': proof_type,
|
||||
})
|
||||
|
||||
await interaction.response.send_message("✅ Report submitted to moderators.", ephemeral=True)
|
||||
@ -525,20 +596,29 @@ class PirateReportCog(commands.Cog):
|
||||
guild = channel.guild
|
||||
stamp = _now_utc_str()
|
||||
|
||||
header_emoji = CHECK if approved else CROSS
|
||||
new_content = (
|
||||
f"{header_emoji} **Pirate Report**\n"
|
||||
f"**Character:** {report['character_name']}\n"
|
||||
f"**Account:** {report['account_name']}\n"
|
||||
f"**Submitted by:** <@{report['submitter_id']}>\n\n"
|
||||
f"**Status:** {'Approved' if approved else 'Rejected'} by <@{payload.user_id}> on {stamp}"
|
||||
)
|
||||
# Build new embed + content (keep proof visible)
|
||||
status_text = f"{'Approved' if approved else 'Rejected'} by <@{payload.user_id}> on {stamp}"
|
||||
report_view = {
|
||||
'character_name': report['character_name'],
|
||||
'account_name': report['account_name'],
|
||||
'submitter_id': report['submitter_id'],
|
||||
'proof_url': report.get('proof_url', ''),
|
||||
'proof_type': report.get('proof_type', ''),
|
||||
'status_line': status_text,
|
||||
}
|
||||
color = discord.Color.green() if approved else discord.Color.red()
|
||||
title = "✅ Pirate Report — Approved" if approved else "❌ Pirate Report — Rejected"
|
||||
new_embed = _make_report_embed(title, color, report_view, include_status=True)
|
||||
|
||||
# For videos, ensure the URL stays in message content so the inline player remains visible.
|
||||
new_content = report_view['proof_url'] if report_view.get('proof_type') == 'video' else None
|
||||
|
||||
try:
|
||||
await msg.clear_reactions()
|
||||
except Exception as e:
|
||||
print("[pirate_report] clear reactions failed:", repr(e))
|
||||
try:
|
||||
await msg.edit(content=new_content)
|
||||
await msg.edit(content=new_content, embed=new_embed)
|
||||
except Exception as e:
|
||||
print("[pirate_report] edit mod msg failed:", repr(e))
|
||||
|
||||
@ -595,4 +675,4 @@ async def setup(bot):
|
||||
else:
|
||||
bot.tree.add_command(cog.report)
|
||||
bot.tree.add_command(cog.edit_pirate)
|
||||
bot.tree.add_command(cog.encounter)
|
||||
bot.tree.add_command(cog.encounter)
|
||||
|
Loading…
Reference in New Issue
Block a user