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