VisionApp/tickets/data.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

119 lines
3.6 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
class TicketStatus(str, Enum):
OPEN = "open"
CLOSED = "closed"
@dataclass
class TicketCategory:
id: str
name: str
emoji: str = ""
description: str = ""
staff_role_id: int | None = None
discord_category_id: int | None = None
def to_dict(self) -> dict:
return {
"id": self.id,
"name": self.name,
"emoji": self.emoji,
"description": self.description,
"staff_role_id": self.staff_role_id,
"discord_category_id": self.discord_category_id,
}
@classmethod
def from_dict(cls, data: dict) -> TicketCategory:
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
@dataclass
class TicketMessage:
author_id: int
author_name: str
content: str
timestamp: str
attachments: list[str] = field(default_factory=list)
is_staff: bool = False
@dataclass
class TicketData:
channel_id: int
guild_id: int
user_id: int
category_id: str
status: TicketStatus = TicketStatus.OPEN
created_at: str = ""
closed_at: str | None = None
closed_by: int | None = None
messages: list[TicketMessage] = field(default_factory=list)
reason: str = ""
def to_dict(self) -> dict:
return {
"channel_id": self.channel_id,
"guild_id": self.guild_id,
"user_id": self.user_id,
"category_id": self.category_id,
"status": self.status.value,
"created_at": self.created_at,
"closed_at": self.closed_at,
"closed_by": self.closed_by,
"messages": [
{
"author_id": m.author_id,
"author_name": m.author_name,
"content": m.content,
"timestamp": m.timestamp,
"attachments": m.attachments,
"is_staff": m.is_staff,
}
for m in self.messages
],
"reason": self.reason,
}
@classmethod
def from_dict(cls, data: dict) -> TicketData:
messages = [TicketMessage(**m) for m in data.pop("messages", [])]
data["status"] = TicketStatus(data.get("status", "open"))
data.pop("transcript_path", None)
data.pop("rating", None)
data.pop("feedback", None)
return cls(messages=messages, **{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
@dataclass
class GuildTicketConfig:
guild_id: int
enabled: bool = False
categories: list[TicketCategory] = field(default_factory=list)
staff_role_id: int | None = None
log_channel_id: int | None = None
panel_channel_id: int | None = None
panel_message_id: int | None = None
transcript_enabled: bool = True
def to_dict(self) -> dict:
return {
"guild_id": self.guild_id,
"enabled": self.enabled,
"categories": [c.to_dict() for c in self.categories],
"staff_role_id": self.staff_role_id,
"log_channel_id": self.log_channel_id,
"panel_channel_id": self.panel_channel_id,
"panel_message_id": self.panel_message_id,
"transcript_enabled": self.transcript_enabled,
}
@classmethod
def from_dict(cls, data: dict) -> GuildTicketConfig:
categories = [TicketCategory.from_dict(c) for c in data.pop("categories", [])]
return cls(categories=categories, **{k: v for k, v in data.items() if k in cls.__dataclass_fields__})