Add message aggregation and relay options for DiscordBridge

- Introduced message aggregation functionality with a configurable interval to reduce Discord API rate limits.
- Added options to relay all messages from Minecraft, including system messages, to Discord.
- Updated configuration comments to reflect new settings and their purposes.
This commit is contained in:
BruceChen 2026-03-29 18:48:00 +08:00
parent 6631180f8a
commit a1516e9680
2 changed files with 74 additions and 5 deletions

View file

@ -1,8 +1,11 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Brigadier.NET.Builder;
using DSharpPlus;
@ -34,6 +37,9 @@ namespace MinecraftClient.ChatBots
private DiscordChannel? discordChannel;
private BridgeDirection bridgeDirection = BridgeDirection.Both;
private readonly ConcurrentQueue<string> aggregationBuffer = new();
private Timer? aggregationTimer;
public static Configs Config = new();
[TomlDoNotInlineObject]
@ -62,6 +68,12 @@ namespace MinecraftClient.ChatBots
[TomlInlineComment("$ChatBot.DiscordBridge.AllowOtherBotMessages$")]
public bool Allow_Other_Bot_Messages = false;
[TomlInlineComment("$ChatBot.DiscordBridge.RelayAllMessages$")]
public bool Relay_All_Messages = false;
[TomlInlineComment("$ChatBot.DiscordBridge.MessageAggregationInterval$")]
public double Message_Aggregation_Interval = 3.0;
[TomlPrecedingComment("$ChatBot.DiscordBridge.Formats$")]
public string PrivateMessageFormat = "**[Private Message]** {username}: {message}";
public string PublicMessageFormat = "{username}: {message}";
@ -70,6 +82,8 @@ namespace MinecraftClient.ChatBots
public void OnSettingUpdate()
{
Message_Send_Timeout = Message_Send_Timeout <= 0 ? 3 : Message_Send_Timeout;
if (Message_Aggregation_Interval < 0)
Message_Aggregation_Interval = 0;
}
}
@ -100,6 +114,12 @@ namespace MinecraftClient.ChatBots
.Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName)))
);
if (Config.Message_Aggregation_Interval > 0)
{
var intervalMs = (int)(Config.Message_Aggregation_Interval * 1000);
aggregationTimer = new Timer(_ => FlushAggregationBuffer(), null, intervalMs, intervalMs);
}
Task.Run(async () => await MainAsync());
}
@ -107,6 +127,7 @@ namespace MinecraftClient.ChatBots
{
McClient.dispatcher.Unregister(CommandName);
McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName);
StopAggregation();
Disconnect();
}
@ -147,6 +168,40 @@ namespace MinecraftClient.ChatBots
return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.bot_DiscordBridge_direction, bridgeName));
}
private void FlushAggregationBuffer()
{
if (aggregationBuffer.IsEmpty || !CanSendMessages())
return;
var sb = new StringBuilder();
while (aggregationBuffer.TryDequeue(out var line))
{
if (sb.Length + line.Length + 1 > 1900)
{
SendMessage(sb.ToString());
sb.Clear();
}
if (sb.Length > 0)
sb.AppendLine();
sb.Append(line);
}
if (sb.Length > 0)
SendMessage(sb.ToString());
}
private void StopAggregation()
{
if (aggregationTimer is not null)
{
aggregationTimer.Dispose();
aggregationTimer = null;
}
FlushAggregationBuffer();
}
~DiscordBridge()
{
Disconnect();
@ -188,7 +243,6 @@ namespace MinecraftClient.ChatBots
text = GetVerbatim(text).Trim();
// Stop the crash when an empty text is recived somehow
if (string.IsNullOrEmpty(text))
return;
@ -205,7 +259,10 @@ namespace MinecraftClient.ChatBots
message = Config.TeleportRequestMessageFormat.Replace("{username}", username).Replace("{timestamp}", GetTimestamp()).Trim();
teleportRequest = true;
}
else message = text;
else if (Config.Relay_All_Messages)
message = text;
else
return;
if (teleportRequest)
{
@ -223,7 +280,13 @@ namespace MinecraftClient.ChatBots
SendMessage(messageBuilder);
return;
}
else SendMessage(GetDiscordText(message));
string discordText = GetDiscordText(message);
if (Config.Message_Aggregation_Interval > 0)
aggregationBuffer.Enqueue(discordText);
else
SendMessage(discordText);
}
/// <summary>

View file

@ -393,6 +393,12 @@ For Discord message formatting, check the following: https://mccteam.github.io/r
<data name="ChatBot.DiscordBridge.AllowOtherBotMessages" xml:space="preserve">
<value>When enabled, messages from other Discord bots in the channel will be relayed to Minecraft chat. The bridge always ignores its own messages to prevent loops.</value>
</data>
<data name="ChatBot.DiscordBridge.RelayAllMessages" xml:space="preserve">
<value>When enabled, all text received from the Minecraft server (including system messages, join/leave notifications, etc.) will be relayed to Discord, not just player chat and private messages.</value>
</data>
<data name="ChatBot.DiscordBridge.MessageAggregationInterval" xml:space="preserve">
<value>Interval in seconds to aggregate messages before sending them to Discord. When set to 0, messages are sent immediately one by one. When set to a value like 1.0, messages received within that interval are batched into a single Discord message. Useful for reducing Discord API rate limits.</value>
</data>
<data name="ChatBot.Farmer" xml:space="preserve">
<value>Automatically farms crops for you (plants, breaks and bonemeals them).
Crop types available: Beetroot, Carrot, Melon, Netherwart, Pumpkin, Potato, Wheat.
@ -964,7 +970,7 @@ Note: This does NOT require a Bot Token, only an Application ID. Discord must be
<value>Show passive mob names on the minimap.</value>
</data>
<data name="Console.Minimap.RefreshInterval" xml:space="preserve">
<value>Minimap refresh interval in milliseconds (200-5000, default 1000).</value>
<value>Minimap refresh interval in milliseconds (100-5000).</value>
</data>
<data name="Main.General.AuthlibUser" xml:space="preserve">
<value>Yggdrasil authlib multi-user selection.</value>