Skip to content

Commit 3ad5922

Browse files
committed
Fix ticket setup and panel creation
1 parent c500c82 commit 3ad5922

9 files changed

Lines changed: 154 additions & 102 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ All notable changes to ZDiscord are documented here.
4040
- Confession embeds use a real love-letter emoji instead of a Discord shortcode.
4141
- Stat update events match the calling thread, fixing Paper quit-event crashes.
4242
- JDA SLF4J provider packaging fixed so startup does not use the fallback logger.
43+
- Ticket setup now uses the guided `/setup` wizard only and posts a ticket-specific setup flow.
44+
- Ticket creation ignores placeholder support-role IDs instead of aborting channel creation.
4345
- `/panel` no longer crashes in thread or forum channels.
4446
- `UpdateChecker` Discord announcement retries on failure.
4547

src/main/java/dev/demonz/zdiscord/config/ConfigManager.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.io.InputStreamReader;
1111
import java.nio.file.Files;
1212
import java.nio.file.StandardCopyOption;
13+
import java.util.ArrayList;
1314
import java.util.List;
1415
import java.util.function.Supplier;
1516
import java.util.logging.Logger;
@@ -18,7 +19,7 @@
1819
public class ConfigManager {
1920

2021

21-
public static final int CURRENT_VERSION = 5;
22+
public static final int CURRENT_VERSION = 6;
2223

2324
private final File dataFolder;
2425
private final Logger logger;
@@ -45,6 +46,7 @@ public ConfigManager(File dataFolder,
4546
saveDefaultConfig();
4647
reload();
4748
migrateIfNeeded();
49+
sanitizePlaceholders();
4850
}
4951

5052
private void saveDefaultConfig() {
@@ -168,4 +170,26 @@ public void save() {
168170
logger.severe("Failed to save config: " + e.getMessage());
169171
}
170172
}
173+
174+
private void sanitizePlaceholders() {
175+
List<String> roles = config.getStringList("tickets.support-roles");
176+
if (roles.isEmpty()) {
177+
return;
178+
}
179+
180+
List<String> clean = new ArrayList<>();
181+
boolean changed = false;
182+
for (String role : roles) {
183+
if (role == null || role.isBlank() || role.startsWith("YOUR_")) {
184+
changed = true;
185+
} else {
186+
clean.add(role);
187+
}
188+
}
189+
190+
if (changed) {
191+
config.set("tickets.support-roles", clean);
192+
save();
193+
}
194+
}
171195
}

src/main/java/dev/demonz/zdiscord/discord/SetupCommand.java

Lines changed: 35 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.demonz.zdiscord.discord;
22

33
import dev.demonz.zdiscord.ZDiscord;
4+
import dev.demonz.zdiscord.modules.TicketModule;
45
import dev.demonz.zdiscord.util.ColorUtil;
56
import dev.demonz.zdiscord.util.EmbedUtil;
67
import dev.demonz.zdiscord.util.StatusEmbedBuilder;
@@ -10,13 +11,11 @@
1011
import net.dv8tion.jda.api.entities.Role;
1112
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
1213
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
13-
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
1414
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
1515
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
1616
import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent;
1717
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
1818
import net.dv8tion.jda.api.hooks.ListenerAdapter;
19-
import net.dv8tion.jda.api.interactions.commands.Command;
2019
import net.dv8tion.jda.api.interactions.components.ActionRow;
2120
import net.dv8tion.jda.api.interactions.components.buttons.Button;
2221
import net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu;
@@ -34,7 +33,6 @@
3433
import java.util.LinkedHashMap;
3534
import java.util.List;
3635
import java.util.Map;
37-
import java.util.stream.Collectors;
3836

3937

4038
public class SetupCommand extends ListenerAdapter {
@@ -69,7 +67,7 @@ public class SetupCommand extends ListenerAdapter {
6967
"Join, quit, death, and advancement events."));
7068
MODULES.put("console", new ModuleInfo("Console", "channels.console",
7169
"Server console output streamed to Discord."));
72-
MODULES.put("tickets", new ModuleInfo("Tickets", "channels.ticket-category",
70+
MODULES.put("tickets", new ModuleInfo("Tickets", "tickets.panel-channel",
7371
"Support ticket system with private channels."));
7472
MODULES.put("staffchat", new ModuleInfo("Staff Chat", "staff-chat.channel",
7573
"Private staff communication bridge."));
@@ -83,8 +81,6 @@ public class SetupCommand extends ListenerAdapter {
8381
"Anonymous confessions via /confess."));
8482
}
8583

86-
private static final List<String> MODULE_NAMES = new ArrayList<>(MODULES.keySet());
87-
8884
public SetupCommand(ZDiscord plugin) {
8985
this.plugin = plugin;
9086
}
@@ -100,34 +96,9 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
10096
return;
10197
}
10298

