""" GhostWipe HTML report generator. Builds a single self-contained HTML file (no external resources) that lets a moderator browse every message GhostWipe deleted for a departing member, switching between channels, searching content, and inspecting attachments. """ from __future__ import annotations import html import json from typing import Any, Dict, List TEMPLATE = r""" __PAGE_TITLE__

Select a channel

""" def _badge_class(reason: str) -> str: return { "left": "badge-left", "kicked": "badge-kicked", "banned": "badge-banned", }.get(reason, "badge-left") def _badge_label(reason: str) -> str: return { "left": "Left", "kicked": "Kicked", "banned": "Banned", }.get(reason, reason.title()) def generate_report_html( *, guild_name: str, member_name: str, member_id: int, avatar_url: str, reason: str, event_time_str: str, moderator: str = None, mod_reason: str = None, dry_run: bool = False, reveal_content: bool = True, channels: List[Dict[str, Any]], version: str = "1.0.0", generated_at: str = "", ) -> str: """Build the standalone HTML report string. ``channels`` is a list of dicts shaped like: { "id": str, "name": str, "type": "text"|"thread"|"voice", "skipped": bool, "skip_reason": str | None, "message_count": int, "messages": [ {"id": str, "timestamp": str, "content": str, "attachments": [{"filename", "url", "size", "is_image"}], "stickers": [str]}, ... ], } """ total_deleted = sum(c["message_count"] for c in channels if not c.get("skipped")) channels_affected = sum(1 for c in channels if not c.get("skipped") and c["message_count"] > 0) channels_skipped = sum(1 for c in channels if c.get("skipped")) if not reveal_content: for ch in channels: for m in ch.get("messages", []): m["redacted"] = True meta_bits = [f"Server: {guild_name}"] if moderator: meta_bits.append(f"By: {moderator}") if mod_reason: meta_bits.append(f"Reason: {mod_reason}") meta_line = " • ".join(meta_bits) payload = { "guild_name": guild_name, "member_name": member_name, "member_id": str(member_id), "reason": reason, "dry_run": dry_run, "meta_line": meta_line, "totals": { "messages_deleted": total_deleted, "channels_affected": channels_affected, "channels_skipped": channels_skipped, }, "channels": channels, } out = TEMPLATE out = out.replace("__PAGE_TITLE__", html.escape(f"GhostWipe Report — {member_name}")) out = out.replace("__AVATAR_URL__", html.escape(avatar_url or "", quote=True)) out = out.replace("__USERNAME__", html.escape(member_name)) out = out.replace("__REASON_BADGE_CLASS__", _badge_class(reason)) out = out.replace("__REASON_LABEL__", _badge_label(reason)) out = out.replace("__USER_ID__", str(member_id)) out = out.replace("__GHOSTWIPE_VERSION__", html.escape(version)) out = out.replace("__GENERATED_AT__", html.escape(generated_at or event_time_str)) out = out.replace("__REPORT_DATA_JSON__", json.dumps(payload)) return out