From dfbca11fa8e9e072f7f9e5f38bf2bbfb04cf7446 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 22:35:12 +0000
Subject: [PATCH 1/6] Initial plan
From 9b6a8c4654524b3bad3819a45dfee622811d88c2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 22:58:01 +0000
Subject: [PATCH 2/6] Add Discord webhook support to notification responses
Co-authored-by: kln581 <79673692+kln581@users.noreply.github.com>
---
.../chatnotify/config/Response.java | 27 +++-
.../util/DiscordWebhookHandler.java | 115 ++++++++++++++++++
.../chatnotify/util/ResponseUtil.java | 2 +
3 files changed, 140 insertions(+), 4 deletions(-)
create mode 100644 common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/config/Response.java b/common/src/main/java/dev/terminalmc/chatnotify/config/Response.java
index 9e29cd30..12750bf9 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/config/Response.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/config/Response.java
@@ -27,7 +27,7 @@
*/
public class Response implements StringSupplier {
- public static final int VERSION = 2;
+ public static final int VERSION = 3;
public final int version = VERSION;
/**
@@ -66,6 +66,12 @@ public class Response implements StringSupplier {
public int cooldownTicks;
public static final int cooldownTicksDefault = 0;
+ /**
+ * The Discord webhook URL (only used when type is DISCORD).
+ */
+ public String webhookUrl;
+ public static final String webhookUrlDefault = "";
+
/**
* Controls how {@link Response#string} is processed.
*/
@@ -83,7 +89,11 @@ public enum Type {
/**
* Convert into a pair of keys for use by the CommandKeys mod.
*/
- COMMANDKEYS("K");
+ COMMANDKEYS("K"),
+ /**
+ * Send as a Discord webhook message.
+ */
+ DISCORD("🪝");
public final String icon;
@@ -100,17 +110,19 @@ public Response() {
string = stringDefault;
delayTicks = delayTicksDefault;
type = Type.values()[0];
+ webhookUrl = webhookUrlDefault;
}
/**
* Not validated.
*/
- Response(boolean enabled, String string, Type type, int delayTicks, int cooldownTicks) {
+ Response(boolean enabled, String string, Type type, int delayTicks, int cooldownTicks, String webhookUrl) {
this.enabled = enabled;
this.string = string;
this.type = type;
this.delayTicks = delayTicks;
this.cooldownTicks = cooldownTicks;
+ this.webhookUrl = webhookUrl;
}
@Override
@@ -179,7 +191,14 @@ public Response deserialize(
silent
);
- return new Response(enabled, string, type, delayTicks, cooldownTicks).validate();
+ String webhookUrl = JsonUtil.getOrDefault(
+ obj,
+ "webhookUrl",
+ webhookUrlDefault,
+ silent
+ );
+
+ return new Response(enabled, string, type, delayTicks, cooldownTicks, webhookUrl).validate();
}
}
}
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java b/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java
new file mode 100644
index 00000000..f1be813d
--- /dev/null
+++ b/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2026 TerminalMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dev.terminalmc.chatnotify.util;
+
+import com.google.gson.JsonObject;
+import dev.terminalmc.chatnotify.ChatNotify;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+
+/**
+ * Handles sending messages to Discord webhooks.
+ */
+public class DiscordWebhookHandler {
+
+ private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(10))
+ .build();
+
+ private DiscordWebhookHandler() {
+ }
+
+ /**
+ * Sends a message to a Discord webhook asynchronously.
+ *
+ * @param webhookUrl the Discord webhook URL
+ * @param content the message content to send
+ */
+ public static void sendAsync(String webhookUrl, String content) {
+ if (webhookUrl == null || webhookUrl.isBlank()) {
+ ChatNotify.LOG.warn("Discord webhook URL is empty, skipping webhook send");
+ return;
+ }
+
+ if (content == null || content.isBlank()) {
+ ChatNotify.LOG.warn("Discord webhook content is empty, skipping webhook send");
+ return;
+ }
+
+ // Validate webhook URL format
+ if (!isValidWebhookUrl(webhookUrl)) {
+ ChatNotify.LOG.error("Invalid Discord webhook URL format: {}", webhookUrl);
+ return;
+ }
+
+ // Build JSON payload
+ JsonObject payload = new JsonObject();
+ payload.addProperty("content", content);
+
+ String jsonPayload = payload.toString();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(webhookUrl))
+ .timeout(Duration.ofSeconds(10))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(jsonPayload, StandardCharsets.UTF_8))
+ .build();
+
+ // Send asynchronously
+ HTTP_CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString())
+ .thenApply(HttpResponse::statusCode)
+ .thenAccept(statusCode -> {
+ if (statusCode >= 200 && statusCode < 300) {
+ ChatNotify.LOG.info("Successfully sent Discord webhook message");
+ } else if (statusCode == 429) {
+ ChatNotify.LOG.warn("Discord webhook rate limited (HTTP {})", statusCode);
+ } else {
+ ChatNotify.LOG.error("Failed to send Discord webhook message (HTTP {})", statusCode);
+ }
+ })
+ .exceptionally(throwable -> {
+ ChatNotify.LOG.error("Error sending Discord webhook message", throwable);
+ return null;
+ });
+ }
+
+ /**
+ * Validates that a webhook URL is a valid Discord webhook URL.
+ *
+ * @param webhookUrl the URL to validate
+ * @return true if the URL is a valid Discord webhook URL
+ */
+ private static boolean isValidWebhookUrl(String webhookUrl) {
+ try {
+ URI uri = URI.create(webhookUrl);
+ String host = uri.getHost();
+ String path = uri.getPath();
+
+ // Discord webhook URLs should be https://discord.com/api/webhooks/... or https://discordapp.com/api/webhooks/...
+ return (host != null && (host.equals("discord.com") || host.equals("discordapp.com"))) &&
+ path != null && path.startsWith("/api/webhooks/");
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/util/ResponseUtil.java b/common/src/main/java/dev/terminalmc/chatnotify/util/ResponseUtil.java
index 8e7be3f4..d800576c 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/util/ResponseUtil.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/util/ResponseUtil.java
@@ -60,6 +60,8 @@ public static void tickResponses(Minecraft mc) {
if (res.sendingString != null && !res.sendingString.isBlank()) {
if (res.type.equals(Response.Type.COMMANDKEYS)) {
CommandKeysWrapper.trySend(res.sendingString);
+ } else if (res.type.equals(Response.Type.DISCORD)) {
+ DiscordWebhookHandler.sendAsync(res.webhookUrl, res.sendingString);
} else {
sending.add(res.sendingString);
}
From 14a6808f65b844bba49decfcca04b955707b1539 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 22:59:38 +0000
Subject: [PATCH 3/6] Add GUI support for Discord webhook URL configuration
Co-authored-by: kln581 <79673692+kln581@users.noreply.github.com>
---
.../gui/widget/list/FilterList.java | 23 +++++++++++++++++++
.../assets/chatnotify/lang/en_us.json | 3 +++
2 files changed, 26 insertions(+)
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java
index 8a733ff0..5c2e39bf 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java
@@ -670,6 +670,29 @@ public ResponseOptions(
? message.string.split("-")[1]
: "");
elements.add(keyField2);
+ } else if (message.type.equals(Response.Type.DISCORD)) {
+ // Discord webhook - show message field and webhook URL field
+ int discordMsgFieldWidth = msgFieldWidth / 2 - fieldSpacing / 2;
+ int webhookFieldWidth = msgFieldWidth / 2 - fieldSpacing / 2;
+
+ // Message field
+ MultiLineTextField msgField =
+ new MultiLineTextField(movingX, 0, discordMsgFieldWidth, height * 2);
+ msgField.setCharacterLimit(256);
+ msgField.setValue(message.string);
+ msgField.setValueListener((val) -> message.string = val.strip());
+ msgField.setHint(localized("option", "notif.response.discord.message").copy());
+ elements.add(msgField);
+ movingX += discordMsgFieldWidth + fieldSpacing;
+
+ // Webhook URL field
+ MultiLineTextField webhookField =
+ new MultiLineTextField(movingX, 0, webhookFieldWidth, height * 2);
+ webhookField.setCharacterLimit(256);
+ webhookField.setValue(message.webhookUrl);
+ webhookField.setValueListener((val) -> message.webhookUrl = val.strip());
+ webhookField.setHint(localized("option", "notif.response.discord.webhook_url").copy());
+ elements.add(webhookField);
} else {
// Response field
MultiLineTextField msgField =
diff --git a/common/src/main/resources/assets/chatnotify/lang/en_us.json b/common/src/main/resources/assets/chatnotify/lang/en_us.json
index 1ce5b19f..2d840097 100644
--- a/common/src/main/resources/assets/chatnotify/lang/en_us.json
+++ b/common/src/main/resources/assets/chatnotify/lang/en_us.json
@@ -172,8 +172,11 @@
"option.chatnotify.notif.response.list.tooltip.warning": "Warning: Can crash the game, use with caution.",
"option.chatnotify.notif.response.time.tooltip": "Time in ticks to wait (after the previous message, if any) before sending.",
"option.chatnotify.notif.response.type.COMMANDKEYS.tooltip": "CommandKeys response.\nIf the CommandKeys mod is loaded (version 2.3.1 or later), this response will trigger all macros with keybinds matching the key IDs you set.",
+ "option.chatnotify.notif.response.type.DISCORD.tooltip": "Discord webhook response.\nSends a message to the specified Discord webhook URL when this notification is activated.",
"option.chatnotify.notif.response.type.NORMAL.tooltip": "Normal response.",
"option.chatnotify.notif.response.type.REGEX.tooltip": "Regex response.\nUse (1), (2) etc in the message to access regex capturing groups from the trigger.",
+ "option.chatnotify.notif.response.discord.message": "Message",
+ "option.chatnotify.notif.response.discord.webhook_url": "Webhook URL",
"option.chatnotify.notif.sound": "Sound",
"option.chatnotify.notif.sound.field.tooltip": "Notification Sound",
"option.chatnotify.notif.sound.open.minecraft_volume.tooltip": "Open Minecraft's volume settings",
From 5f158e4fbbee240d614dd9cbe28e3c1e19eeb8cc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 23:00:34 +0000
Subject: [PATCH 4/6] Address code review feedback: add HTTPS validation and
null safety
Co-authored-by: kln581 <79673692+kln581@users.noreply.github.com>
---
.../terminalmc/chatnotify/gui/widget/list/FilterList.java | 2 +-
.../terminalmc/chatnotify/util/DiscordWebhookHandler.java | 7 +++++--
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java
index 5c2e39bf..4a08bb52 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java
@@ -689,7 +689,7 @@ public ResponseOptions(
MultiLineTextField webhookField =
new MultiLineTextField(movingX, 0, webhookFieldWidth, height * 2);
webhookField.setCharacterLimit(256);
- webhookField.setValue(message.webhookUrl);
+ webhookField.setValue(message.webhookUrl != null ? message.webhookUrl : "");
webhookField.setValueListener((val) -> message.webhookUrl = val.strip());
webhookField.setHint(localized("option", "notif.response.discord.webhook_url").copy());
elements.add(webhookField);
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java b/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java
index f1be813d..499657e5 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java
@@ -102,11 +102,14 @@ public static void sendAsync(String webhookUrl, String content) {
private static boolean isValidWebhookUrl(String webhookUrl) {
try {
URI uri = URI.create(webhookUrl);
+ String scheme = uri.getScheme();
String host = uri.getHost();
String path = uri.getPath();
- // Discord webhook URLs should be https://discord.com/api/webhooks/... or https://discordapp.com/api/webhooks/...
- return (host != null && (host.equals("discord.com") || host.equals("discordapp.com"))) &&
+ // Discord webhook URLs must use HTTPS for secure transmission
+ // and should be https://discord.com/api/webhooks/... or https://discordapp.com/api/webhooks/...
+ return "https".equals(scheme) &&
+ (host != null && (host.equals("discord.com") || host.equals("discordapp.com"))) &&
path != null && path.startsWith("/api/webhooks/");
} catch (Exception e) {
return false;
From 4bc1a4957fa5075b70c738a062bd7d54c9a749b4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 23:03:04 +0000
Subject: [PATCH 5/6] Update documentation for Discord webhook feature
Co-authored-by: kln581 <79673692+kln581@users.noreply.github.com>
---
README.md | 16 +++++++++++++---
changelog.md | 4 ++++
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 5f3876e0..bc4989c9 100644
--- a/README.md
+++ b/README.md
@@ -27,8 +27,8 @@ Plays a ping sound when your name is mentioned, with options to create custom al
- Adjust sound volume and pitch per-notification.
- Customize message highlighting with a color picker and format controls.
- Use regex patterns, inclusion and exclusion triggers for fine-grained control.
-- Add automatic response messages or trigger [CommandKeys](https://modrinth.com/project/commandkeys)
- macros.
+- Add automatic response messages, trigger [CommandKeys](https://modrinth.com/project/commandkeys)
+ macros, or send notifications to Discord webhooks.
@@ -133,7 +133,17 @@ match the message.
#### Response
-Response messages will be sent in chat when the notification is activated.
+Response messages can be sent when the notification is activated. ChatNotify supports multiple response types:
+
+- **Normal**: Sends a message directly in chat
+- **Regex**: Uses regex capture groups from the trigger in the response message
+- **CommandKeys**: Triggers [CommandKeys](https://modrinth.com/project/commandkeys) macros
+- **Discord**: Sends a message to a Discord webhook (useful for monitoring chat events remotely)
+
+For Discord webhooks, you'll need to:
+1. Create a webhook in your Discord server (Server Settings > Integrations > Webhooks)
+2. Copy the webhook URL
+3. Select "Discord" as the response type and paste the webhook URL
Use with caution, as you can easily make a notification send a response which triggers the
notification again in a loop, which will spam chat and then crash the game.
diff --git a/changelog.md b/changelog.md
index 4e6d546a..e2718978 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,9 @@
# Changelog
+## Unreleased
+
+- Added Discord webhook support for notification responses
+
## 2.6.6
- Fixed format code translation not resetting format on color change
From 7d7a5d4dd73aa300f9bf7619529dabeab15d785f Mon Sep 17 00:00:00 2001
From: kln581
Date: Mon, 2 Feb 2026 23:05:53 -0800
Subject: [PATCH 6/6] Move Discord webhook settings to Detection tab
---
README.md | 3 +-
changelog.md | 2 +-
.../terminalmc/chatnotify/config/Config.java | 36 ++++++++++++-
.../chatnotify/config/Response.java | 26 +++-------
.../gui/widget/list/FilterList.java | 24 +--------
.../gui/widget/list/root/ControlList.java | 1 +
.../gui/widget/list/root/DetectionList.java | 50 +++++++++++++++++++
.../util/DiscordWebhookHandler.java | 39 +++++++++++++--
.../chatnotify/util/ResponseUtil.java | 10 +++-
.../chatnotify/util/text/MessageUtil.java | 14 +++++-
.../assets/chatnotify/lang/en_us.json | 8 +--
.../assets/chatnotify/lang/ru_ru.json | 4 ++
.../assets/chatnotify/lang/zh_cn.json | 4 ++
.../assets/chatnotify/lang/zh_tw.json | 4 ++
gradle.properties | 2 +-
15 files changed, 174 insertions(+), 53 deletions(-)
diff --git a/README.md b/README.md
index bc4989c9..3baf2722 100644
--- a/README.md
+++ b/README.md
@@ -143,7 +143,8 @@ Response messages can be sent when the notification is activated. ChatNotify sup
For Discord webhooks, you'll need to:
1. Create a webhook in your Discord server (Server Settings > Integrations > Webhooks)
2. Copy the webhook URL
-3. Select "Discord" as the response type and paste the webhook URL
+3. In ChatNotify, open Detection > Sender Detection and enable Discord Webhook
+4. Paste the webhook URL into the Webhook URL field
Use with caution, as you can easily make a notification send a response which triggers the
notification again in a loop, which will spam chat and then crash the game.
diff --git a/changelog.md b/changelog.md
index e2718978..c27aaad8 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,6 @@
# Changelog
-## Unreleased
+## 2.6.7
- Added Discord webhook support for notification responses
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/config/Config.java b/common/src/main/java/dev/terminalmc/chatnotify/config/Config.java
index 50177065..7de39ba1 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/config/Config.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/config/Config.java
@@ -61,7 +61,7 @@
*/
public class Config {
- public static final int VERSION = 9;
+ public static final int VERSION = 10;
public final int version = VERSION;
private static final Path CONFIG_DIR = Services.PLATFORM.getConfigDir();
public static final String FILE_NAME = ChatNotify.MOD_ID + ".json";
@@ -199,6 +199,18 @@ public enum SenderDetectionMode {
public static final Supplier> prefixesDefault =
() -> new ArrayList<>(List.of("/shout", "/me", "!"));
+ /**
+ * Whether Discord webhook responses are enabled.
+ */
+ public boolean discordWebhookEnabled;
+ public static final boolean discordWebhookEnabledDefault = false;
+
+ /**
+ * The global Discord webhook URL for all notifications.
+ */
+ public String discordWebhookUrl;
+ public static final String discordWebhookUrlDefault = "";
+
// Notifications
/**
@@ -228,6 +240,8 @@ public Config() {
SenderDetectionMode.values()[0],
checkOwnMessagesDefault,
prefixesDefault.get(),
+ discordWebhookEnabledDefault,
+ discordWebhookUrlDefault,
notificationsDefault.get()
);
}
@@ -250,6 +264,8 @@ public Config() {
SenderDetectionMode senderDetectionMode,
boolean checkOwnMessages,
List prefixes,
+ boolean discordWebhookEnabled,
+ String discordWebhookUrl,
List notifications
) {
this.debugMode = debugMode;
@@ -266,6 +282,8 @@ public Config() {
this.senderDetectionMode = senderDetectionMode;
this.checkOwnMessages = checkOwnMessages;
this.prefixes = prefixes;
+ this.discordWebhookEnabled = discordWebhookEnabled;
+ this.discordWebhookUrl = discordWebhookUrl;
this.notifications = notifications;
}
@@ -634,6 +652,20 @@ public Config deserialize(
silent
);
+ boolean discordWebhookEnabled = JsonUtil.getOrDefault(
+ obj,
+ "discordWebhookEnabled",
+ discordWebhookEnabledDefault,
+ silent
+ );
+
+ String discordWebhookUrl = JsonUtil.getOrDefault(
+ obj,
+ "discordWebhookUrl",
+ discordWebhookUrlDefault,
+ silent
+ );
+
List notifications = JsonUtil.getOrDefault(
ctx,
obj,
@@ -658,6 +690,8 @@ public Config deserialize(
senderDetectionMode,
checkOwnMessages,
prefixes,
+ discordWebhookEnabled,
+ discordWebhookUrl,
notifications
).validate();
}
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/config/Response.java b/common/src/main/java/dev/terminalmc/chatnotify/config/Response.java
index 12750bf9..1f3dc56e 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/config/Response.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/config/Response.java
@@ -40,6 +40,11 @@ public class Response implements StringSupplier {
*/
public transient @Nullable String sendingString;
+ /**
+ * The original message that triggered this response.
+ */
+ public transient @Nullable net.minecraft.network.chat.Component triggerMessage;
+
// Options
/**
@@ -66,12 +71,6 @@ public class Response implements StringSupplier {
public int cooldownTicks;
public static final int cooldownTicksDefault = 0;
- /**
- * The Discord webhook URL (only used when type is DISCORD).
- */
- public String webhookUrl;
- public static final String webhookUrlDefault = "";
-
/**
* Controls how {@link Response#string} is processed.
*/
@@ -93,7 +92,7 @@ public enum Type {
/**
* Send as a Discord webhook message.
*/
- DISCORD("🪝");
+ DISCORD("D");
public final String icon;
@@ -110,19 +109,17 @@ public Response() {
string = stringDefault;
delayTicks = delayTicksDefault;
type = Type.values()[0];
- webhookUrl = webhookUrlDefault;
}
/**
* Not validated.
*/
- Response(boolean enabled, String string, Type type, int delayTicks, int cooldownTicks, String webhookUrl) {
+ Response(boolean enabled, String string, Type type, int delayTicks, int cooldownTicks) {
this.enabled = enabled;
this.string = string;
this.type = type;
this.delayTicks = delayTicks;
this.cooldownTicks = cooldownTicks;
- this.webhookUrl = webhookUrl;
}
@Override
@@ -191,14 +188,7 @@ public Response deserialize(
silent
);
- String webhookUrl = JsonUtil.getOrDefault(
- obj,
- "webhookUrl",
- webhookUrlDefault,
- silent
- );
-
- return new Response(enabled, string, type, delayTicks, cooldownTicks, webhookUrl).validate();
+ return new Response(enabled, string, type, delayTicks, cooldownTicks).validate();
}
}
}
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java
index 4a08bb52..e8a515ca 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/FilterList.java
@@ -671,28 +671,8 @@ public ResponseOptions(
: "");
elements.add(keyField2);
} else if (message.type.equals(Response.Type.DISCORD)) {
- // Discord webhook - show message field and webhook URL field
- int discordMsgFieldWidth = msgFieldWidth / 2 - fieldSpacing / 2;
- int webhookFieldWidth = msgFieldWidth / 2 - fieldSpacing / 2;
-
- // Message field
- MultiLineTextField msgField =
- new MultiLineTextField(movingX, 0, discordMsgFieldWidth, height * 2);
- msgField.setCharacterLimit(256);
- msgField.setValue(message.string);
- msgField.setValueListener((val) -> message.string = val.strip());
- msgField.setHint(localized("option", "notif.response.discord.message").copy());
- elements.add(msgField);
- movingX += discordMsgFieldWidth + fieldSpacing;
-
- // Webhook URL field
- MultiLineTextField webhookField =
- new MultiLineTextField(movingX, 0, webhookFieldWidth, height * 2);
- webhookField.setCharacterLimit(256);
- webhookField.setValue(message.webhookUrl != null ? message.webhookUrl : "");
- webhookField.setValueListener((val) -> message.webhookUrl = val.strip());
- webhookField.setHint(localized("option", "notif.response.discord.webhook_url").copy());
- elements.add(webhookField);
+ // Discord webhook - no fields needed, just enabled/disabled
+ // The message that triggered the notification is sent automatically
} else {
// Response field
MultiLineTextField msgField =
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/root/ControlList.java b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/root/ControlList.java
index 8e4189f0..f80dd552 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/root/ControlList.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/root/ControlList.java
@@ -143,5 +143,6 @@ private static class Controls2 extends Entry {
));
}
}
+
}
}
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/root/DetectionList.java b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/root/DetectionList.java
index 0fa8540e..e1eea00a 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/root/DetectionList.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/gui/widget/list/root/DetectionList.java
@@ -31,6 +31,8 @@
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
+import java.time.Duration;
+
import static dev.terminalmc.chatnotify.util.Localization.localized;
public class DetectionList extends OptionList {
@@ -75,6 +77,7 @@ protected void addEntries() {
addEntry(new Entry.SelfNotify(dynEntryX, dynEntryWidth, entryHeight));
addEntry(new Entry.SenderDetection(dynEntryX, dynEntryWidth, entryHeight));
+ addEntry(new Entry.DiscordWebhook(dynEntryX, dynEntryWidth, entryHeight));
addEntry(new OptionList.Entry.Text(
entryX,
@@ -265,6 +268,53 @@ private static class SenderDetection extends Entry {
}
}
+ private static class DiscordWebhook extends Entry {
+
+ DiscordWebhook(int x, int width, int height) {
+ super();
+
+ int toggleWidth = Math.min(120, (width - SPACE) / 3);
+ TextField webhookField = new TextField(
+ x + toggleWidth + SPACE,
+ 0,
+ width - toggleWidth - SPACE,
+ height
+ );
+ webhookField.setMaxLength(256);
+ webhookField.setValue(Config.get().discordWebhookUrl);
+ webhookField.setTooltip(Tooltip.create(localized(
+ "option",
+ "detection.discord_webhook.url.tooltip"
+ )));
+ webhookField.setTooltipDelay(Duration.ofMillis(500));
+ webhookField.setResponder((val) -> Config.get().discordWebhookUrl = val.strip());
+ webhookField.setHint(localized("option", "detection.discord_webhook.url").copy());
+ webhookField.active = Config.get().discordWebhookEnabled;
+ elements.add(webhookField);
+
+ elements.add(CycleButton.booleanBuilder(
+ CommonComponents.OPTION_ON.copy().withStyle(ChatFormatting.GREEN),
+ CommonComponents.OPTION_OFF.copy().withStyle(ChatFormatting.RED)
+ )
+ .withInitialValue(Config.get().discordWebhookEnabled)
+ .withTooltip((status) -> Tooltip.create(localized(
+ "option",
+ "detection.discord_webhook.tooltip"
+ )))
+ .create(
+ x,
+ 0,
+ toggleWidth,
+ height,
+ localized("option", "detection.discord_webhook"),
+ (button, status) -> {
+ Config.get().discordWebhookEnabled = status;
+ webhookField.active = status;
+ }
+ ));
+ }
+ }
+
private static class PrefixFieldEntry extends Entry {
PrefixFieldEntry(int x, int width, int height, DetectionList list, int index) {
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java b/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java
index 499657e5..aae4dc60 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/util/DiscordWebhookHandler.java
@@ -18,8 +18,10 @@
import com.google.gson.JsonObject;
import dev.terminalmc.chatnotify.ChatNotify;
+import net.minecraft.client.Minecraft;
+import net.minecraft.network.chat.Component;
+import org.jetbrains.annotations.Nullable;
-import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@@ -44,8 +46,9 @@ private DiscordWebhookHandler() {
*
* @param webhookUrl the Discord webhook URL
* @param content the message content to send
+ * @param triggerMessage the original message that triggered the notification
*/
- public static void sendAsync(String webhookUrl, String content) {
+ public static void sendAsync(String webhookUrl, String content, @Nullable Component triggerMessage) {
if (webhookUrl == null || webhookUrl.isBlank()) {
ChatNotify.LOG.warn("Discord webhook URL is empty, skipping webhook send");
return;
@@ -62,9 +65,37 @@ public static void sendAsync(String webhookUrl, String content) {
return;
}
- // Build JSON payload
+ // Build JSON payload with embed
+ JsonObject embed = new JsonObject();
+ embed.addProperty("title", "Chat triggered");
+
+ // Use the trigger message content if available, otherwise use the response content
+ String description = triggerMessage != null
+ ? triggerMessage.getString()
+ : content;
+ embed.addProperty("description", description);
+ embed.addProperty("color", 3303592); // #3268a8
+
+ JsonObject footer = new JsonObject();
+ // Get server/world name
+ Minecraft mc = Minecraft.getInstance();
+ String serverName = "Minecraft";
+ if (mc.level != null) {
+ if (mc.getCurrentServer() != null) {
+ // Multiplayer - use server name
+ serverName = mc.getCurrentServer().name;
+ } else if (mc.getSingleplayerServer() != null) {
+ // Singleplayer - use world name
+ serverName = mc.getSingleplayerServer().getWorldData().getLevelSettings().levelName();
+ }
+ }
+ footer.addProperty("text", serverName);
+ embed.add("footer", footer);
+
JsonObject payload = new JsonObject();
- payload.addProperty("content", content);
+ com.google.gson.JsonArray embeds = new com.google.gson.JsonArray();
+ embeds.add(embed);
+ payload.add("embeds", embeds);
String jsonPayload = payload.toString();
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/util/ResponseUtil.java b/common/src/main/java/dev/terminalmc/chatnotify/util/ResponseUtil.java
index d800576c..b6323653 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/util/ResponseUtil.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/util/ResponseUtil.java
@@ -61,7 +61,15 @@ public static void tickResponses(Minecraft mc) {
if (res.type.equals(Response.Type.COMMANDKEYS)) {
CommandKeysWrapper.trySend(res.sendingString);
} else if (res.type.equals(Response.Type.DISCORD)) {
- DiscordWebhookHandler.sendAsync(res.webhookUrl, res.sendingString);
+ Config config = Config.get();
+ String webhookUrl = config.discordWebhookUrl;
+ if (config.discordWebhookEnabled && !webhookUrl.isBlank()) {
+ DiscordWebhookHandler.sendAsync(
+ webhookUrl,
+ res.sendingString,
+ res.triggerMessage
+ );
+ }
} else {
sending.add(res.sendingString);
}
diff --git a/common/src/main/java/dev/terminalmc/chatnotify/util/text/MessageUtil.java b/common/src/main/java/dev/terminalmc/chatnotify/util/text/MessageUtil.java
index 2de8df97..467dac52 100644
--- a/common/src/main/java/dev/terminalmc/chatnotify/util/text/MessageUtil.java
+++ b/common/src/main/java/dev/terminalmc/chatnotify/util/text/MessageUtil.java
@@ -314,7 +314,7 @@ private static String checkOwner(String cleanStr) {
// Send response messages
Matcher subsMatcher = trig.type == Trigger.Type.REGEX ? matcher : null;
- sendResponses(notif, subsMatcher);
+ sendResponses(notif, subsMatcher, msg);
// Restyle
msg = StyleUtil.restyle(msg, cleanStr, trig, matcher, notif.textStyle, restyleAll);
@@ -633,10 +633,22 @@ private static void copyClipboardMsg(Notification notif, Component msg, Matcher
* @param notif the Notification.
*/
private static void sendResponses(Notification notif, @Nullable Matcher matcher) {
+ sendResponses(notif, matcher, null);
+ }
+
+ /**
+ * Sends all response messages of the specified notification, if the relevant control is
+ * enabled.
+ *
+ * @param notif the Notification.
+ * @param triggerMessage the original message that triggered the notification
+ */
+ private static void sendResponses(Notification notif, @Nullable Matcher matcher, @Nullable Component triggerMessage) {
if (notif.responseEnabled) {
int totalDelay = 0;
for (Response msg : notif.responses) {
msg.sendingString = msg.string;
+ msg.triggerMessage = triggerMessage;
if (msg.type.equals(Response.Type.REGEX) && matcher != null && matcher.find(0)) {
// Capturing group substitution
for (int i = 0; i <= matcher.groupCount(); i++) {
diff --git a/common/src/main/resources/assets/chatnotify/lang/en_us.json b/common/src/main/resources/assets/chatnotify/lang/en_us.json
index 2d840097..48faf915 100644
--- a/common/src/main/resources/assets/chatnotify/lang/en_us.json
+++ b/common/src/main/resources/assets/chatnotify/lang/en_us.json
@@ -85,6 +85,10 @@
"option.chatnotify.detection.sender.mode.status.SENT_MATCH": "Sent message match",
"option.chatnotify.detection.sender.mode.status.SENT_MATCH.tooltip": "Incoming messages will be identified as sent by you if they match a recently-sent message and match a trigger of the first notification.",
"option.chatnotify.detection.sender.tooltip": "If your own messages are triggering notifications unexpectedly, try adjusting the options below.",
+ "option.chatnotify.detection.discord_webhook": "Discord Webhook",
+ "option.chatnotify.detection.discord_webhook.tooltip": "Enable or disable Discord webhook responses for all notifications.",
+ "option.chatnotify.detection.discord_webhook.url": "Webhook URL",
+ "option.chatnotify.detection.discord_webhook.url.tooltip": "Set the global Discord webhook URL for all notifications.",
"option.chatnotify.notif": "Notifications",
"option.chatnotify.notif.click_edit": "Click to edit.\nRight-click to toggle.",
"option.chatnotify.notif.color.field.tooltip": "Text Restyle Color",
@@ -172,11 +176,9 @@
"option.chatnotify.notif.response.list.tooltip.warning": "Warning: Can crash the game, use with caution.",
"option.chatnotify.notif.response.time.tooltip": "Time in ticks to wait (after the previous message, if any) before sending.",
"option.chatnotify.notif.response.type.COMMANDKEYS.tooltip": "CommandKeys response.\nIf the CommandKeys mod is loaded (version 2.3.1 or later), this response will trigger all macros with keybinds matching the key IDs you set.",
- "option.chatnotify.notif.response.type.DISCORD.tooltip": "Discord webhook response.\nSends a message to the specified Discord webhook URL when this notification is activated.",
+ "option.chatnotify.notif.response.type.DISCORD.tooltip": "Discord webhook response.\nUses the global Discord webhook settings in Detection.",
"option.chatnotify.notif.response.type.NORMAL.tooltip": "Normal response.",
"option.chatnotify.notif.response.type.REGEX.tooltip": "Regex response.\nUse (1), (2) etc in the message to access regex capturing groups from the trigger.",
- "option.chatnotify.notif.response.discord.message": "Message",
- "option.chatnotify.notif.response.discord.webhook_url": "Webhook URL",
"option.chatnotify.notif.sound": "Sound",
"option.chatnotify.notif.sound.field.tooltip": "Notification Sound",
"option.chatnotify.notif.sound.open.minecraft_volume.tooltip": "Open Minecraft's volume settings",
diff --git a/common/src/main/resources/assets/chatnotify/lang/ru_ru.json b/common/src/main/resources/assets/chatnotify/lang/ru_ru.json
index fb1101f3..a72f4dde 100644
--- a/common/src/main/resources/assets/chatnotify/lang/ru_ru.json
+++ b/common/src/main/resources/assets/chatnotify/lang/ru_ru.json
@@ -85,6 +85,10 @@
"option.chatnotify.detection.sender.mode.status.SENT_MATCH": "Совпадение отправленного сообщения",
"option.chatnotify.detection.sender.mode.status.SENT_MATCH.tooltip": "Входящие сообщения будут определены как отправленные вами, если они совпадают с недавно отправленным сообщением и соответствуют триггеру первого уведомления.",
"option.chatnotify.detection.sender.tooltip": "Если ваши собственные сообщения вызывают уведомления, попробуйте настроить следующие параметры.",
+ "option.chatnotify.detection.discord_webhook": "Discord Webhook",
+ "option.chatnotify.detection.discord_webhook.tooltip": "Включить или отключить ответы вебхука Discord для всех уведомлений.",
+ "option.chatnotify.detection.discord_webhook.url": "URL вебхука",
+ "option.chatnotify.detection.discord_webhook.url.tooltip": "Установите глобальный URL вебхука Discord для всех уведомлений.",
"option.chatnotify.notif": "Параметры уведомлений",
"option.chatnotify.notif.click_edit": "Нажмите, чтобы редактировать.\nПКМ для переключения.",
"option.chatnotify.notif.color.field.tooltip": "Цвет изменения стиля текста",
diff --git a/common/src/main/resources/assets/chatnotify/lang/zh_cn.json b/common/src/main/resources/assets/chatnotify/lang/zh_cn.json
index f394d81e..3307346c 100644
--- a/common/src/main/resources/assets/chatnotify/lang/zh_cn.json
+++ b/common/src/main/resources/assets/chatnotify/lang/zh_cn.json
@@ -85,6 +85,10 @@
"option.chatnotify.detection.sender.mode.status.SENT_MATCH": "已发送消息匹配",
"option.chatnotify.detection.sender.mode.status.SENT_MATCH.tooltip": "如果传入消息与最近发送的消息匹配且与第一个通知的触发器匹配,则将其识别为你发送的消息",
"option.chatnotify.detection.sender.tooltip": "If your own messages are triggering notifications unexpectedly, try adjusting the options below.",
+ "option.chatnotify.detection.discord_webhook": "Discord Webhook",
+ "option.chatnotify.detection.discord_webhook.tooltip": "Enable or disable Discord webhook responses for all notifications.",
+ "option.chatnotify.detection.discord_webhook.url": "Webhook URL",
+ "option.chatnotify.detection.discord_webhook.url.tooltip": "Set the global Discord webhook URL for all notifications.",
"option.chatnotify.notif": "通知选项",
"option.chatnotify.notif.click_edit": "点击编辑\n右键点击切换",
"option.chatnotify.notif.color.field.tooltip": "文本重样式颜色",
diff --git a/common/src/main/resources/assets/chatnotify/lang/zh_tw.json b/common/src/main/resources/assets/chatnotify/lang/zh_tw.json
index ed9c9303..c8f53990 100644
--- a/common/src/main/resources/assets/chatnotify/lang/zh_tw.json
+++ b/common/src/main/resources/assets/chatnotify/lang/zh_tw.json
@@ -85,6 +85,10 @@
"option.chatnotify.detection.sender.mode.status.SENT_MATCH": "Sent message match",
"option.chatnotify.detection.sender.mode.status.SENT_MATCH.tooltip": "Incoming messages will be identified as sent by you if they match a recently-sent message and match a trigger of the first notification.",
"option.chatnotify.detection.sender.tooltip": "If your own messages are triggering notifications unexpectedly, try adjusting the options below.",
+ "option.chatnotify.detection.discord_webhook": "Discord Webhook",
+ "option.chatnotify.detection.discord_webhook.tooltip": "Enable or disable Discord webhook responses for all notifications.",
+ "option.chatnotify.detection.discord_webhook.url": "Webhook URL",
+ "option.chatnotify.detection.discord_webhook.url.tooltip": "Set the global Discord webhook URL for all notifications.",
"option.chatnotify.notif": "通知選項",
"option.chatnotify.notif.click_edit": "Click to edit.\nRight-click to toggle.",
"option.chatnotify.notif.color.field.tooltip": "Text Restyle Color",
diff --git a/gradle.properties b/gradle.properties
index 27b5b09b..238ff3d1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,7 +10,7 @@
template_version=10
# Mod Version
-mod_version=2.6.6
+mod_version=2.6.7
# 'STABLE', 'BETA' or 'ALPHA'
mod_version_type=STABLE