103-
var moduleOption = event.getOption("module");
104-
var channelOption = event.getOption("channel");
105-
if (moduleOption != null && channelOption != null) {
106-
String module = moduleOption.getAsString();
107-
TextChannel target = channelOption.getAsChannel().asTextChannel();
108-
quickSetup(event, module, target);
109-
return;
110-
}
11199
showWizardPanel(event);
112100
}
113101

114-
@Override
115-
public void onCommandAutoCompleteInteraction(CommandAutoCompleteInteractionEvent event) {
116-
if (!"setup".equals(event.getName())) {
117-
return;
118-
}
119-
if (!"module".equals(event.getFocusedOption().getName())) {
120-
return;
121-
}
122-
String typed = event.getFocusedOption().getValue().toLowerCase();
123-
List<Command.Choice> choices = MODULE_NAMES.stream()
124-
.filter(name -> name.startsWith(typed))
125-
.map(name -> new Command.Choice(name, name))
126-
.limit(25)
127-
.collect(Collectors.toList());
128-
event.replyChoices(choices).queue();
129-
}
130-
131102
private void showWizardPanel(SlashCommandInteractionEvent event) {
132103
Guild guild = event.getGuild();
133104
String guildIcon = guild != null && guild.getIconUrl() != null
@@ -225,6 +196,11 @@ private void handleModuleSelect(StringSelectInteractionEvent event) {
225196
return;
226197
}
227198

199+
if ("tickets".equals(module)) {
200+
showTicketChannelPrompt(event);
201+
return;
202+
}
203+
228204
EmbedBuilder prompt = new EmbedBuilder()
229205
.setTitle(":gear: Configure " + capitalize(module))
230206
.setDescription(info.description
@@ -270,16 +246,16 @@ private void handleChannelSelect(EntitySelectInteractionEvent event) {
270246
}
271247
TextChannel channel = (TextChannel) selectedChannel;
272248

273-
saveToConfig(info.configPath, channel.getId());
274-
275249
switch (module) {
276250
case "status":
251+
saveToConfig(info.configPath, channel.getId());
277252
handleStatusSetup(event, channel);
278253
return;
279254
case "tickets":
280255
handleTicketSetup(event, channel);
281256
return;
282257
default:
258+
saveToConfig(info.configPath, channel.getId());
283259
postActivationNotice(channel, info, event.getUser().getName());
284260
event.replyEmbeds(EmbedUtil.success(
285261
capitalize(module) + " has been linked to " + channel.getAsMention() + ".")
@@ -289,6 +265,29 @@ private void handleChannelSelect(EntitySelectInteractionEvent event) {
289265
}
290266
}
291267

268+
private void showTicketChannelPrompt(StringSelectInteractionEvent event) {
269+
EmbedBuilder prompt = new EmbedBuilder()
270+
.setTitle(":ticket: Ticket Setup \u2014 Step 1 of 3")
271+
.setDescription("Select the text channel where ZDiscord should post the public ticket panel.\n\n"
272+
+ "Private tickets will be created in that channel's category. "
273+
+ "If the channel is not inside a category, tickets will be created at the server root.")
274+
.setColor(ColorUtil.parseHex("#5865F2"))
275+
.setFooter("Step 1/3 \u2022 Select ticket panel channel")
276+
.setTimestamp(Instant.now());
277+
278+
EntitySelectMenu channelMenu = EntitySelectMenu.create(
279+
CHANNEL_MENU_PREFIX + "tickets", EntitySelectMenu.SelectTarget.CHANNEL)
280+
.setPlaceholder("Select the ticket panel channel")
281+
.setMinValues(1)
282+
.setMaxValues(1)
283+
.build();
284+
285+
event.replyEmbeds(prompt.build())
286+
.addActionRow(channelMenu)
287+
.setEphemeral(true)
288+
.queue();
289+
}
290+
292291
private void handleStatusSetup(EntitySelectInteractionEvent event, TextChannel channel) {
293292
StatusEmbedBuilder.StatusContext ctx = StatusEmbedBuilder.StatusContext.capture(
294293
plugin.getBotManager()::getGuild,
@@ -321,6 +320,8 @@ private void handleTicketSetup(EntitySelectInteractionEvent event, TextChannel c
321320
saveToConfig("tickets.panel-channel", channel.getId());
322321
if (channel.getParentCategory() != null) {
323322
saveToConfig("channels.ticket-category", channel.getParentCategory().getId());
323+
} else {
324+
saveToConfig("channels.ticket-category", "");
324325
}
325326

326327
EmbedBuilder rolePrompt = new EmbedBuilder()
@@ -670,7 +671,7 @@ private String supportRoleMention(net.dv8tion.jda.api.events.interaction.Generic
670671
if (!list.isEmpty()) {
671672
roleId = list.get(0);
672673
}
673-
if (roleId == null) {
674+
if (!TicketModule.isUsableSnowflake(roleId)) {
674675
return "(unset)";
675676
}
676677
Guild guild = event.getGuild();
@@ -772,29 +773,6 @@ private void postTicketPanel(TextChannel channel) {
772773
plugin.getTicketModule().postPanel(channel);
773774
}
774775

775-
private void quickSetup(SlashCommandInteractionEvent event, String module, TextChannel target) {
776-
ModuleInfo info = MODULES.get(module);
777-
if (info == null) {
778-
event.reply("Unknown module: `" + module + "`. Available: "
779-
+ String.join(", ", MODULE_NAMES)).setEphemeral(true).queue();
780-
return;
781-
}
782-
783-
saveToConfig(info.configPath, target.getId());
784-
785-
if ("tickets".equals(module) && target.getParentCategory() != null) {
786-
saveToConfig("channels.ticket-category", target.getParentCategory().getId());
787-
postTicketPanel(target);
788-
}
789-
790-
postActivationNotice(target, info, event.getUser().getName());
791-
792-
event.replyEmbeds(EmbedUtil.success(
793-
capitalize(module) + " linked to " + target.getAsMention() + ".")
794-
.build())
795-
.setEphemeral(true).queue();
796-
}
797-
798776
private void postActivationNotice(TextChannel channel, ModuleInfo info, String username) {
799777
channel.sendMessageEmbeds(EmbedUtil.simple(
800778
info.label + " activated",

src/main/java/dev/demonz/zdiscord/discord/SlashCommandManager.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,7 @@ public void registerCommands() {
5252
Commands.slash("leaderboard", "View player leaderboards")
5353
.addOption(OptionType.STRING, "stat",
5454
"Stat to view (kills, deaths, playtime)", true),
55-
Commands.slash("setup", "Open the ZDiscord setup wizard")
56-
.addOption(OptionType.STRING, "module",
57-
"Quick setup: module name (chat, status, events, etc.)",
58-
false, true)
59-
.addOption(OptionType.CHANNEL, "channel",
60-
"Quick setup: target channel", false),
55+
Commands.slash("setup", "Open the ZDiscord setup wizard"),
6156
Commands.slash("profile", "View a Minecraft player's profile card")
6257
.addOption(OptionType.STRING, "player",
6358
"Player name (omit for yourself)", false),

src/main/java/dev/demonz/zdiscord/discord/listeners/TicketButtonListener.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import net.dv8tion.jda.api.Permission;
66
import net.dv8tion.jda.api.entities.Member;
77
import net.dv8tion.jda.api.entities.Message;
8+
import net.dv8tion.jda.api.entities.User;
89
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
10+
import net.dv8tion.jda.api.interactions.InteractionHook;
911
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
1012
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
1113
import net.dv8tion.jda.api.hooks.ListenerAdapter;
@@ -68,9 +70,7 @@ public void onStringSelectInteraction(StringSelectInteractionEvent event) {
6870
return;
6971
}
7072
String categoryId = event.getValues().get(0);
71-
plugin.getTicketModule().createTicketForCategory(event.getUser(), categoryId);
72-
event.reply("Your support ticket has been created. Check the new channel.")
73-
.setEphemeral(true).queue();
73+
createTicketFromInteraction(event, event.getUser(), categoryId);
7474
}
7575

7676
private void handleQuickOpen(ButtonInteractionEvent event) {
@@ -84,9 +84,28 @@ private void handleQuickOpen(ButtonInteractionEvent event) {
8484
.setEphemeral(true).queue();
8585
return;
8686
}
87-
plugin.getTicketModule().createTicketForCategory(event.getUser(), defaultId);
88-
event.reply("Your support ticket has been created. Check the new channel.")
89-
.setEphemeral(true).queue();
87+
createTicketFromInteraction(event, event.getUser(), defaultId);
88+
}
89+
90+
private void createTicketFromInteraction(
91+
net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent event,
92+
User user,
93+
String categoryId) {
94+
if (event instanceof ButtonInteractionEvent) {
95+
((ButtonInteractionEvent) event).deferReply(true).queue(
96+
hook -> completeTicketCreate(hook, user, categoryId));
97+
} else if (event instanceof StringSelectInteractionEvent) {
98+
((StringSelectInteractionEvent) event).deferReply(true).queue(
99+
hook -> completeTicketCreate(hook, user, categoryId));
100+
}
101+
}
102+
103+
private void completeTicketCreate(InteractionHook hook, User user, String categoryId) {
104+
plugin.getPlatformAdapter().runAsync(() -> {
105+
String message = plugin.getTicketModule()
106+
.createTicketForCategory(user, categoryId);
107+
hook.sendMessage(message).queue();
108+
});
90109
}
91110

92111
private void handleClose(ButtonInteractionEvent event) {

0 commit comments

Comments
 (0)