diff --git a/VPLink.Common/Data/PlainTextMessageBuilder.cs b/VPLink.Common/Data/PlainTextMessageBuilder.cs
index a1f5ff0..b749a3c 100644
--- a/VPLink.Common/Data/PlainTextMessageBuilder.cs
+++ b/VPLink.Common/Data/PlainTextMessageBuilder.cs
@@ -1,5 +1,4 @@
using Cysharp.Text;
-using Humanizer;
namespace VPLink.Common.Data;
@@ -35,13 +34,12 @@ public struct PlainTextMessageBuilder : IDisposable
/// The timestamp.
/// The format.
/// The trailing whitespace trivia.
- public void AddTimestamp(DateTimeOffset timestamp, TimestampFormat format = TimestampFormat.None,
- char whitespace = ' ')
+ public void AddTimestamp(DateTimeOffset timestamp, TimestampFormat format, char whitespace = ' ')
{
switch (format)
{
case TimestampFormat.Relative:
- AddWord(timestamp.Humanize(), whitespace);
+ AddWord(FormatRelativeTime(timestamp), whitespace);
break;
case TimestampFormat.None:
@@ -104,4 +102,53 @@ public struct PlainTextMessageBuilder : IDisposable
{
return _builder.ToString().Trim();
}
+
+ private static string FormatRelativeTime(DateTimeOffset targetTime)
+ {
+ TimeSpan timeDifference = DateTimeOffset.Now - targetTime;
+ bool isFuture = timeDifference.TotalMilliseconds < 0;
+ int value;
+ string unit;
+
+ timeDifference = TimeSpan.FromMilliseconds(Math.Abs(timeDifference.TotalMilliseconds));
+ switch (timeDifference.TotalDays)
+ {
+ case >= 365:
+ unit = "year";
+ value = (int)(timeDifference.TotalDays / 365);
+ break;
+
+ case >= 30:
+ unit = "month";
+ value = (int)(timeDifference.TotalDays / 30);
+ break;
+
+ case >= 1:
+ unit = "day";
+ value = (int)timeDifference.TotalDays;
+ break;
+
+ default:
+ if (timeDifference.TotalHours >= 1)
+ {
+ unit = "hour";
+ value = (int)timeDifference.TotalHours;
+ }
+ else if (timeDifference.TotalMinutes >= 1)
+ {
+ unit = "minute";
+ value = (int)timeDifference.TotalMinutes;
+ }
+ else
+ {
+ unit = "second";
+ value = (int)timeDifference.TotalSeconds;
+ }
+
+ break;
+ }
+
+ string suffix = value > 1 ? "s" : "";
+ return isFuture ? $"in {value} {unit}{suffix}" : $"{value} {unit}{suffix} ago";
+ }
}
diff --git a/VPLink.Common/MentionUtility.cs b/VPLink.Common/MentionUtility.cs
index 5bc355d..08826b2 100644
--- a/VPLink.Common/MentionUtility.cs
+++ b/VPLink.Common/MentionUtility.cs
@@ -24,6 +24,11 @@ public static class MentionUtility
else
switch (contents[0])
{
+ // custom emote
+ case ':':
+ ParseCustomEmote(contents, ref builder, whitespaceTrivia);
+ break;
+
// user mention
case '@':
ParseUserMention(guild, contents, ref builder, whitespaceTrivia);
@@ -71,6 +76,15 @@ public static class MentionUtility
whitespaceTrivia);
}
+ private static void ParseCustomEmote(ReadOnlySpan contents,
+ ref PlainTextMessageBuilder builder,
+ char whitespaceTrivia)
+ {
+ contents = contents[1..];
+ ReadOnlySpan name = contents[..contents.IndexOf(':')];
+ builder.AddWord($":{name.ToString()}:", whitespaceTrivia);
+ }
+
private static void ParseTimestamp(ReadOnlySpan contents,
ref PlainTextMessageBuilder builder,
char whitespaceTrivia)
diff --git a/VPLink/Services/DiscordMessageService.cs b/VPLink/Services/DiscordMessageService.cs
index 8611e36..2230eaf 100644
--- a/VPLink/Services/DiscordMessageService.cs
+++ b/VPLink/Services/DiscordMessageService.cs
@@ -96,6 +96,9 @@ internal sealed class DiscordMessageService : BackgroundService, IDiscordMessage
SanitizeContent(guild, message.Content, ref builder);
var content = builder.ToString();
+ Span testSpan = stackalloc byte[Utf8Encoding.GetByteCount(content)];
+ Utf8Encoding.GetBytes(content, testSpan);
+
_logger.LogInformation("Message by {Author}: {Content}", author, content);
var messages = new List();
@@ -141,14 +144,16 @@ internal sealed class DiscordMessageService : BackgroundService, IDiscordMessage
private static void AddMessage(ICollection messages, string displayName, string content)
{
- Span buffer = stackalloc byte[255]; // VP message length limit
int byteCount = Utf8Encoding.GetByteCount(content);
+ Span buffer = stackalloc byte[byteCount];
+ Utf8Encoding.GetBytes(content, buffer);
+
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), false));
+ int length = Math.Min(byteCount - offset, 255); // VP message length limit
+ Span slice = buffer.Slice(offset, length);
+ messages.Add(new RelayedMessage(displayName, Utf8Encoding.GetString(slice), false));
offset += length;
}
}
@@ -199,6 +204,8 @@ internal sealed class DiscordMessageService : BackgroundService, IDiscordMessage
{
Utf8ValueStringBuilder wordBuffer = ZString.CreateUtf8StringBuilder();
+ Span chars = stackalloc char[2];
+ Span bytes = stackalloc byte[4];
for (var index = 0; index < content.Length; index++)
{
char current = content[index];
@@ -207,6 +214,13 @@ internal sealed class DiscordMessageService : BackgroundService, IDiscordMessage
AddWord(guild, ref builder, ref wordBuffer, current);
wordBuffer.Clear();
}
+ else if (char.IsSurrogate(current))
+ {
+ content.Slice(index++, 2).CopyTo(chars);
+ int byteCount = Utf8Encoding.GetByteCount(chars);
+ Utf8Encoding.GetBytes(chars, bytes);
+ wordBuffer.AppendLiteral(bytes[..byteCount]);
+ }
else
{
wordBuffer.Append(current);
@@ -256,6 +270,10 @@ internal sealed class DiscordMessageService : BackgroundService, IDiscordMessage
MentionUtility.ParseTag(guild, temp[..tagLength], ref builder, whitespace);
break;
+ case var _ when char.IsSurrogate(current):
+ buffer.Append(chars.Slice(index++, 2));
+ break;
+
default:
buffer.Append(current);
break;
diff --git a/VPLink/VPLink.csproj b/VPLink/VPLink.csproj
index 4d33764..5d383f9 100644
--- a/VPLink/VPLink.csproj
+++ b/VPLink/VPLink.csproj
@@ -9,7 +9,7 @@
Oliver Booth
https://github.com/oliverbooth/VpBridge
git
- 1.3.0
+ 1.3.1