119 lines
3.4 KiB
C#
119 lines
3.4 KiB
C#
using FASTER.core;
|
|
using Nostr.Client.Messages;
|
|
using Nostr.Client.Requests;
|
|
using NostrRelay;
|
|
|
|
namespace FASTERlay;
|
|
|
|
public class FasterRelay : INostrRelay, IDisposable
|
|
{
|
|
private readonly ClientSession<byte[], byte[], byte[], byte[], Empty, SimpleFunctions<byte[], byte[]>> _session;
|
|
private readonly ClientSession<string, byte[], byte[], byte[], Empty, TagSetFunctions> _tagsSession;
|
|
|
|
public FasterRelay(NostrStore store)
|
|
{
|
|
_session = store.MainStore.For(new SimpleFunctions<byte[], byte[]>()).NewSession<SimpleFunctions<byte[], byte[]>>();
|
|
_tagsSession = store.TagStore.For(new TagSetFunctions()).NewSession<TagSetFunctions>();
|
|
}
|
|
|
|
public ValueTask<bool> AcceptConnection(NostrClientContext context)
|
|
{
|
|
return ValueTask.FromResult(true);
|
|
}
|
|
|
|
public async IAsyncEnumerable<NostrEvent> HandleRequest(NostrClientContext context, NostrRequest req)
|
|
{
|
|
var iter = _session.Iterate();
|
|
var filter = req.NostrFilter;
|
|
while (iter.GetNext(out _, out _, out var bytes))
|
|
{
|
|
var ev = NostrBuf.Decode(bytes);
|
|
if (ev != default)
|
|
{
|
|
if (filter.Since.HasValue && ev.CreatedAt <= filter.Since)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (filter.Until.HasValue && ev.CreatedAt > filter.Until)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (filter.Ids != default && !filter.Ids.Contains(ev.Id!))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (filter.Authors != default && !filter.Authors.Contains(ev.Pubkey!))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (filter.Kinds != default && !filter.Kinds.Contains(ev.Kind))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
yield return ev;
|
|
}
|
|
}
|
|
}
|
|
|
|
public async ValueTask<HandleEventResponse> HandleEvent(NostrClientContext context, NostrEvent ev)
|
|
{
|
|
await SaveEvent(ev);
|
|
return new(true, null);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_session.Dispose();
|
|
_tagsSession.Dispose();
|
|
}
|
|
|
|
private async Task SaveEvent(NostrEvent ev)
|
|
{
|
|
var idBytes = Convert.FromHexString(ev.Id!);
|
|
var eventBytes = NostrBuf.Encode(ev);
|
|
|
|
await _session.UpsertAsync(ref idBytes, ref eventBytes);
|
|
|
|
var indexTags = ev.Tags!.Where(a => a.TagIdentifier.Length == 1);
|
|
foreach (var tag in indexTags)
|
|
{
|
|
var kIndex = $"{tag.TagIdentifier}.{tag.AdditionalData[0]}";
|
|
await _tagsSession.RMWAsync(ref kIndex, ref idBytes);
|
|
}
|
|
|
|
await _session.CompletePendingAsync();
|
|
await _tagsSession.CompletePendingAsync();
|
|
}
|
|
|
|
private async Task<NostrEvent?> GetEvent(string id)
|
|
{
|
|
var idBytes = Convert.FromHexString(id);
|
|
var read = await _session.ReadAsync(ref idBytes);
|
|
if (read.Status.Found)
|
|
{
|
|
var data = read.Complete().output;
|
|
return NostrBuf.Decode(data);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
private class TagSetFunctions : SimpleFunctions<string, byte[]>
|
|
{
|
|
public TagSetFunctions() : base((l, r) =>
|
|
{
|
|
var output = new byte[l.Length + r.Length];
|
|
Buffer.BlockCopy(l, 0, output, 0, l.Length);
|
|
Buffer.BlockCopy(r, 0, output, l.Length, r.Length);
|
|
return output;
|
|
})
|
|
{
|
|
}
|
|
}
|
|
}
|