Skip to content

Commit e1dd77a

Browse files
Add Webhook Support (#16)
* Add Webhook Support * Make HTTP Request Async * Fix error message grammar
1 parent 25efed7 commit e1dd77a

7 files changed

Lines changed: 182 additions & 3 deletions

File tree

src/main/java/simplexity/simplepms/SimplePMs.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
import simplexity.simplepms.commands.SocialSpy;
1515
import simplexity.simplepms.commands.Unblock;
1616
import simplexity.simplepms.config.ConfigHandler;
17+
import simplexity.simplepms.hooks.DiscordWebHook;
1718
import simplexity.simplepms.listeners.JoinListener;
1819
import simplexity.simplepms.listeners.PreCommandListener;
20+
import simplexity.simplepms.listeners.PrivateMessageListener;
1921
import simplexity.simplepms.listeners.QuitListener;
2022
import simplexity.simplepms.logic.Constants;
2123
import simplexity.simplepms.saving.SqlHandler;
@@ -47,6 +49,7 @@ private void registerListeners() {
4749
getServer().getPluginManager().registerEvents(new QuitListener(), this);
4850
getServer().getPluginManager().registerEvents(new PreCommandListener(), this);
4951
getServer().getPluginManager().registerEvents(new JoinListener(), this);
52+
getServer().getPluginManager().registerEvents(new PrivateMessageListener(), this);
5053
}
5154

5255
private void loadConfigStuff() {
@@ -91,6 +94,7 @@ private void registerPermissions() {
9194
@Override
9295
public void onDisable() {
9396
SqlHandler.getInstance().shutdownConnection();
97+
DiscordWebHook.removeClient();
9498
}
9599

96100
public static MiniMessage getMiniMessage() {

src/main/java/simplexity/simplepms/config/ConfigHandler.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
import org.bukkit.configuration.file.FileConfiguration;
77
import org.jetbrains.annotations.NotNull;
88
import simplexity.simplepms.SimplePMs;
9+
import simplexity.simplepms.hooks.DiscordWebHook;
910

11+
import java.net.URI;
1012
import java.util.ArrayList;
1113
import java.util.HashSet;
1214
import java.util.List;
15+
import java.util.logging.Level;
1316
import java.util.logging.Logger;
1417

1518
public class ConfigHandler {
@@ -22,12 +25,13 @@ public static ConfigHandler getInstance() {
2225

2326
private final Logger logger = SimplePMs.getInstance().getLogger();
2427
private boolean mysqlEnabled, playersSendToConsole, playersSendToHiddenPlayers, consoleHasSocialSpy,
25-
commandSpyEnabled, consoleHasCommandSpy, receiveSoundEnabled, sendSoundEnabled, spySoundEnabled;
28+
commandSpyEnabled, consoleHasCommandSpy, receiveSoundEnabled, sendSoundEnabled, spySoundEnabled, webhookEnabled;
2629
private NamespacedKey receiveSound = Registry.SOUNDS.getKey(Sound.BLOCK_NOTE_BLOCK_XYLOPHONE);
2730
private NamespacedKey sendSound = Registry.SOUNDS.getKey(Sound.ENTITY_ALLAY_ITEM_THROWN);
2831
private NamespacedKey spySound = Registry.SOUNDS.getKey(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM);
2932
private float receivePitch, receiveVolume, sendPitch, sendVolume, spyPitch, spyVolume;
30-
private String mysqlIp, mysqlName, mysqlUsername, mysqlPassword, normalFormat, socialSpyFormat;
33+
private String mysqlIp, mysqlName, mysqlUsername, mysqlPassword, normalFormat, socialSpyFormat, webhookBody;
34+
private URI webhookUri;
3135
private final List<String> validNamesForConsole = new ArrayList<>();
3236
private final HashSet<String> commandsToSpy = new HashSet<>();
3337

@@ -54,9 +58,11 @@ public void loadConfigValues() {
5458
receiveSoundEnabled = config.getBoolean("sounds.received.enabled", false);
5559
sendSoundEnabled = config.getBoolean("sounds.sent.enabled", false);
5660
spySoundEnabled = config.getBoolean("sounds.spy.enabled", false);
61+
webhookEnabled = config.getBoolean("webhook.enabled", false);
5762
if (receiveSoundEnabled) loadReceiveSoundInfo(config);
5863
if (sendSoundEnabled) loadSendSoundInfo(config);
5964
if (spySoundEnabled) loadSpySoundInfo(config);
65+
if (webhookEnabled) loadWebhook(config);
6066
}
6167

6268
private void updateHashSet(HashSet<String> set, List<String> list) {
@@ -85,6 +91,25 @@ private void loadSpySoundInfo(FileConfiguration config) {
8591
spyVolume = getValidFloat(config.getDouble("sounds.spy.volume", 0.5));
8692
}
8793

94+
private void loadWebhook(FileConfiguration config) {
95+
webhookBody = config.getString("webhook.json-body", null);
96+
String uri = config.getString("webhook.url", null);
97+
if (uri == null || uri.isBlank()) {
98+
logger.warning(LocaleMessage.LOG_ERROR_WEBHOOK_URL_BLANK.getMessage());
99+
webhookEnabled = false;
100+
return;
101+
}
102+
try {
103+
webhookUri = URI.create(uri);
104+
}
105+
catch (IllegalArgumentException e) {
106+
logger.log(Level.WARNING, LocaleMessage.LOG_ERROR_WEBHOOK_URL_MALFORMED.getMessage(), e);
107+
webhookEnabled = false;
108+
return;
109+
}
110+
if (webhookEnabled) DiscordWebHook.createClient();
111+
}
112+
88113
private NamespacedKey getValidSound(String soundString, NamespacedKey defaultSound) {
89114
NamespacedKey key = NamespacedKey.fromString(soundString);
90115
if (key == null || Registry.SOUNDS.get(key) == null) {
@@ -219,4 +244,10 @@ public float getSpyPitch() {
219244
public float getSpyVolume() {
220245
return spyVolume;
221246
}
247+
248+
public boolean isWebhookEnabled() { return webhookEnabled; }
249+
250+
public URI getWebhookUri() { return webhookUri; }
251+
252+
public String getWebhookBody() { return webhookBody; }
222253
}

src/main/java/simplexity/simplepms/config/LocaleMessage.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ public enum LocaleMessage {
3737
"https://jd.papermc.io/paper/1.21.5/io/papermc/paper/registry/keys/SoundEventKeys.html"),
3838
LOG_ERROR_USING_DEFAULT_SOUND("console-error.using-default-sound", "Using %default-sound% until a valid sound is provided"),
3939
LOG_ERROR_FLOAT_OUT_OF_RANGE("console-error.float-out-of-range", "The number %number% is out of range! Volume and pitch values must be a number between 0 and 2!"),
40-
LOG_ERROR_USING_DEFAULT_FLOAT("console-error.using-default-float", "Setting to 1.0 until a valid value is provided");
40+
LOG_ERROR_USING_DEFAULT_FLOAT("console-error.using-default-float", "Setting to 1.0 until a valid value is provided"),
41+
LOG_ERROR_WEBHOOK_URL_BLANK("console-error.webhook.url.blank", "Webhook URL cannot be null or blank. Disabling webhooks."),
42+
LOG_ERROR_WEBHOOK_URL_MALFORMED("console-error.webhook.url.malformed", "Webhook URL is malformed and does not meet expected standards. Disabling webhooks."),
43+
LOG_ERROR_WEBHOOK_CLIENT_NOT_CREATED("console-error.webhook.client-not-created", "HTTP Client was not created, this should be impossible. Let Simplexity know what happened.");
4144

4245

4346
private final String path;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package simplexity.simplepms.hooks;
2+
3+
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
4+
import org.bukkit.command.CommandSender;
5+
import org.bukkit.entity.Player;
6+
import simplexity.simplepms.SimplePMs;
7+
import simplexity.simplepms.config.ConfigHandler;
8+
import simplexity.simplepms.config.LocaleMessage;
9+
import simplexity.simplepms.logic.MessageUtils;
10+
11+
import java.net.URI;
12+
import java.net.http.HttpClient;
13+
import java.net.http.HttpRequest;
14+
import java.net.http.HttpResponse;
15+
import java.util.Map;
16+
import java.util.concurrent.CompletableFuture;
17+
import java.util.logging.Level;
18+
19+
public class DiscordWebHook {
20+
21+
public static HttpClient client;
22+
23+
public static void sendWebHook(CommandSender sender, CommandSender recipient, String message) {
24+
25+
if (client == null) {
26+
SimplePMs.getInstance().getLogger().log(Level.WARNING, LocaleMessage.LOG_ERROR_WEBHOOK_CLIENT_NOT_CREATED.getMessage());
27+
createClient();
28+
}
29+
30+
String content = MessageUtils.getInstance().format(
31+
ConfigHandler.getInstance().getWebhookBody(),
32+
Map.of(
33+
"sender", sender.getName(),
34+
"sender_display_name", (sender instanceof Player player) ? PlainTextComponentSerializer.plainText().serialize(player.displayName()) : sender.getName(),
35+
"sender_uuid", (sender instanceof Player player) ? player.getUniqueId().toString() : "",
36+
"recipient", recipient.getName(),
37+
"recipient_display_name", (recipient instanceof Player player) ? PlainTextComponentSerializer.plainText().serialize(player.displayName()) : recipient.getName(),
38+
"recipient_uuid", (recipient instanceof Player player) ? player.getUniqueId().toString() : "",
39+
"message", message,
40+
"timestamp", Long.toString(System.currentTimeMillis()/1000)
41+
)
42+
);
43+
44+
try {
45+
HttpRequest request = HttpRequest.newBuilder()
46+
.uri(ConfigHandler.getInstance().getWebhookUri())
47+
.header("Content-Type", "application/json")
48+
.POST(HttpRequest.BodyPublishers.ofString(content))
49+
.build();
50+
51+
CompletableFuture<HttpResponse<String>> asyncResponse = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
52+
asyncResponse.thenAccept(response -> {
53+
if (response.statusCode() < 200 || response.statusCode() >= 300) {
54+
//noinspection StringTemplateMigration: String Template is considered preview and may be removed in a future release.
55+
SimplePMs.getInstance().getLogger().log(Level.WARNING, "Webhook has failed to send, HTTP Status " + response.statusCode() + " with JSON Body:\n" + response.body());
56+
}
57+
});
58+
}
59+
catch (Exception e) {
60+
e.printStackTrace();
61+
}
62+
}
63+
64+
public static void createClient() {
65+
removeClient();
66+
client = HttpClient.newHttpClient();
67+
}
68+
69+
public static void removeClient() {
70+
if (client == null) return;
71+
client.close();
72+
client = null;
73+
}
74+
75+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package simplexity.simplepms.listeners;
2+
3+
import org.bukkit.event.EventHandler;
4+
import org.bukkit.event.EventPriority;
5+
import org.bukkit.event.Listener;
6+
import simplexity.simplepms.config.ConfigHandler;
7+
import simplexity.simplepms.events.PrivateMessageEvent;
8+
import simplexity.simplepms.hooks.DiscordWebHook;
9+
10+
public class PrivateMessageListener implements Listener {
11+
12+
@EventHandler(priority = EventPriority.MONITOR)
13+
public void onPrivateMessage(PrivateMessageEvent event) {
14+
if (!ConfigHandler.getInstance().isWebhookEnabled()) return;
15+
DiscordWebHook.sendWebHook(event.getInitiator(), event.getRecipient(), event.getMessageContent());
16+
}
17+
18+
}

src/main/java/simplexity/simplepms/logic/MessageUtils.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@
1414
import simplexity.simplepms.config.ConfigHandler;
1515
import simplexity.simplepms.config.LocaleMessage;
1616

17+
import java.util.Map;
18+
import java.util.regex.Matcher;
19+
import java.util.regex.Pattern;
20+
1721
public class MessageUtils {
1822
private static MessageUtils instance;
1923
private final MiniMessage miniMessage = SimplePMs.getMiniMessage();
24+
private final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{([a-zA-Z0-9_]+)}");
2025

2126
private MessageUtils() {
2227
}
@@ -87,4 +92,24 @@ public TagResolver papiTag(final Player player) {
8792
});
8893
}
8994

95+
public String format(String message, Map<String, String> values) {
96+
Matcher matcher = PLACEHOLDER_PATTERN.matcher(message);
97+
StringBuilder result = new StringBuilder();
98+
99+
int lastEnd = 0;
100+
while (matcher.find()) {
101+
result.append(message, lastEnd, matcher.start());
102+
103+
String key = matcher.group(1);
104+
String value = values.get(key);
105+
106+
if (value != null) result.append(value);
107+
else result.append(matcher.group());
108+
lastEnd = matcher.end();
109+
}
110+
result.append(message, lastEnd, message.length());
111+
112+
return result.toString();
113+
}
114+
90115
}

src/main/resources/config.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,26 @@ sounds:
5050
sound: minecraft:entity.item_frame.rotate_item
5151
pitch: 1.8
5252
volume: 0.5
53+
###
54+
# This adds Webhook Support focused for Discord
55+
###
56+
webhook:
57+
enabled: false
58+
url: ""
59+
# This is the JSON Body to send to the webhook.
60+
# The default value is modeled after https://docs.discord.com/developers/resources/webhook#execute-webhook
61+
# These are the placeholders that can be used:
62+
# <sender>: Sender's Username
63+
# <sender_display_name>: Sender's Display Name (Nickname), Non-players will have the same name as <sender>
64+
# <sender_uuid>: Sender's UUID, formatted as xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, this is blank for non-players.
65+
# <recipient>: Recipient's Username
66+
# <recipient_display_name>: Recipient's Display Name (Nickname), Non-players will have the same name as <recipient>
67+
# <recipient_uuid>: Recipient's UUID, formatted as xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, this is blank for non-players.
68+
# <message>: The message sent through SimplePMs.
69+
# <timestamp>: Unix Timestamp (https://www.unixtimestamp.com/)
70+
json-body: |
71+
{
72+
"content": "{message}\n-# {sender_display_name} ({sender}) sent {recipient_display_name} ({recipient}) this on <t:{timestamp}>",
73+
"avatar_url": "https://mc-heads.net/avatar/{sender_uuid}",
74+
"username": "{sender} > {recipient}"
75+
}

0 commit comments

Comments
 (0)