feat(ticket): rewrite ticket system from scratch inspired by Kuby
All checks were successful
ci / ci (push) Successful in 46s
All checks were successful
ci / ci (push) Successful in 46s
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
This commit is contained in:
parent
4d2be145aa
commit
57b6f09dd7
10 changed files with 905 additions and 943 deletions
119
tickets/data.py
Normal file
119
tickets/data.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
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__})
|
||||
Loading…
Add table
Add a link
Reference in a new issue