Skip to content

Commit 8eb77d4

Browse files
committed
Hotfix v1.2.0 confession and stat events
1 parent b6b71e1 commit 8eb77d4

10 files changed

Lines changed: 180 additions & 75 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ All notable changes to ZDiscord are documented here.
1818
- `ColorUtil.toDiscordMarkdown()` for `&l`, `&o`, `&n`, `&m` conversion.
1919
- `FollowModule` — in-memory cache with non-blocking DM dispatch.
2020
- `PlayerProfileBuilder` for profile card embeds with Discord username resolution.
21+
- In-game `/confess <message>` command for anonymous confessions.
2122

2223
### Changed
2324
- `config-version` bumped to 4. Default avatar changed to mc-heads.net.
@@ -36,6 +37,9 @@ All notable changes to ZDiscord are documented here.
3637
- `MySQLStorage.isFollowing` uses `SELECT COUNT(*)` instead of fetching all followers.
3738
- Profile card shows actual Discord username instead of raw ID.
3839
- Confession handles use monotonic counter instead of `hash % 10000`.
40+
- Confession embeds use a real love-letter emoji instead of a Discord shortcode.
41+
- Stat update events match the calling thread, fixing Paper quit-event crashes.
42+
- JDA SLF4J provider packaging fixed so startup does not use the fallback logger.
3943
- `/panel` no longer crashes in thread or forum channels.
4044
- `UpdateChecker` Discord announcement retries on failure.
4145

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ User-facing strings live in `messages.yml`. They accept `&` colour codes and the
109109
| `/zdiscord dump` | `zdiscord.admin` | Write a diagnostics file |
110110
| `/discord` | `zdiscord.discord` | Show the Discord invite link |
111111
| `/sc [message]` | `zdiscord.staffchat` | Send to staff chat (toggle with no message) |
112+
| `/confess <message>` | `zdiscord.confess` | Post an anonymous confession |
112113

113114
### Discord slash commands
114115

pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,6 @@
214214
<pattern>gnu.trove</pattern>
215215
<shadedPattern>dev.demonz.zdiscord.libs.trove</shadedPattern>
216216
</relocation>
217-
<relocation>
218-
<pattern>org.slf4j</pattern>
219-
<shadedPattern>dev.demonz.zdiscord.libs.slf4j</shadedPattern>
220-
</relocation>
221217
</relocations>
222218
<filters>
223219
<filter>

