diff --git a/NostrServices.Client/NostrServices.Client.csproj b/NostrServices.Client/NostrServices.Client.csproj index 800f194..91c914a 100644 --- a/NostrServices.Client/NostrServices.Client.csproj +++ b/NostrServices.Client/NostrServices.Client.csproj @@ -6,7 +6,7 @@ enable true https://git.v0l.io/Kieran/NostrServices - 1.0.1 + 1.0.2 Kieran Client wrapper for https://nostr.api.v0l.io https://git.v0l.io/Kieran/NostrServices @@ -19,4 +19,8 @@ + + + + diff --git a/NostrServices.Client/NostrServicesClient.cs b/NostrServices.Client/NostrServicesClient.cs index 927a2c6..ffe12cc 100644 --- a/NostrServices.Client/NostrServicesClient.cs +++ b/NostrServices.Client/NostrServicesClient.cs @@ -1,6 +1,8 @@ +using System.Net.Http.Headers; using Newtonsoft.Json; using Nostr.Client.Json; using Nostr.Client.Messages; +using NostrServices.Model; namespace NostrServices.Client; @@ -15,54 +17,47 @@ public class NostrServicesClient _client.Timeout = TimeSpan.FromSeconds(60); } - public async Task Profile(string id) + public async Task Profile(string id) { - var rsp = await _client.GetAsync($"/api/v1/export/profile/{id}"); - if (rsp.IsSuccessStatusCode) - { - var json = await rsp.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); - } - - return default; + return await Get(HttpMethod.Get, $"/api/v1/export/profile/{id}"); } public async Task Event(string id) { - var rsp = await _client.GetAsync($"/api/v1/export/{id}"); + return await Get(HttpMethod.Get, $"/api/v1/export/{id}"); + } + + public async Task LinkPreview(string url) + { + return await Get(HttpMethod.Get, $"/api/v1/preview?url={Uri.EscapeDataString(url)}"); + } + + public async Task?> CloseRelays(double lat, double lon, int count = 5) + { + return await Get>(HttpMethod.Post, $"/api/v1/relays?count={count}", new {lat, lon}); + } + + public async Task?> TopRelays(int count = 5) + { + return await Get>(HttpMethod.Get, $"/api/v1/relays/top?count={count}"); + } + + private async Task Get(HttpMethod method, string path, object? body = null) + { + var req = new HttpRequestMessage(method, path); + if (body != null) + { + req.Content = new StringContent(JsonConvert.SerializeObject(body, NostrSerializer.Settings), + new MediaTypeHeaderValue("application/json")); + } + + var rsp = await _client.SendAsync(req); if (rsp.IsSuccessStatusCode) { var json = await rsp.Content.ReadAsStringAsync(); - return NostrJson.Deserialize(json); + return JsonConvert.DeserializeObject(json, NostrSerializer.Settings); } return default; } - - public class NostrProfile - { - [JsonProperty("pubkey")] - public byte[] PubKey { get; init; } = null!; - - [JsonProperty("name")] - public string? Name { get; init; } - - [JsonProperty("about")] - public string? About { get; init; } - - [JsonProperty("picture")] - public string? Picture { get; init; } - - [JsonProperty("nip05")] - public string? Nip05 { get; init; } - - [JsonProperty("lud16")] - public string? LightningAddress { get; init; } - - [JsonProperty("banner")] - public string? Banner { get; init; } - - [JsonProperty("created")] - public DateTime Created { get; init; } - } } diff --git a/NostrServices.Model/LinkPreviewData.cs b/NostrServices.Model/LinkPreviewData.cs new file mode 100644 index 0000000..92fdf01 --- /dev/null +++ b/NostrServices.Model/LinkPreviewData.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; +using ProtoBuf; + +namespace NostrServices.Model; + +[ProtoContract] +public class LinkPreviewData +{ + [ProtoMember(1)] + [JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public List> OgTags { get; init; } = new(); + + [ProtoIgnore] + [JsonProperty("og_tags")] + public List OgTagsJson => OgTags.Select(a => new[] {a.Key, a.Value}).ToList(); + + [ProtoMember(2)] + [JsonProperty("title")] + public string? Title { get; init; } + + [ProtoMember(3)] + [JsonProperty("description")] + public string? Description { get; init; } + + [ProtoMember(4)] + [JsonProperty("image")] + public string? Image { get; init; } +} diff --git a/NostrServices.Model/NostrServices.Model.csproj b/NostrServices.Model/NostrServices.Model.csproj new file mode 100644 index 0000000..076b41a --- /dev/null +++ b/NostrServices.Model/NostrServices.Model.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/NostrServices.Model/Profile.cs b/NostrServices.Model/Profile.cs new file mode 100644 index 0000000..5cfbd1b --- /dev/null +++ b/NostrServices.Model/Profile.cs @@ -0,0 +1,66 @@ +using NBitcoin.JsonConverters; +using Newtonsoft.Json; +using Nostr.Client.Json; +using Nostr.Client.Messages; +using Nostr.Client.Messages.Metadata; +using ProtoBuf; + +namespace NostrServices.Model; + +[ProtoContract] +public class CompactProfile +{ + [ProtoMember(1)] + [JsonProperty("pubkey")] + [JsonConverter(typeof(HexJsonConverter))] + public byte[] PubKey { get; init; } = null!; + + [ProtoMember(2)] + [JsonProperty("name")] + public string? Name { get; init; } + + [ProtoMember(3)] + [JsonProperty("about")] + public string? About { get; init; } + + [ProtoMember(4)] + [JsonProperty("picture")] + public string? Picture { get; init; } + + [ProtoMember(5)] + [JsonProperty("nip05")] + public string? Nip05 { get; init; } + + [ProtoMember(6)] + [JsonProperty("lud16")] + public string? Lud16 { get; init; } + + [ProtoMember(7)] + [JsonProperty("banner")] + public string? Banner { get; init; } + + [ProtoMember(8)] + [JsonProperty("created")] + public DateTime Created { get; init; } + + public static CompactProfile? FromNostrEvent(NostrEvent ev) + { + var meta = NostrJson.Deserialize(ev.Content); + if (meta != default) + { + return new() + { + PubKey = Convert.FromHexString(ev.Pubkey!), + Name = meta.Name, + About = meta.About, + Picture = meta.Picture, + Nip05 = meta.Nip05, + Lud16 = meta.Lud16, + Banner = meta.Banner, + Created = ev.CreatedAt!.Value + }; + } + + return default; + } +} diff --git a/NostrServices.Model/RelayDistance.cs b/NostrServices.Model/RelayDistance.cs new file mode 100644 index 0000000..28548df --- /dev/null +++ b/NostrServices.Model/RelayDistance.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json; + +namespace NostrServices.Model; + +public class RelayDistance +{ + [JsonProperty("url")] + public Uri Url { get; init; } = null!; + + [JsonProperty("distance")] + public double Distance { get; init; } + + [JsonProperty("users")] + public long? Users { get; init; } + + [JsonProperty("country")] + public string? Country { get; init; } + + [JsonProperty("city")] + public string? City { get; init; } + + [JsonProperty("description")] + public string? Description { get; init; } + + [JsonProperty("is_paid")] + public bool? IsPaid { get; init; } + + [JsonProperty("is_write_restricted")] + public bool? IsWriteRestricted { get; init; } + + [JsonProperty("lat")] + public double? Latitude { get; init; } + + [JsonProperty("lon")] + public double? Longitude { get; init; } +} diff --git a/NostrServices.sln b/NostrServices.sln index 4ffb49f..b134b21 100644 --- a/NostrServices.sln +++ b/NostrServices.sln @@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PayForReactions", "PayForRe EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NostrServices.Client", "NostrServices.Client\NostrServices.Client.csproj", "{17CA7036-95BC-4851-BAB1-419996EA2761}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NostrServices.Model", "NostrServices.Model\NostrServices.Model.csproj", "{70E7A5B7-005F-4EEE-9C0D-7FFB1389ED2A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -49,5 +51,9 @@ Global {17CA7036-95BC-4851-BAB1-419996EA2761}.Debug|Any CPU.Build.0 = Debug|Any CPU {17CA7036-95BC-4851-BAB1-419996EA2761}.Release|Any CPU.ActiveCfg = Release|Any CPU {17CA7036-95BC-4851-BAB1-419996EA2761}.Release|Any CPU.Build.0 = Release|Any CPU + {70E7A5B7-005F-4EEE-9C0D-7FFB1389ED2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70E7A5B7-005F-4EEE-9C0D-7FFB1389ED2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70E7A5B7-005F-4EEE-9C0D-7FFB1389ED2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70E7A5B7-005F-4EEE-9C0D-7FFB1389ED2A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/NostrServices/Controllers/ImportController.cs b/NostrServices/Controllers/ImportController.cs index 83a482e..b16bdf2 100644 --- a/NostrServices/Controllers/ImportController.cs +++ b/NostrServices/Controllers/ImportController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Nostr.Client.Json; using Nostr.Client.Messages; +using NostrServices.Model; using NostrServices.Services; namespace NostrServices.Controllers; diff --git a/NostrServices/Controllers/LinkPreviewController.cs b/NostrServices/Controllers/LinkPreviewController.cs index b204d29..85fcbcc 100644 --- a/NostrServices/Controllers/LinkPreviewController.cs +++ b/NostrServices/Controllers/LinkPreviewController.cs @@ -3,8 +3,7 @@ using System.Text; using AngleSharp; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; -using Newtonsoft.Json; -using ProtoBuf; +using NostrServices.Model; using StackExchange.Redis; namespace NostrServices.Controllers; @@ -102,28 +101,3 @@ public class LinkPreviewController : Controller return default; } } - -[ProtoContract] -public class LinkPreviewData -{ - [ProtoMember(1)] - [JsonIgnore] - [System.Text.Json.Serialization.JsonIgnore] - public List> OgTags { get; init; } = new(); - - [ProtoIgnore] - [JsonProperty("og_tags")] - public List OgTagsJson => OgTags.Select(a => new[] {a.Key, a.Value}).ToList(); - - [ProtoMember(2)] - [JsonProperty("title")] - public string? Title { get; init; } - - [ProtoMember(3)] - [JsonProperty("description")] - public string? Description { get; init; } - - [ProtoMember(4)] - [JsonProperty("image")] - public string? Image { get; init; } -} diff --git a/NostrServices/Controllers/OpenGraphController.cs b/NostrServices/Controllers/OpenGraphController.cs index 96febaa..7245b11 100644 --- a/NostrServices/Controllers/OpenGraphController.cs +++ b/NostrServices/Controllers/OpenGraphController.cs @@ -1,10 +1,10 @@ using AngleSharp; using AngleSharp.Dom; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; using Nostr.Client.Identifiers; using Nostr.Client.Messages; using Nostr.Client.Utils; +using NostrServices.Model; using NostrServices.Services; using ProtoBuf; @@ -18,14 +18,11 @@ public class OpenGraphController : Controller { private readonly ILogger _logger; private readonly RedisStore _redisStore; - private readonly HttpClient _httpClient; - public OpenGraphController(ILogger logger, RedisStore redisStore, HttpClient httpClient) + public OpenGraphController(ILogger logger, RedisStore redisStore) { _logger = logger; _redisStore = redisStore; - _httpClient = httpClient; - _httpClient.Timeout = TimeSpan.FromSeconds(2); } /// @@ -118,7 +115,7 @@ public class OpenGraphController : Controller { case (long)NostrKind.LiveEvent: { - var host = ev.Tags.FirstOrDefault(a => a is {Key: "p", Values: [_, "host", ..]})?.Values[0] ?? ev.PubKey.ToHex(); + var host = ev.Tags.FirstOrDefault(a => a.Key == "p" && a.Values[1] == "host")?.Values[0] ?? ev.PubKey.ToHex(); var hostProfile = await _redisStore.GetProfile(host); var hostName = hostProfile?.Name ?? profile?.Name ?? "Nostrich"; var stream = ev.GetFirstTagValue("streaming") ?? ev.GetFirstTagValue("recording") ?? ""; diff --git a/NostrServices/Controllers/RelaysController.cs b/NostrServices/Controllers/RelaysController.cs index bd4fa4f..ebc83a3 100644 --- a/NostrServices/Controllers/RelaysController.cs +++ b/NostrServices/Controllers/RelaysController.cs @@ -20,7 +20,7 @@ public class RelaysController : Controller const int distance = 5000 * 1000; // 5,000km var relays = await _database.FindCloseRelays(pos.Lat, pos.Lon, distance, count); - return Json(relays.Select(RelayDistance.FromDistance)); + return Json(relays.Select(a => a.FromDistance())); } [HttpGet("top")] @@ -30,7 +30,7 @@ public class RelaysController : Controller var topSelected = top.OrderByDescending(a => a.Value).Take(count); var infos = await Task.WhenAll(topSelected.Select(a => _database.GetRelay(a.Key))); - return Json(infos.Where(a => a != default).Select(a => RelayDistance.FromInfo(a!))); + return Json(infos.Where(a => a != default).Select(a => a!.FromInfo())); } } @@ -43,42 +43,12 @@ public class LatLonReq public double Lon { get; init; } } -class RelayDistance +public static class RelayDistanceExtension { - [JsonProperty("url")] - public Uri Url { get; init; } = null!; - - [JsonProperty("distance")] - public double Distance { get; init; } - - [JsonProperty("users")] - public long? Users { get; init; } - - [JsonProperty("country")] - public string? Country { get; init; } - - [JsonProperty("city")] - public string? City { get; init; } - - [JsonProperty("description")] - public string? Description { get; init; } - - [JsonProperty("is_paid")] - public bool? IsPaid { get; init; } - - [JsonProperty("is_write_restricted")] - public bool? IsWriteRestricted { get; init; } - - [JsonProperty("lat")] - public double? Latitude { get; init; } - - [JsonProperty("lon")] - public double? Longitude { get; init; } - - public static RelayDistance FromDistance(Services.RelayDistance x) + public static Model.RelayDistance FromDistance(this RelayDistance x) { var rp = x.Relay.Positions.FirstOrDefault(a => a.IpAddress == x.IpAddress) ?? x.Relay.Positions.First(); - return new RelayDistance() + return new Model.RelayDistance() { Url = x.Relay.Url, Distance = x.Distance, @@ -93,10 +63,10 @@ class RelayDistance }; } - public static RelayDistance FromInfo(RelayInfo x) + public static Model.RelayDistance FromInfo(this RelayInfo x) { var rp = x.Positions.First(); - return new RelayDistance() + return new Model.RelayDistance() { Url = x.Url, Distance = 0, diff --git a/NostrServices/Extensions.cs b/NostrServices/Extensions.cs index f0b4a83..328fc91 100644 --- a/NostrServices/Extensions.cs +++ b/NostrServices/Extensions.cs @@ -5,6 +5,7 @@ using Nostr.Client.Json; using Nostr.Client.Messages; using Nostr.Client.Utils; using NostrRelay; +using NostrServices.Model; using NostrServices.Services; using ProtoBuf; using StackExchange.Redis; diff --git a/NostrServices/NostrServices.csproj b/NostrServices/NostrServices.csproj index fa7c680..58f89b7 100644 --- a/NostrServices/NostrServices.csproj +++ b/NostrServices/NostrServices.csproj @@ -31,6 +31,7 @@ + diff --git a/NostrServices/Services/EventHandlers/RedisEventCache.cs b/NostrServices/Services/EventHandlers/RedisEventCache.cs index d7df081..1df314d 100644 --- a/NostrServices/Services/EventHandlers/RedisEventCache.cs +++ b/NostrServices/Services/EventHandlers/RedisEventCache.cs @@ -1,6 +1,7 @@ using NBitcoin; using Newtonsoft.Json; using Nostr.Client.Messages; +using NostrServices.Model; namespace NostrServices.Services.EventHandlers; diff --git a/NostrServices/Services/RedisStore.cs b/NostrServices/Services/RedisStore.cs index bda9da4..f00de22 100644 --- a/NostrServices/Services/RedisStore.cs +++ b/NostrServices/Services/RedisStore.cs @@ -1,11 +1,8 @@ using NBitcoin; -using NBitcoin.JsonConverters; -using Newtonsoft.Json; using Nostr.Client.Identifiers; -using Nostr.Client.Json; using Nostr.Client.Messages; -using Nostr.Client.Messages.Metadata; using Nostr.Client.Utils; +using NostrServices.Model; using ProtoBuf; using StackExchange.Redis; @@ -231,64 +228,6 @@ public class CompactEvent } } -[ProtoContract] -public class CompactProfile -{ - [ProtoMember(1)] - [JsonProperty("pubkey")] - [JsonConverter(typeof(HexJsonConverter))] - public byte[] PubKey { get; init; } = null!; - - [ProtoMember(2)] - [JsonProperty("name")] - public string? Name { get; init; } - - [ProtoMember(3)] - [JsonProperty("about")] - public string? About { get; init; } - - [ProtoMember(4)] - [JsonProperty("picture")] - public string? Picture { get; init; } - - [ProtoMember(5)] - [JsonProperty("nip05")] - public string? Nip05 { get; init; } - - [ProtoMember(6)] - [JsonProperty("lud16")] - public string? Lud16 { get; init; } - - [ProtoMember(7)] - [JsonProperty("banner")] - public string? Banner { get; init; } - - [ProtoMember(8)] - [JsonProperty("created")] - public DateTime Created { get; init; } - - public static CompactProfile? FromNostrEvent(NostrEvent ev) - { - var meta = NostrJson.Deserialize(ev.Content); - if (meta != default) - { - return new() - { - PubKey = Convert.FromHexString(ev.Pubkey!), - Name = meta.Name, - About = meta.About, - Picture = meta.Picture, - Nip05 = meta.Nip05, - Lud16 = meta.Lud16, - Banner = meta.Banner, - Created = ev.CreatedAt!.Value - }; - } - - return default; - } -} - [ProtoContract] public class RelayDistance { diff --git a/PayForReactions/ReactionQueue.cs b/PayForReactions/ReactionQueue.cs index 3c9ee6d..90c8284 100644 --- a/PayForReactions/ReactionQueue.cs +++ b/PayForReactions/ReactionQueue.cs @@ -105,7 +105,7 @@ public class ReactionQueueWorker : BackgroundService continue; } - var parsedTarget = Lnurl.ParseLnUrl(senderProfile.LightningAddress ?? ""); + var parsedTarget = Lnurl.ParseLnUrl(senderProfile.Lud16 ?? ""); if (parsedTarget == default) { _logger.LogInformation( @@ -115,7 +115,7 @@ public class ReactionQueueWorker : BackgroundService continue; } - _logger.LogInformation("Starting payment for {name} - {addr}", senderProfile.Name, senderProfile.LightningAddress); + _logger.LogInformation("Starting payment for {name} - {addr}", senderProfile.Name, senderProfile.Lud16); var svc = await _lnurl.LoadAsync(parsedTarget.ToString()); if (svc == default) {