using FASTER.core; using Nostr.Client.Messages; using Nostr.Client.Requests; using NostrRelay; namespace FASTERlay; public class FasterRelay : INostrRelay, IDisposable { private readonly ClientSession> _session; private readonly ClientSession _tagsSession; public FasterRelay(NostrStore store) { _session = store.MainStore.For(new SimpleFunctions()).NewSession>(); _tagsSession = store.TagStore.For(new TagSetFunctions()).NewSession(); } public ValueTask AcceptConnection(NostrClientContext context) { return ValueTask.FromResult(true); } public async IAsyncEnumerable 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 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 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 { 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; }) { } } }