Ticket System

Sistema de tickets de suporte com workflow completo

Funcionalidades

  • Criação de tickets via comando
  • Modal para descrever o problema
  • Criação automática de canal privado
  • Transições de estado (aberto/resolvido/fechado)

Código

typescript
1import {
2 FlowRegistry, FlowEngine, MemoryStore, createServer,
3 Container, Section, TextDisplay, Button, ActionRow,
4 DiscordAPI, ChannelType
5} from 'discord-flow';
6
7const registry = new FlowRegistry();
8const engine = new FlowEngine(registry, new MemoryStore());
9const discord = new DiscordAPI({ token: process.env.DISCORD_TOKEN! });
10
11registry.define('tickets', (flow) => {
12 flow.start('idle');
13
14 // Comando para abrir ticket
15 flow.state('idle')
16 .on.command({
17 name: 'ticket',
18 description: 'Abre um ticket de suporte'
19 }, () => ({
20 response: {
21 flags: 64,
22 content: 'Clique no botão para abrir um ticket:',
23 components: [
24 ActionRow({
25 components: [
26 Button({
27 customId: 'open_ticket',
28 label: 'Abrir Ticket',
29 style: 1
30 })
31 ]
32 })
33 ]
34 }
35 }));
36
37 // Botão para abrir ticket
38 flow.state('idle')
39 .on.button('open_ticket', () => ({
40 response: {
41 type: 9, // Modal
42 data: {
43 custom_id: 'ticket_modal',
44 title: 'Novo Ticket',
45 components: [{
46 type: 1,
47 components: [{
48 type: 4,
49 custom_id: 'description',
50 label: 'Descreva seu problema',
51 style: 2,
52 placeholder: 'Seja detalhado...',
53 required: true
54 }]
55 }]
56 }
57 }
58 }));
59
60 // Submissão do modal
61 flow.state('idle')
62 .on.modal('ticket_modal', async (ctx) => {
63 const description = ctx.interaction.data
64 ?.components?.[0]?.components?.[0]?.value;
65
66 // Criar canal privado
67 const channel = await discord.createChannel(ctx.guild!.id, {
68 name: `ticket-${ctx.user.id.slice(-4)}`,
69 type: ChannelType.GuildText,
70 permissionOverwrites: [
71 {
72 id: ctx.guild!.id,
73 type: 0,
74 deny: '1024' // VIEW_CHANNEL
75 },
76 {
77 id: ctx.user.id,
78 type: 1,
79 allow: '1024'
80 }
81 ]
82 });
83
84 // Enviar mensagem no canal
85 await discord.sendMessage(channel.id, {
86 components: [
87 Container({
88 accentColor: 0xFFA500,
89 components: [
90 TextDisplay({ content: '# Ticket Aberto' }),
91 TextDisplay({ content: `**Usuário:** <@${ctx.user.id}>` }),
92 TextDisplay({ content: `**Problema:**\n${description}` }),
93 ActionRow({
94 components: [
95 Button({ customId: 'resolve', label: 'Resolver', style: 3 }),
96 Button({ customId: 'close', label: 'Fechar', style: 4 })
97 ]
98 })
99 ]
100 })
101 ]
102 });
103
104 return {
105 response: {
106 flags: 64,
107 content: `Ticket criado em <#${channel.id}>`
108 },
109 transition: 'open'
110 };
111 });
112
113 // Estado: Ticket aberto
114 flow.state('open')
115 .on.button('resolve', (ctx) => ({
116 response: {
117 content: 'Ticket marcado como resolvido!',
118 components: [
119 Container({
120 accentColor: 0x1EBE38,
121 components: [
122 TextDisplay({ content: '# Ticket Resolvido' }),
123 ActionRow({
124 components: [
125 Button({ customId: 'close', label: 'Fechar', style: 2 })
126 ]
127 })
128 ]
129 })
130 ]
131 },
132 transition: 'resolved'
133 }));
134
135 // Estado: Resolvido
136 flow.state('resolved')
137 .on.button('close', async (ctx) => {
138 // Deletar canal após 5 segundos
139 setTimeout(async () => {
140 await discord.deleteChannel(ctx.channel.id);
141 }, 5000);
142
143 return {
144 response: {
145 content: 'Canal será fechado em 5 segundos...'
146 },
147 transition: 'idle'
148 };
149 });
150});
151
152createServer({
153 publicKey: process.env.DISCORD_PUBLIC_KEY!,
154 token: process.env.DISCORD_TOKEN!,
155 engine
156}).start();