diff --git a/VPLink.Common/Configuration/IChatConfiguration.cs b/VPLink.Common/Configuration/IChatConfiguration.cs
index 86351a5..4edf066 100644
--- a/VPLink.Common/Configuration/IChatConfiguration.cs
+++ b/VPLink.Common/Configuration/IChatConfiguration.cs
@@ -18,4 +18,16 @@ public interface IChatConfiguration
///
/// The font style.
FontStyle Style { get; set; }
+
+ ///
+ /// Gets or sets the color of a reply message.
+ ///
+ /// The reply message color.
+ uint ReplyColor { get; set; }
+
+ ///
+ /// Gets or sets the font style of a reply message.
+ ///
+ /// The reply font style.
+ FontStyle ReplyStyle { get; set; }
}
diff --git a/VPLink.Common/Data/PlainTextMessageBuilder.cs b/VPLink.Common/Data/PlainTextMessageBuilder.cs
index 7c039a5..7d9db94 100644
--- a/VPLink.Common/Data/PlainTextMessageBuilder.cs
+++ b/VPLink.Common/Data/PlainTextMessageBuilder.cs
@@ -85,6 +85,14 @@ public struct PlainTextMessageBuilder : IDisposable
}
}
+ ///
+ /// Clears the builder.
+ ///
+ public void Clear()
+ {
+ _builder.Clear();
+ }
+
///
public void Dispose()
{
diff --git a/VPLink.Common/Data/RelayedMessage.cs b/VPLink.Common/Data/RelayedMessage.cs
index b4f9886..281d96f 100644
--- a/VPLink.Common/Data/RelayedMessage.cs
+++ b/VPLink.Common/Data/RelayedMessage.cs
@@ -10,12 +10,20 @@ public readonly struct RelayedMessage
///
/// The author.
/// The content.
- public RelayedMessage(string author, string content)
+ /// A value indicating whether this message is a reply.
+ public RelayedMessage(string? author, string content, bool isReply)
{
Author = author;
Content = content;
+ IsReply = isReply;
}
+ ///
+ /// Gets the user that sent the message.
+ ///
+ /// The user that sent the message.
+ public string? Author { get; }
+
///
/// Gets the message content.
///
@@ -23,8 +31,8 @@ public readonly struct RelayedMessage
public string Content { get; }
///
- /// Gets the user that sent the message.
+ /// Gets a value indicating whether this message is a reply.
///
- /// The user that sent the message.
- public string Author { get; }
+ /// if this message is a reply; otherwise, .
+ public bool IsReply { get; }
}
diff --git a/VPLink.Common/Extensions/UserExtensions.cs b/VPLink.Common/Extensions/UserExtensions.cs
new file mode 100644
index 0000000..fcb5977
--- /dev/null
+++ b/VPLink.Common/Extensions/UserExtensions.cs
@@ -0,0 +1,25 @@
+using Discord;
+
+namespace VPLink.Common.Extensions;
+
+///
+/// Provides extension methods for the interface.
+///
+public static class UserExtensions
+{
+ ///
+ /// Gets the display name of the user.
+ ///
+ /// The user.
+ /// The display name.
+ /// is null.
+ public static string GetDisplayName(this IUser user)
+ {
+ return user switch
+ {
+ null => throw new ArgumentNullException(nameof(user)),
+ IGuildUser member => member.Nickname ?? member.GlobalName ?? member.Username,
+ _ => user.GlobalName ?? user.Username
+ };
+ }
+}
diff --git a/VPLink/Configuration/ChatConfiguration.cs b/VPLink/Configuration/ChatConfiguration.cs
index 7fe03cb..f5f6b11 100644
--- a/VPLink/Configuration/ChatConfiguration.cs
+++ b/VPLink/Configuration/ChatConfiguration.cs
@@ -11,4 +11,10 @@ internal sealed class ChatConfiguration : IChatConfiguration
///
public FontStyle Style { get; set; } = FontStyle.Regular;
+
+ ///
+ public uint ReplyColor { get; set; } = 0x808080;
+
+ ///
+ public FontStyle ReplyStyle { get; set; } = FontStyle.Italic;
}
diff --git a/VPLink/Services/DiscordMessageService.cs b/VPLink/Services/DiscordMessageService.cs
index 46b5eea..cc68664 100644
--- a/VPLink/Services/DiscordMessageService.cs
+++ b/VPLink/Services/DiscordMessageService.cs
@@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging;
using VPLink.Common;
using VPLink.Common.Configuration;
using VPLink.Common.Data;
+using VPLink.Common.Extensions;
using VPLink.Common.Services;
using VpSharp.Entities;
@@ -88,45 +89,106 @@ internal sealed class DiscordMessageService : BackgroundService, IDiscordMessage
if (!ValidateMessage(arg, out IUserMessage? message, out IGuildUser? author))
return Task.CompletedTask;
- string displayName = author.Nickname ?? author.GlobalName ?? author.Username;
+ string displayName = author.GetDisplayName();
var builder = new PlainTextMessageBuilder();
- Utf8ValueStringBuilder wordBuffer = ZString.CreateUtf8StringBuilder();
- SanitizeContent(author.Guild, message.Content, ref builder, ref wordBuffer);
+ IGuild guild = author.Guild;
+ SanitizeContent(guild, message.Content, ref builder);
var content = builder.ToString();
_logger.LogInformation("Message by {Author}: {Content}", author, content);
- Span buffer = stackalloc byte[255]; // VP message length limit
var messages = new List();
- int byteCount = Utf8Encoding.GetByteCount(content);
+ MessageReference reference = arg.Reference;
+ if (reference?.MessageId.IsSpecified == true)
+ {
+ string? replyContent = GetReplyContent(arg, reference, out IUserMessage? fetchedMessage);
+ if (replyContent is not null)
+ {
+ IUser replyAuthor = fetchedMessage!.Author;
+ _logger.LogInformation("Replying to {Author}: {Content}", replyAuthor, replyContent);
+ builder.Clear();
+ SanitizeContent(guild, replyContent, ref builder);
+ replyContent = builder.ToString();
+ messages.Add(new RelayedMessage(null!, $"↩️ Replying to {fetchedMessage.Author.GetDisplayName()}:", true));
+ messages.Add(new RelayedMessage(null!, replyContent, true));
+ }
+ }
+
+ AddMessage(messages, displayName, content);
+
+ IReadOnlyCollection attachments = message.Attachments;
+ foreach (IAttachment attachment in attachments)
+ {
+ messages.Add(new RelayedMessage(displayName, attachment.Url, false));
+ }
+
+ messages.ForEach(_messageReceived.OnNext);
+ builder.Dispose();
+ return Task.CompletedTask;
+ }
+
+ private static void AddMessage(ICollection messages, string displayName, string content)
+ {
+ Span buffer = stackalloc byte[255]; // VP message length limit
+ int byteCount = Utf8Encoding.GetByteCount(content);
var offset = 0;
while (offset < byteCount)
{
int length = Math.Min(byteCount - offset, 255);
Utf8Encoding.GetBytes(content.AsSpan(offset, length), buffer);
- messages.Add(new RelayedMessage(displayName, Utf8Encoding.GetString(buffer)));
+ messages.Add(new RelayedMessage(displayName, Utf8Encoding.GetString(buffer), false));
offset += length;
}
-
- IReadOnlyCollection attachments = message.Attachments;
- foreach (IAttachment attachment in attachments)
- {
- messages.Add(new RelayedMessage(displayName, attachment.Url));
- }
-
- messages.ForEach(_messageReceived.OnNext);
- builder.Dispose();
- wordBuffer.Dispose();
- return Task.CompletedTask;
}
- private static void SanitizeContent(IGuild guild,
- ReadOnlySpan content,
- ref PlainTextMessageBuilder builder,
- ref Utf8ValueStringBuilder wordBuffer)
+ private string? GetReplyContent(SocketMessage message, MessageReference reference, out IUserMessage? fetchedMessage)
{
+ fetchedMessage = null;
+ IGuild authorGuild = ((IGuildUser)message.Author).Guild;
+ IGuild guild = authorGuild;
+
+ Optional referenceGuildId = reference.GuildId;
+ Optional referenceMessageId = reference.MessageId;
+
+ if (!referenceMessageId.IsSpecified)
+ {
+ return null;
+ }
+
+ if (referenceGuildId.IsSpecified)
+ {
+ guild = _discordClient.GetGuild(referenceGuildId.Value) ?? authorGuild;
+ }
+
+ ulong referenceChannelId = reference.ChannelId;
+
+ if (!referenceMessageId.IsSpecified)
+ {
+ return null;
+ }
+
+ if (guild.GetChannelAsync(referenceChannelId).GetAwaiter().GetResult() is not ITextChannel channel)
+ {
+ return null;
+ }
+
+ IMessage? referencedMessage = channel.GetMessageAsync(referenceMessageId.Value).GetAwaiter().GetResult();
+ if (referencedMessage is null)
+ {
+ return null;
+ }
+
+ fetchedMessage = referencedMessage as IUserMessage;
+ string? content = referencedMessage.Content;
+ return string.IsNullOrWhiteSpace(content) ? null : content;
+ }
+
+ private static void SanitizeContent(IGuild guild, ReadOnlySpan content, ref PlainTextMessageBuilder builder)
+ {
+ Utf8ValueStringBuilder wordBuffer = ZString.CreateUtf8StringBuilder();
+
for (var index = 0; index < content.Length; index++)
{
char current = content[index];
@@ -145,6 +207,8 @@ internal sealed class DiscordMessageService : BackgroundService, IDiscordMessage
{
AddWord(guild, ref builder, ref wordBuffer, '\0');
}
+
+ wordBuffer.Dispose();
}
private static void AddWord(IGuild guild,
diff --git a/VPLink/Services/VirtualParadiseMessageService.cs b/VPLink/Services/VirtualParadiseMessageService.cs
index 8d61eac..48f0117 100644
--- a/VPLink/Services/VirtualParadiseMessageService.cs
+++ b/VPLink/Services/VirtualParadiseMessageService.cs
@@ -45,8 +45,8 @@ internal sealed class VirtualParadiseMessageService : BackgroundService, IVirtua
{
IChatConfiguration configuration = _configurationService.VirtualParadiseConfiguration.Chat;
- Color color = Color.FromArgb((int)configuration.Color);
- FontStyle style = configuration.Style;
+ Color color = Color.FromArgb((int)(message.IsReply ? configuration.ReplyColor : configuration.Color));
+ FontStyle style = message.IsReply ? configuration.ReplyStyle : configuration.Style;
string content = Format.StripMarkDown(message.Content);
return _virtualParadiseClient.SendMessageAsync(message.Author, content, style, color);
@@ -68,7 +68,7 @@ internal sealed class VirtualParadiseMessageService : BackgroundService, IVirtua
_logger.LogInformation("Message by {Author}: {Content}", message.Author, message.Content);
- var relayedMessage = new RelayedMessage(message.Author.Name, message.Content);
+ var relayedMessage = new RelayedMessage(message.Author.Name, message.Content, false);
_messageReceived.OnNext(relayedMessage);
}
}