111 lines
3.5 KiB
C#
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();
|
|
}
|
|
}
|