feat: handle message queue delay and fix wrapping
Make message wrapping work with non-English languages. Special-case /tell wrapping to send to the correct recipient. Add an artificial wait between sending messages in the queue, longer for public commands to prevent rate-limiting.
This commit is contained in:
parent
e41b90d63e
commit
c092bf2019
|
@ -6,6 +6,7 @@
|
|||
<InputAssemblies Include="$(OutputPath)\*.dll"
|
||||
Exclude="$(OutputPath)\$(AssemblyName).dll;
|
||||
$(OutputPath)\libsodium.dll;
|
||||
$(OutputPath)\xivchat_native_tools.dll;
|
||||
$(OutputPath)\System.Buffers.dll;
|
||||
$(OutputPath)\System.Memory.dll;
|
||||
$(OutputPath)\System.Numerics.Vectors.dll"/>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace XIVChatPlugin {
|
||||
public static unsafe class NativeTools {
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private readonly struct RawVec {
|
||||
public readonly byte** pointer;
|
||||
public readonly uint length;
|
||||
private readonly uint capacity;
|
||||
}
|
||||
|
||||
[DllImport("xivchat_native_tools.dll")]
|
||||
private static extern RawVec* wrap(byte* input, uint width);
|
||||
|
||||
[DllImport("xivchat_native_tools.dll")]
|
||||
private static extern void wrap_free(RawVec* raw);
|
||||
|
||||
public static IEnumerable<string> Wrap(string input, uint width) {
|
||||
RawVec* raw;
|
||||
fixed (byte* ptr = Encoding.UTF8.GetBytes(input).Terminate()) {
|
||||
raw = wrap(ptr, width);
|
||||
}
|
||||
|
||||
if (raw == null) {
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var strings = new List<string>((int) raw->length);
|
||||
for (var i = 0; i < raw->length; i++) {
|
||||
var bytes = Util.ReadTerminated(raw->pointer[i]);
|
||||
strings.Add(Encoding.UTF8.GetString(bytes));
|
||||
}
|
||||
|
||||
wrap_free(raw);
|
||||
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -6,6 +6,7 @@ using Sodium;
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
@ -23,8 +24,25 @@ using XIVChatCommon.Message.Server;
|
|||
|
||||
namespace XIVChatPlugin {
|
||||
public class Server : IDisposable {
|
||||
private const int MaxMessageLength = 500;
|
||||
|
||||
private static readonly string[] PublicPrefixes = {
|
||||
"/t ",
|
||||
"/tell ",
|
||||
"/reply ",
|
||||
"/r ",
|
||||
"/say ",
|
||||
"/s ",
|
||||
"/shout ",
|
||||
"/sh ",
|
||||
"/yell ",
|
||||
"/y ",
|
||||
};
|
||||
|
||||
private readonly Plugin _plugin;
|
||||
|
||||
private readonly Stopwatch _sendWatch = new();
|
||||
|
||||
private readonly CancellationTokenSource _tokenSource = new();
|
||||
private readonly ConcurrentQueue<string> _toGame = new();
|
||||
|
||||
|
@ -50,11 +68,13 @@ namespace XIVChatPlugin {
|
|||
private const int MaxMessageSize = 128_000;
|
||||
|
||||
public Server(Plugin plugin) {
|
||||
this._plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
|
||||
this._plugin = plugin;
|
||||
if (this._plugin.Config.KeyPair == null) {
|
||||
this.RegenerateKeyPair();
|
||||
}
|
||||
|
||||
this._sendWatch.Start();
|
||||
|
||||
this._plugin.Functions.ReceiveFriendList += this.OnReceiveFriendList;
|
||||
}
|
||||
|
||||
|
@ -194,7 +214,7 @@ namespace XIVChatPlugin {
|
|||
|
||||
if (sender.Payloads.Count > 0) {
|
||||
var format = this.FormatFor(chatCode.Type);
|
||||
if (format != null && format.IsPresent) {
|
||||
if (format is { IsPresent: true }) {
|
||||
chunks.Add(new TextChunk(format.Before) {
|
||||
FallbackColour = colour,
|
||||
});
|
||||
|
@ -251,10 +271,25 @@ namespace XIVChatPlugin {
|
|||
client.Queue.Writer.TryWrite(new Availability(available));
|
||||
}
|
||||
|
||||
int time;
|
||||
if (this._toGame.TryPeek(out var peek) && PublicPrefixes.Any(prefix => peek.StartsWith(prefix))) {
|
||||
time = 1_000;
|
||||
} else if (this._currentChannel is InputChannel.Tell or InputChannel.Say or InputChannel.Shout or InputChannel.Yell) {
|
||||
time = 1_000;
|
||||
} else {
|
||||
time = 250;
|
||||
}
|
||||
|
||||
if (this._sendWatch.Elapsed < TimeSpan.FromMilliseconds(time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._toGame.TryDequeue(out var message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendWatch.Restart();
|
||||
|
||||
this._plugin.Functions.ProcessChatBox(message);
|
||||
}
|
||||
|
||||
|
@ -575,7 +610,7 @@ namespace XIVChatPlugin {
|
|||
chunks.Add(new TextChunk(text) {
|
||||
FallbackColour = defaultColour,
|
||||
Foreground = foreground.Count > 0 ? foreground.Peek() : (uint?) null,
|
||||
Glow = glow.Count > 0 ? glow.Peek() : (uint?) null,
|
||||
Glow = glow.Count > 0 ? glow.Peek() : null,
|
||||
Italic = italic,
|
||||
});
|
||||
}
|
||||
|
@ -643,9 +678,7 @@ namespace XIVChatPlugin {
|
|||
private IEnumerable<ServerMessage> MessagesAfter(DateTime time) => this._backlog.Where(msg => msg.Timestamp > time).ToArray();
|
||||
|
||||
private static IEnumerable<string> Wrap(string input) {
|
||||
const int limit = 500;
|
||||
|
||||
if (input.Length <= limit) {
|
||||
if (input.Length <= MaxMessageLength) {
|
||||
return new[] {
|
||||
input,
|
||||
};
|
||||
|
@ -656,59 +689,22 @@ namespace XIVChatPlugin {
|
|||
var space = input.IndexOf(' ');
|
||||
if (space != -1) {
|
||||
prefix = input.Substring(0, space);
|
||||
input = input.Substring(space + 1);
|
||||
}
|
||||
}
|
||||
|
||||
var parts = new List<string>();
|
||||
|
||||
var builder = new StringBuilder(limit);
|
||||
|
||||
foreach (var word in input.Split(' ')) {
|
||||
if (word.Length > limit) {
|
||||
var wordParts = (int) Math.Ceiling((float) word.Length / limit);
|
||||
for (var i = 0; i < wordParts; i++) {
|
||||
var start = i == 0 ? 0 : (i * limit);
|
||||
var partLength = limit;
|
||||
if (prefix.Length != 0) {
|
||||
start = start == 0 ? 0 : (start - (prefix.Length + 1) * i);
|
||||
partLength = partLength - prefix.Length - 1;
|
||||
// handle wrapping tells
|
||||
if (prefix is "/tell" or "/t") {
|
||||
var tellSpace = input.IndexOfCount(' ', 3);
|
||||
if (tellSpace != -1) {
|
||||
prefix = input.Substring(0, tellSpace);
|
||||
input = input.Substring(tellSpace + 1);
|
||||
}
|
||||
|
||||
var part = word.Length - start < partLength ? word.Substring(start) : word.Substring(start, partLength);
|
||||
if (part.Length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prefix.Length != 0) {
|
||||
part = prefix + " " + part;
|
||||
}
|
||||
|
||||
parts.Add(part);
|
||||
} else {
|
||||
input = input.Substring(space + 1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (builder.Length + word.Length > limit) {
|
||||
parts.Add(builder.ToString().TrimEnd(' '));
|
||||
builder.Clear();
|
||||
}
|
||||
|
||||
if (builder.Length == 0 && prefix.Length != 0) {
|
||||
builder.Append(prefix);
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
builder.Append(word);
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
if (builder.Length != 0) {
|
||||
parts.Add(builder.ToString().TrimEnd(' '));
|
||||
}
|
||||
|
||||
return parts.ToArray();
|
||||
return NativeTools.Wrap(input, MaxMessageLength)
|
||||
.Select(text => $"{prefix} {text}")
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private void BroadcastMessage(Encodable message) {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XIVChatPlugin {
|
||||
internal static class Util {
|
||||
public static int IndexOfCount(this string source, char toFind, int position) {
|
||||
var index = -1;
|
||||
for (var i = 0; i < position; i++) {
|
||||
index = source.IndexOf(toFind, index + 1);
|
||||
|
||||
if (index == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public static byte[] Terminate(this byte[] bytes) {
|
||||
var terminated = new byte[bytes.Length + 1];
|
||||
Array.Copy(bytes, terminated, bytes.Length);
|
||||
terminated[terminated.Length - 1] = 0;
|
||||
return terminated;
|
||||
}
|
||||
|
||||
public static unsafe byte[] ReadTerminated(byte* mem) {
|
||||
var bytes = new List<byte>();
|
||||
while (*mem != 0) {
|
||||
bytes.Add(*mem);
|
||||
mem += 1;
|
||||
}
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,25 +6,26 @@
|
|||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>1.5.1</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud, Version=5.2.4.2, Culture=neutral, PublicKeyToken=null">
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGui.NET, Version=1.72.0.0, Culture=neutral, PublicKeyToken=null">
|
||||
<Reference Include="ImGui.NET">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||
<Reference Include="ImGuiScene">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
|
@ -40,7 +41,8 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\XIVChatCommon\XIVChatCommon.csproj"/>
|
||||
</ItemGroup>
|
||||
<Target Name="CopyLibsodium" AfterTargets="AfterBuild">
|
||||
<Target Name="CopyNativeLibraries" AfterTargets="AfterBuild">
|
||||
<Copy SourceFiles="Resources\lib\libsodium.dll" DestinationFolder="$(OutDir)"/>
|
||||
<Copy SourceFiles="Resources\lib\xivchat_native_tools.dll" DestinationFolder="$(OutDir)"/>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
Loading…
Reference in New Issue