src/main/java/dev/demonz/zdiscord/ZDiscord.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import dev.demonz.zdiscord.discord.WebhookManager;
99
import dev.demonz.zdiscord.api.ZDiscordAPIImpl;
1010
import dev.demonz.zdiscord.api.ZDiscordProvider;
11+
import dev.demonz.zdiscord.minecraft.commands.ConfessCommand;
1112
import dev.demonz.zdiscord.minecraft.commands.DiscordCommand;
1213
import dev.demonz.zdiscord.minecraft.commands.StaffChatCommand;
1314
import dev.demonz.zdiscord.minecraft.commands.ZDiscordCommand;
@@ -20,6 +21,7 @@
2021
import dev.demonz.zdiscord.minecraft.listeners.PaperStaffChatListener;
2122
import dev.demonz.zdiscord.modules.AntiRaidModule;
2223
import dev.demonz.zdiscord.modules.CommandLoggerModule;
24+
import dev.demonz.zdiscord.modules.ConfessionModule;
2325
import dev.demonz.zdiscord.modules.ConsoleModule;
2426
import dev.demonz.zdiscord.modules.EmbedBuilderModule;
2527
import dev.demonz.zdiscord.modules.FollowModule;
@@ -70,6 +72,7 @@ public class ZDiscord extends JavaPlugin {
7072
private VoiceStatusModule voiceStatusModule;
7173
private ConsoleModule consoleModule;
7274
private FollowModule followModule;
75+
private ConfessionModule confessionModule;
7376

7477
@Override
7578
public void onEnable() {
@@ -93,6 +96,7 @@ public void onEnable() {
9396
new org.bstats.bukkit.Metrics(this, 29652);
9497

9598
botManager = new BotManager(this);
99+
confessionModule = new ConfessionModule(this);
96100
slashCommandManager = new SlashCommandManager(this);
97101
setupCommand = new SetupCommand(this);
98102
if (!botManager.connect()) {
@@ -299,6 +303,10 @@ private void registerCommands() {
299303
StaffChatCommand executor = new StaffChatCommand(this);
300304
sc.setExecutor(executor);
301305
}
306+
org.bukkit.command.PluginCommand confess = getCommand("confess");
307+
if (confess != null) {
308+
confess.setExecutor(new ConfessCommand(this));
309+
}
302310
}
303311

304312
public void reload() {
@@ -410,6 +418,10 @@ public FollowModule getFollowModule() {
410418
return followModule;
411419
}
412420

421+
public ConfessionModule getConfessionModule() {
422+
return confessionModule;
423+
}
424+
413425
public void debug(String message) {
414426
ZLogger.debug(ZLogger.Category.SYSTEM, message);
415427
}

src/main/java/dev/demonz/zdiscord/api/events/ZDiscordStatUpdateEvent.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ public class ZDiscordStatUpdateEvent extends Event implements Cancellable {
1818

1919
public ZDiscordStatUpdateEvent(UUID playerUUID, String stat,
2020
long oldValue, long newValue) {
21-
super(true);
21+
this(playerUUID, stat, oldValue, newValue, false);
22+
}
23+
24+
public ZDiscordStatUpdateEvent(UUID playerUUID, String stat,
25+
long oldValue, long newValue,
26+
boolean async) {
27+
super(async);
2228
this.playerUUID = playerUUID;
2329
this.stat = stat;
2430
this.oldValue = oldValue;

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

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,13 @@
2121

2222
import java.time.Instant;
2323
import java.util.UUID;
24-
import java.util.concurrent.ConcurrentHashMap;
25-
import java.util.concurrent.atomic.AtomicInteger;
2624
import java.util.stream.Collectors;
2725

2826

2927
public class SlashCommandManager extends ListenerAdapter {
3028

3129
private final ZDiscord plugin;
3230

33-
34-
private final ConcurrentHashMap<String, Long> confessionCooldowns = new ConcurrentHashMap<>();
35-
36-
37-
private final AtomicInteger confessionCounter = new AtomicInteger(0);
38-
3931
public SlashCommandManager(ZDiscord plugin) {
4032
this.plugin = plugin;
4133
}
@@ -480,66 +472,7 @@ private void handleUnfollow(SlashCommandInteractionEvent event) {
480472
}
481473

482474
private void handleConfess(SlashCommandInteractionEvent event) {
483-
String channelId = plugin.getConfigManager()
484-
.getString("channels.confessions", "").trim();
485-
if (channelId.isEmpty() || channelId.startsWith("YOUR_")) {
486-
event.reply(":lock: Confessions are disabled on this server "
487-
+ "(no `channels.confessions` is configured).")
488-
.setEphemeral(true).queue();
489-
return;
490-
}
491-
var channel = plugin.getBotManager().getJda()
492-
.getTextChannelById(channelId);
493-
if (channel == null) {
494-
event.reply(":lock: Confessions are disabled on this server "
495-
+ "(the configured channel could not be found).")
496-
.setEphemeral(true).queue();
497-
return;
498-
}
499-
500-
501-
long cooldownMs = plugin.getConfigManager()
502-
.getInt("confessions.cooldown", 300) * 1000L;
503-
String userId = event.getUser().getId();
504-
long now = System.currentTimeMillis();
505-
Long lastConfession = confessionCooldowns.get(userId);
506-
if (lastConfession != null && (now - lastConfession) < cooldownMs) {
507-
long remaining = (cooldownMs - (now - lastConfession)) / 1000;
508-
event.reply(":clock1: You can confess again in " + remaining + " seconds.")
509-
.setEphemeral(true).queue();
510-
return;
511-
}
512-
513475
String message = event.getOption("message").getAsString();
514-
if (message.length() > 1500) {
515-
event.reply(":lock: Your confession is too long (max 1500 characters).")
516-
.setEphemeral(true).queue();
517-
return;
518-
}
519-
520-
521-
522-
523-
int handleNum = confessionCounter.incrementAndGet();
524-
String handle = "Confessor #" + handleNum;
525-
526-
String colorHex = plugin.getConfigManager()
527-
.getString("confessions.color", "#9B59B6");
528-
529-
EmbedBuilder embed = new EmbedBuilder()
530-
.setAuthor(":love_letter: A new confession", null, null)
531-
.setDescription(ColorUtil.toDiscordMarkdown(message))
532-
.setColor(ColorUtil.parseHex(colorHex))
533-
.setFooter(handle + " \u00B7 posted at", null)
534-
.setTimestamp(Instant.now());
535-
536-
channel.sendMessageEmbeds(embed.build()).queue(
537-
success -> {
538-
confessionCooldowns.put(userId, now);
539-
event.reply(":white_check_mark: Your confession was posted anonymously.")
540-
.setEphemeral(true).queue();
541-
},
542-
error -> event.reply(":x: Failed to post your confession: "
543-
+ error.getMessage()).setEphemeral(true).queue());
476+
plugin.getConfessionModule().postFromDiscord(event, message);
544477
}
545478
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package dev.demonz.zdiscord.minecraft.commands;
2+
3+
import dev.demonz.zdiscord.ZDiscord;
4+
import org.bukkit.command.Command;
5+
import org.bukkit.command.CommandExecutor;
6+
import org.bukkit.command.CommandSender;
7+
import org.bukkit.entity.Player;
8+
9+
public class ConfessCommand implements CommandExecutor {
10+
11+
private final ZDiscord plugin;
12+
13+
public ConfessCommand(ZDiscord plugin) {
14+
this.plugin = plugin;
15+
}
16+
17+
@Override
18+
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
19+
if (!(sender instanceof Player)) {
20+
sender.sendMessage(plugin.getMessageManager().get("player-only"));
21+
return true;
22+
}
23+
if (!sender.hasPermission("zdiscord.confess")) {
24+
sender.sendMessage(plugin.getMessageManager().get("no-permission"));
25+
return true;
26+
}
27+
if (args.length == 0) {
28+
sender.sendMessage("Usage: /" + label + " <message>");
29+
return true;
30+
}
31+
32+
plugin.getConfessionModule().postFromMinecraft(
33+
(Player) sender, String.join(" ", args));
34+
return true;
35+
}
36+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package dev.demonz.zdiscord.modules;
2+
3+
import dev.demonz.zdiscord.ZDiscord;
4+
import dev.demonz.zdiscord.util.ColorUtil;
5+
import net.dv8tion.jda.api.EmbedBuilder;
6+
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
7+
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
8+
import org.bukkit.entity.Player;
9+
10+
import java.time.Instant;
11+
import java.util.concurrent.ConcurrentHashMap;
12+
import java.util.concurrent.atomic.AtomicInteger;
13+
14+
public class ConfessionModule {
15+
16+
private static final String CONFESSION_TITLE = "\uD83D\uDC8C A new confession";
17+
private static final int MAX_MESSAGE_LENGTH = 1500;
18+
19+
private final ZDiscord plugin;
20+
private final ConcurrentHashMap<String, Long> cooldowns = new ConcurrentHashMap<>();
21+
private final AtomicInteger counter = new AtomicInteger(0);
22+
23+
public ConfessionModule(ZDiscord plugin) {
24+
this.plugin = plugin;
25+
}
26+
27+
public void postFromDiscord(SlashCommandInteractionEvent event, String message) {
28+
post(
29+
"discord:" + event.getUser().getId(),
30+
message,
31+
reply -> event.reply(reply).setEphemeral(true).queue());
32+
}
33+
34+
public void postFromMinecraft(Player player, String message) {
35+
post(
36+
"minecraft:" + player.getUniqueId(),
37+
message,
38+
reply -> plugin.getPlatformAdapter().runForEntity(
39+
player, () -> player.sendMessage(reply)));
40+
}
41+
42+
private void post(String cooldownKey, String message, ReplyTarget replyTarget) {
43+
if (plugin.getBotManager() == null
44+
|| plugin.getBotManager().getJda() == null
45+
|| !plugin.getBotManager().isConnected()) {
46+
replyTarget.reply("Confessions are unavailable because the Discord bot is not connected.");
47+
return;
48+
}
49+
50+
String channelId = plugin.getConfigManager()
51+
.getString("channels.confessions", "").trim();
52+
if (channelId.isEmpty() || channelId.startsWith("YOUR_")) {
53+
replyTarget.reply("Confessions are disabled on this server "
54+
+ "(no channels.confessions is configured).");
55+
return;
56+
}
57+
58+
TextChannel channel = plugin.getBotManager().getJda()
59+
.getTextChannelById(channelId);
60+
if (channel == null) {
61+
replyTarget.reply("Confessions are disabled on this server "
62+
+ "(the configured channel could not be found).");
63+
return;
64+
}
65+
66+
String cleaned = message == null ? "" : message.trim();
67+
if (cleaned.isEmpty()) {
68+
replyTarget.reply("Usage: /confess <message>");
69+
return;
70+
}
71+
if (cleaned.length() > MAX_MESSAGE_LENGTH) {
72+
replyTarget.reply("Your confession is too long (max "
73+
+ MAX_MESSAGE_LENGTH + " characters).");
74+
return;
75+
}
76+
77+
long cooldownMs = plugin.getConfigManager()
78+
.getInt("confessions.cooldown", 300) * 1000L;
79+
long now = System.currentTimeMillis();
80+
Long lastConfession = cooldowns.get(cooldownKey);
81+
if (lastConfession != null && (now - lastConfession) < cooldownMs) {
82+
long remaining = Math.max(1L, (cooldownMs - (now - lastConfession) + 999L) / 1000L);
83+
replyTarget.reply("You can confess again in " + remaining + " seconds.");
84+
return;
85+
}
86+
87+
String handle = "Confessor #" + counter.incrementAndGet();
88+
String colorHex = plugin.getConfigManager()
89+
.getString("confessions.color", "#9B59B6");
90+
91+
EmbedBuilder embed = new EmbedBuilder()
92+
.setAuthor(CONFESSION_TITLE, null, null)
93+
.setDescription(ColorUtil.toDiscordMarkdown(cleaned))
94+
.setColor(ColorUtil.parseHex(colorHex))
95+
.setFooter(handle + " \u00B7 posted at", null)
96+
.setTimestamp(Instant.now());
97+
98+
channel.sendMessageEmbeds(embed.build()).queue(
99+
success -> {
100+
cooldowns.put(cooldownKey, now);
101+
replyTarget.reply("Your confession was posted anonymously.");
102+
},
103+
error -> replyTarget.reply("Failed to post your confession: "
104+
+ error.getMessage()));
105+
}
106+
107+
private interface ReplyTarget {
108+
void reply(String message);
109+
}
110+
}

src/main/java/dev/demonz/zdiscord/modules/LeaderboardModule.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public void incrementStatBy(UUID uuid, String stat, long amount) {
156156
return oldRef[0] + amount;
157157
});
158158
ZDiscordStatUpdateEvent event = new ZDiscordStatUpdateEvent(
159-
uuid, stat, oldRef[0], newValue);
159+
uuid, stat, oldRef[0], newValue, !Bukkit.isPrimaryThread());
160160
Bukkit.getPluginManager().callEvent(event);
161161
if (event.isCancelled()) {
162162
statsCache.computeIfAbsent(uuid, k -> new ConcurrentHashMap<>())
@@ -174,7 +174,7 @@ public void setStat(UUID uuid, String stat, long value) {
174174
return value;
175175
});
176176
ZDiscordStatUpdateEvent event = new ZDiscordStatUpdateEvent(
177-
uuid, stat, oldRef[0], value);
177+
uuid, stat, oldRef[0], value, !Bukkit.isPrimaryThread());
178178
Bukkit.getPluginManager().callEvent(event);
179179
if (event.isCancelled()) {
180180
statsCache.computeIfAbsent(uuid, k -> new ConcurrentHashMap<>())

src/main/resources/plugin.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ commands:
2929
usage: /<command> [message]
3030
permission: zdiscord.staffchat
3131
aliases: [staffchat]
32+
confess:
33+
description: Post an anonymous confession to the configured Discord channel
34+
usage: /<command> <message>
35+
permission: zdiscord.confess
3236

3337
permissions:
3438
zdiscord.admin:
@@ -61,3 +65,6 @@ permissions:
6165
zdiscord.staffchat:
6266
description: Access to staff chat
6367
default: op
68+
zdiscord.confess:
69+
description: Allows posting anonymous confessions
70+
default: true

0 commit comments

Comments
 (0)