-
Notifications
You must be signed in to change notification settings - Fork 0
Make Discord the flagship notification method for player joins #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
cf0bf22
79849e9
8753698
8eb38e5
8db0ae1
6772770
ca99f22
d7f7b4f
48db8e0
78b0f9a
1ef2a36
cc7aa76
d89a7c5
0843eed
3de83b1
4b7a776
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| name: Run Tests | ||
|
|
||
| on: | ||
| push: | ||
| pull_request: | ||
|
|
||
| jobs: | ||
| test: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up JDK 21 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: '21' | ||
| distribution: 'temurin' | ||
| cache: 'gradle' | ||
|
|
||
| - name: Run tests with Gradle | ||
| run: gradle test --no-daemon | ||
|
|
||
| - name: Publish test results | ||
| uses: EnricoMi/publish-unit-test-result-action@v2 | ||
| if: always() | ||
| with: | ||
| files: | | ||
| build/test-results/**/*.xml | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -112,4 +112,8 @@ buildNumber.properties | |
| # Common working directory | ||
| run/ | ||
|
|
||
| .env | ||
| .env | ||
|
|
||
| # Gradle | ||
| .gradle/ | ||
| build/ | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.dansplugins.herald; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.OutputStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.net.HttpURLConnection; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.net.URL; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.charset.StandardCharsets; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class DiscordNotifier { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final String webhookUrl; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public DiscordNotifier(String webhookUrl) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.webhookUrl = webhookUrl; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Send a message to Discord via webhook | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param content The message content to send | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @throws IOException if there's an error sending the message | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void sendMessage(String content) throws IOException { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (webhookUrl == null || webhookUrl.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new IllegalArgumentException("Discord webhook URL is not configured"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| URL url = new URL(webhookUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connection.setRequestMethod("POST"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connection.setRequestProperty("Content-Type", "application/json"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connection.setDoOutput(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create JSON payload with the message content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String jsonPayload = String.format("{\"content\": \"%s\"}", escapeJson(content)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try (OutputStream os = connection.getOutputStream()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.write(input, 0, input.length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int responseCode = connection.getResponseCode(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (responseCode < 200 || responseCode >= 300) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new IOException("Discord webhook returned error code: " + responseCode); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+47
to
+64
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connection.setDoOutput(true); | |
| // Create JSON payload with the message content | |
| String jsonPayload = String.format("{\"content\": \"%s\"}", escapeJson(content)); | |
| try (OutputStream os = connection.getOutputStream()) { | |
| byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8); | |
| os.write(input, 0, input.length); | |
| } | |
| int responseCode = connection.getResponseCode(); | |
| if (responseCode < 200 || responseCode >= 300) { | |
| throw new IOException("Discord webhook returned error code: " + responseCode); | |
| } | |
| connection.setConnectTimeout(5000); // 5 seconds connect timeout | |
| connection.setReadTimeout(10000); // 10 seconds read timeout | |
| connection.setDoOutput(true); | |
| // Create JSON payload with the message content | |
| String jsonPayload = String.format("{\"content\": \"%s\"}", escapeJson(content)); | |
| try { | |
| try (OutputStream os = connection.getOutputStream()) { | |
| byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8); | |
| os.write(input, 0, input.length); | |
| } | |
| int responseCode = connection.getResponseCode(); | |
| if (responseCode < 200 || responseCode >= 300) { | |
| throw new IOException("Discord webhook returned error code: " + responseCode); | |
| } | |
| } finally { | |
| if (connection != null) { | |
| connection.disconnect(); | |
| } | |
| } |
Outdated
Copilot
AI
Mar 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current JSON escaping only handles backslash, quote, and a few whitespace controls. JSON strings must also escape other control characters (e.g., backspace/formfeed and any chars < 0x20 via \uXXXX), otherwise the payload can become invalid JSON if such characters appear in content. Consider implementing complete JSON string escaping here.
| return text.replace("\\", "\\\\") | |
| .replace("\"", "\\\"") | |
| .replace("\n", "\\n") | |
| .replace("\r", "\\r") | |
| .replace("\t", "\\t"); | |
| StringBuilder sb = new StringBuilder(text.length()); | |
| for (int i = 0; i < text.length(); i++) { | |
| char c = text.charAt(i); | |
| switch (c) { | |
| case '"': | |
| sb.append("\\\""); | |
| break; | |
| case '\\': | |
| sb.append("\\\\"); | |
| break; | |
| case '\b': | |
| sb.append("\\b"); | |
| break; | |
| case '\f': | |
| sb.append("\\f"); | |
| break; | |
| case '\n': | |
| sb.append("\\n"); | |
| break; | |
| case '\r': | |
| sb.append("\\r"); | |
| break; | |
| case '\t': | |
| sb.append("\\t"); | |
| break; | |
| default: | |
| if (c < 0x20) { | |
| sb.append("\\u00"); | |
| String hex = Integer.toHexString(c); | |
| if (hex.length() == 1) { | |
| sb.append('0'); | |
| } | |
| sb.append(hex); | |
| } else { | |
| sb.append(c); | |
| } | |
| break; | |
| } | |
| } | |
| return sb.toString(); |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,9 @@ public final class Herald extends JavaPlugin implements Listener { | |||||||||
| private String smtpPassword; | ||||||||||
| private String emailSender; | ||||||||||
| private boolean useTLS; | ||||||||||
| private boolean discordEnabled; | ||||||||||
| private String discordWebhookUrl; | ||||||||||
| private DiscordNotifier discordNotifier; | ||||||||||
|
|
||||||||||
| @Override | ||||||||||
| public void onEnable() { | ||||||||||
|
|
@@ -52,6 +55,16 @@ private void loadConfiguration() { | |||||||||
| smtpPassword = getConfig().getString("smtp.password"); | ||||||||||
| emailSender = getConfig().getString("email.sender"); | ||||||||||
| useTLS = getConfig().getBoolean("smtp.use-tls", true); | ||||||||||
|
|
||||||||||
| // Load Discord settings | ||||||||||
| discordEnabled = getConfig().getBoolean("discord.enabled", false); | ||||||||||
| discordWebhookUrl = getConfig().getString("discord.webhook-url"); | ||||||||||
|
|
||||||||||
| // Initialize Discord notifier if enabled | ||||||||||
| if (discordEnabled && discordWebhookUrl != null && !discordWebhookUrl.isEmpty()) { | ||||||||||
| discordNotifier = new DiscordNotifier(discordWebhookUrl); | ||||||||||
| getLogger().info("Discord notifications enabled"); | ||||||||||
|
||||||||||
| getLogger().info("Discord notifications enabled"); | |
| getLogger().info("Discord notifications enabled"); | |
| } else if (discordEnabled) { | |
| getLogger().warning("Discord notifications are enabled in config, but 'discord.webhook-url' is missing or empty. Discord notifications will be skipped."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow runs
gradle test, but this repo doesn't include the Gradle wrapper (gradlew) and GitHub-hosted runners don't guarantee agradleexecutable. This will likely make CI fail. Prefer committing the Gradle wrapper and running./gradlew test --no-daemon, or add a step to install/setup Gradle before this command.