0.3.9.7.a3

Changes to Pirate Reports:
- Proof is no longer **required** but **encouraged**.
- Mods can click a button on the review to jump to the report ACK message
This commit is contained in:
Franz Rolfsvaag 2025-08-11 09:26:56 +02:00
parent b6980794d7
commit 4f0e000c93
2 changed files with 76 additions and 17 deletions

2
bot.py
View File

@ -9,7 +9,7 @@ from modules.common.boot_notice import post_boot_notice
# Version consists of:
# Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesnt trigger auto update)
VERSION = "0.3.9.7.a2"
VERSION = "0.3.9.7.a3"
# ---------- Env loading ----------

View File

@ -27,12 +27,15 @@ 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()))
def _now_utc_str() -> str:
return datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
def _parse_bool(s: str) -> bool:
v = s.strip().lower()
if v in ('y', 'yes', 'true', 't', '1'):
@ -41,6 +44,7 @@ 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).
@ -68,24 +72,53 @@ def _classify_discord_media(url: str):
except Exception:
return (None, "Invalid URL format.")
def _jump_url(guild_id: int, channel_id: int, message_id: int) -> str:
return f"https://discord.com/channels/{guild_id}/{channel_id}/{message_id}"
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)
proof_url = (report_dict.get('proof_url') or "").strip()
proof_type = (report_dict.get('proof_type') or "").strip()
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', ''))
# Show proof inline if it's an image; for videos we'll keep the URL in message content.
if proof_url and proof_type == 'image':
e.set_image(url=proof_url)
# Add a compact proof field for quick visibility (always safe to include)
e.add_field(
name="Proof",
value=proof_url if proof_url else "_No proof provided — strongly encouraged._",
inline=False
)
# A small hint footer
e.set_footer(text="Proof is included in this message.")
e.set_footer(text="Attach proof when possible to speed up moderation.")
return e
# --------------------- Views ----------------------
class ReportJumpView(discord.ui.View):
"""Simple link button to jump to the user's original ack message."""
def __init__(self, url: str):
super().__init__(timeout=None)
if url:
self.add_item(discord.ui.Button(label="Jump to message", style=discord.ButtonStyle.link, url=url))
# --------------------- Modals ----------------------
class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
@ -106,9 +139,9 @@ class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
required=True
)
self.proof_url = discord.ui.TextInput(
label="Proof (Discord media URL)",
placeholder="Direct link to image/video from Discord (CDN)",
required=True,
label="Proof (Discord media URL — optional)",
placeholder="Direct Discord CDN link to image/video (highly encouraged, but optional)",
required=False,
max_length=300
)
@ -122,7 +155,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()
proof_raw = (self.proof_url.value or "").strip()
if not _acct_ok(acct):
return await interaction.response.send_message(
@ -130,9 +163,13 @@ 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)
proof_type = ""
proof_val = ""
if proof_raw:
pt, pv = _classify_discord_media(proof_raw)
if pt is None:
return await interaction.response.send_message(f"❌ Invalid proof link: {pv}", ephemeral=True)
proof_type, proof_val = pt, pv # valid
dm = self.cog.bot.data_manager
char_l = char.lower()
@ -160,7 +197,7 @@ class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
print("[pirate_report] ack send failed:", repr(e))
ack = None
# Send to mod channel with ✅/❌, including inline media
# Send to mod channel with ✅/❌, including inline media if present
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)
@ -173,11 +210,20 @@ class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
'proof_type': proof_type,
}
# Build jump button (if ack exists)
view = None
if ack:
try:
url = _jump_url(interaction.guild.id, interaction.channel.id, ack.id)
view = ReportJumpView(url)
except Exception:
view = None
try:
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)
mod_msg = await mod_ch.send(content=content, embed=embed, view=view)
await mod_msg.add_reaction(CHECK)
await mod_msg.add_reaction(CROSS)
except Exception as e:
@ -200,6 +246,7 @@ class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
await interaction.response.send_message("✅ Report submitted to moderators.", ephemeral=True)
class EditPirateModal(discord.ui.Modal, title="Edit Pirate Entry"):
def __init__(self, cog: "PirateReportCog"):
super().__init__()
@ -274,6 +321,7 @@ class EditPirateModal(discord.ui.Modal, title="Edit Pirate Entry"):
await interaction.response.send_message("✅ Pirate updated.", ephemeral=True)
await self.cog._refresh_pirates_list(interaction.guild)
class EncounterModal(discord.ui.Modal, title="Log Pirate Encounter"):
def __init__(self, cog: "PirateReportCog"):
super().__init__()
@ -409,6 +457,7 @@ class EncounterModal(discord.ui.Modal, title="Log Pirate Encounter"):
await self.cog._refresh_pirates_list(interaction.guild)
# -------------- Cog: commands + listeners ---------------
class PirateReportCog(commands.Cog):
@ -596,7 +645,7 @@ class PirateReportCog(commands.Cog):
guild = channel.guild
stamp = _now_utc_str()
# Build new embed + content (keep proof visible)
# Build new embed + content (keep proof visible if present)
status_text = f"{'Approved' if approved else 'Rejected'} by <@{payload.user_id}> on {stamp}"
report_view = {
'character_name': report['character_name'],
@ -613,12 +662,21 @@ class PirateReportCog(commands.Cog):
# 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
# Always (re)attach jump button if we have the ack info
view = None
try:
if report.get('origin_channel_id') and report.get('ack_message_id'):
url = _jump_url(guild.id, report['origin_channel_id'], report['ack_message_id'])
view = ReportJumpView(url)
except Exception:
view = 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, embed=new_embed)
await msg.edit(content=new_content, embed=new_embed, view=view)
except Exception as e:
print("[pirate_report] edit mod msg failed:", repr(e))
@ -648,5 +706,6 @@ class PirateReportCog(commands.Cog):
dm.remove('reports', lambda r: r.get('report_id') == msg.id)
async def setup(bot):
await bot.add_cog(PirateReportCog(bot))