74 lines
2.5 KiB
C#
74 lines
2.5 KiB
C#
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
|
|
namespace Lskj.AgentPet.Host.Core.AstrBot;
|
|
|
|
public sealed record SseFrame(string Event, string Data, string? Id);
|
|
|
|
public static class SseParser
|
|
{
|
|
public const int MaxEventCharacters = 1024 * 1024;
|
|
|
|
public static async IAsyncEnumerable<SseFrame> ReadAsync(
|
|
Stream source,
|
|
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(source);
|
|
using StreamReader reader = new(
|
|
source,
|
|
new UTF8Encoding(false, true),
|
|
detectEncodingFromByteOrderMarks: true,
|
|
bufferSize: 4096,
|
|
leaveOpen: true);
|
|
string eventName = "message";
|
|
string? id = null;
|
|
StringBuilder data = new();
|
|
|
|
while (true)
|
|
{
|
|
string? line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
|
|
if (line is null)
|
|
{
|
|
if (data.Length > 0)
|
|
yield return new SseFrame(eventName, TrimFinalNewline(data), id);
|
|
yield break;
|
|
}
|
|
if (line.Length == 0)
|
|
{
|
|
if (data.Length > 0)
|
|
yield return new SseFrame(eventName, TrimFinalNewline(data), id);
|
|
eventName = "message";
|
|
data.Clear();
|
|
continue;
|
|
}
|
|
if (line[0] == ':') continue;
|
|
|
|
int separator = line.IndexOf(':');
|
|
string field = separator < 0 ? line : line[..separator];
|
|
string value = separator < 0 ? string.Empty : line[(separator + 1)..];
|
|
if (value.StartsWith(' ')) value = value[1..];
|
|
switch (field)
|
|
{
|
|
case "event":
|
|
eventName = string.IsNullOrWhiteSpace(value) ? "message" : value;
|
|
break;
|
|
case "data":
|
|
if (data.Length + value.Length + 1 > MaxEventCharacters)
|
|
throw new HostError("astrbot_sse_event_too_large", "AstrBot SSE 单个事件超过 1 MB。 ");
|
|
data.Append(value).Append('\n');
|
|
break;
|
|
case "id":
|
|
if (!value.Contains('\0')) id = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string TrimFinalNewline(StringBuilder value)
|
|
{
|
|
return value.Length > 0 && value[value.Length - 1] == '\n'
|
|
? value.ToString(0, value.Length - 1)
|
|
: value.ToString();
|
|
}
|
|
}
|