2024-02-02 16:44:28 +00:00

111 lines
3.5 KiB
C#

using System.Net.WebSockets;
using Newtonsoft.Json;
using Nostr.Client.Identifiers;
using Nostr.Client.Json;
using Nostr.Client.Messages;
using Nostr.Client.Utils;
using NostrRelay;
using NostrServices.Client;
using NostrServices.Services;
using ProtoBuf;
using StackExchange.Redis;
namespace NostrServices;
public static class Extensions
{
public static string? GetFirstTagValue(this CompactEvent ev, string key)
{
return ev.Tags.FirstOrDefault(a => a.Key == key)?.Values[0];
}
public static async Task<bool> SetAsync<T>(this IDatabase db, RedisKey key, T val, TimeSpan? expire = null)
{
using var ms = new MemoryStream();
Serializer.Serialize(ms, val);
return await db.StringSetAsync(key, ms.ToArray(), expire);
}
public static async Task<T?> GetAsync<T>(this IDatabase db, RedisKey key) where T : class
{
var data = await db.StringGetAsync(key);
if (data is {HasValue: true, IsNullOrEmpty: false})
{
return Serializer.Deserialize<T>(((byte[])data!).AsSpan());
}
return default;
}
public static NostrIdentifier ToIdentifier(this NostrEvent ev)
{
if ((long)ev.Kind is >= 30_000 and < 40_000)
{
return new NostrAddressIdentifier(ev.Tags!.FindFirstTagValue("d")!, ev.Pubkey!, [], ev.Kind);
}
return new NostrEventIdentifier(ev.Id!, ev.Pubkey, [], ev.Kind);
}
public static NostrIdentifier ToIdentifier(this CompactEvent ev)
{
if (ev.Kind is >= 30_000 and < 40_000)
{
return new NostrAddressIdentifier(ev.GetFirstTagValue("d")!, ev.PubKey.ToHex(), [], (NostrKind)ev.Kind);
}
return new NostrEventIdentifier(ev.Id.ToHex(), ev.PubKey.ToHex(), [], (NostrKind)ev.Kind);
}
public static NostrIdentifier ToIdentifier(this CompactProfile px)
{
return new NostrProfileIdentifier(px.PubKey.ToHex(), []);
}
public static async Task<NostrIdentifier?> TryParseIdentifier(string id)
{
NostrIdentifier? nout;
if ((NostrIdentifierParser.TryParse(id, out nout) && nout != default) ||
(nout = NostrBareIdentifier.Parse(id)) != default ||
(nout = await TryParseNip05(id, default)) != default)
{
return nout;
}
return default;
}
private static async Task<NostrIdentifier?> TryParseNip05(string id, CancellationToken cts)
{
try
{
using var client = new HttpClient();
if (id.Contains("@"))
{
var idSplit = id.Split("@");
var url = new Uri($"https://{idSplit[1]}/.well-known/nostr.json?name={Uri.EscapeDataString(idSplit[0])}");
var json = await client.GetStringAsync(url, cts);
var parsed = JsonConvert.DeserializeObject<NostrJson>(json);
var match = parsed?.Names?.FirstOrDefault(a => a.Key.Equals(idSplit[0], StringComparison.CurrentCultureIgnoreCase));
if (match.HasValue && !string.IsNullOrEmpty(match.Value.Value))
{
return new NostrProfileIdentifier(match.Value.Value.ToLower(), null);
}
}
}
catch (Exception ex)
{
//_logger.LogWarning("Failed to parse nostr address {id} {msg}", id, ex.Message);
}
return default;
}
class NostrJson
{
[JsonProperty("names")]
public Dictionary<string, string>? Names { get; init; } = new();
}
}