VisionApp/tickets/transcript.py
Mathis 57b6f09dd7
All checks were successful
ci / ci (push) Successful in 46s
feat(ticket): rewrite ticket system from scratch inspired by Kuby
New minimal ticket system (dev only):
- Panel with create ticket button (Components V2, no accent colors)
- Category selection via dropdown or direct modal (1 cat)
- Ticket channel creation with permissions, staff role, welcome message
- Close flow: staff check, transcript HTML (dark theme), log, auto-delete
- /ticket setup, /ticket config (interactive panel)
- Storage: JSON per-ticket + config.json (singleton pattern)
- Modular architecture: data.py, storage.py, ui.py, transcript.py, cog.py
- Removed old modmail system
2026-09-20 20:04:10 +02:00

174 lines
4.4 KiB
Python

from __future__ import annotations
import html
from datetime import datetime, timezone
from tickets.data import TicketData
def _format_ts(dt_str: str) -> str:
try:
dt = datetime.fromisoformat(dt_str)
return dt.strftime("%d/%m/%Y %H:%M:%S")
except (ValueError, TypeError):
return dt_str
def generate_transcript(ticket: TicketData, guild_name: str = "") -> str:
now = datetime.now(timezone.utc).strftime("%d/%m/%Y %H:%M:%S")
messages_html = ""
for msg in ticket.messages:
content = html.escape(msg.content) if msg.content else ""
ts = _format_ts(msg.timestamp)
badge = '<span class="badge badge-staff">STAFF</span>' if msg.is_staff else ""
att_html = ""
for att in msg.get("attachments", []) if hasattr(msg, "attachments") else msg.attachments:
if any(att.lower().endswith(ext) for ext in (".png", ".jpg", ".jpeg", ".gif", ".webp")):
att_html += f'<div class="attachment"><img src="{html.escape(att)}" /></div>'
else:
att_html += f'<div class="attachment"><a href="{html.escape(att)}">{html.escape(att.rsplit("/", 1)[-1])}</a></div>'
messages_html += f"""
<div class="message {"staff" if msg.is_staff else "user"}">
<div class="msg-header">
<span class="author">{html.escape(msg.author_name)}</span>
{badge}
<span class="timestamp">{ts}</span>
</div>
<div class="msg-content">{content}</div>
{att_html}
</div>
"""
if not messages_html:
messages_html = '<div class="empty">Aucun message.</div>'
return f"""<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Transcript — Ticket #{ticket.channel_id}</title>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background: #0b0d11;
color: #dcddde;
padding: 20px;
}}
.header {{
background: #1a1d23;
border: 1px solid #2f3136;
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
text-align: center;
}}
.header h1 {{
font-size: 22px;
color: #fff;
margin-bottom: 8px;
}}
.header .meta {{
font-size: 13px;
color: #72767d;
}}
.header .meta span {{
background: #2f3136;
padding: 2px 10px;
border-radius: 20px;
margin: 0 4px;
}}
.messages {{
max-width: 800px;
margin: 0 auto;
}}
.message {{
padding: 12px 16px;
margin-bottom: 4px;
border-radius: 8px;
}}
.message:hover {{
background: rgba(255,255,255,0.02);
}}
.message.staff {{
border-left: 3px solid #57f287;
}}
.message.user {{
border-left: 3px solid #5865f2;
}}
.msg-header {{
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}}
.author {{
font-weight: 600;
font-size: 14px;
color: #fff;
}}
.badge {{
font-size: 10px;
font-weight: 700;
padding: 2px 6px;
border-radius: 4px;
text-transform: uppercase;
}}
.badge-staff {{ background: #57f287; color: #000; }}
.timestamp {{
font-size: 11px;
color: #4f545c;
margin-left: auto;
}}
.msg-content {{
font-size: 14px;
line-height: 1.5;
color: #dcddde;
white-space: pre-wrap;
word-break: break-word;
margin-left: 0;
}}
.attachment {{
margin-top: 6px;
}}
.attachment img {{
max-width: 400px;
max-height: 300px;
border-radius: 8px;
border: 1px solid #2f3136;
}}
.attachment a {{ color: #00aff4; text-decoration: none; }}
.empty {{
text-align: center;
color: #72767d;
padding: 40px;
}}
.footer {{
text-align: center;
color: #4f545c;
font-size: 11px;
margin-top: 32px;
padding: 16px;
border-top: 1px solid #2f3136;
}}
</style>
</head>
<body>
<div class="header">
<h1>Ticket #{ticket.channel_id}</h1>
<div class="meta">
<span>Categorie: {html.escape(ticket.category_id)}</span>
<span>User: {ticket.user_id}</span>
<span>Cree: {_format_ts(ticket.created_at)}</span>
{f"<span>Ferme: {_format_ts(ticket.closed_at)}</span>" if ticket.closed_at else ""}
<span>Genere: {now}</span>
</div>
</div>
<div class="messages">{messages_html}</div>
<div class="footer">Vision Studio — Transcript</div>
</body>
</html>"""