Compare commits
10 Commits
9ba3fc118d
...
00e724ffa4
Author | SHA1 | Date | |
---|---|---|---|
00e724ffa4 | |||
2cd43ee2d6 | |||
b72957fc4b | |||
4e89ba6775 | |||
fd12f5e5ba | |||
0a42894a6f | |||
4ec40984d6 | |||
7ad353fdcd | |||
03a805e622 | |||
4e1c62ac90 |
@ -18,12 +18,13 @@ public class NostrServicesClient
|
||||
|
||||
public async Task<CompactProfile?> Profile(string id)
|
||||
{
|
||||
return await Get<CompactProfile>(HttpMethod.Get, $"/api/v1/export/profile/{id}");
|
||||
var ev = await Get<NostrEvent>(HttpMethod.Get, $"https://nostr-rs.api.v0l.io/event/0/{id}");
|
||||
return ev != default ? CompactProfile.FromNostrEvent(ev) : default;
|
||||
}
|
||||
|
||||
public async Task<NostrEvent?> Event(string id)
|
||||
{
|
||||
return await Get<NostrEvent>(HttpMethod.Get, $"/api/v1/export/{id}");
|
||||
return await Get<NostrEvent>(HttpMethod.Get, $"https://nostr-rs.api.v0l.io/event/{id}");
|
||||
}
|
||||
|
||||
public async Task<LinkPreviewData?> LinkPreview(string url)
|
||||
|
30
NostrServices/Controllers/AvatarController.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace NostrServices.Controllers;
|
||||
|
||||
[Route("/api/v1/avatar")]
|
||||
public class AvatarController : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Robohash avatar endpoints
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Available sets: `cyberpunks` / `robots` / `zombies`
|
||||
/// </remarks>
|
||||
/// <param name="set"></param>
|
||||
/// <param name="value">Any value, can contain a file extension, response is only image/webp</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{set}/{value}")]
|
||||
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 86400)]
|
||||
public IActionResult GetAvatar([FromRoute] string set, [FromRoute] string value)
|
||||
{
|
||||
var hash = System.Security.Cryptography.SHA256.HashData(
|
||||
Encoding.UTF8.GetBytes(Path.GetFileNameWithoutExtension(value)));
|
||||
var hashInt = BitConverter.ToInt32(hash);
|
||||
var dir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", set);
|
||||
var fileList = Directory.EnumerateFiles(dir, "*.webp").ToList();
|
||||
var pickImg = fileList[Math.Abs(hashInt % fileList.Count)];
|
||||
return PhysicalFile(pickImg, "image/webp");
|
||||
}
|
||||
}
|
@ -14,17 +14,8 @@ namespace NostrServices.Controllers;
|
||||
/// Add OpenGraph tags to html documents
|
||||
/// </summary>
|
||||
[Route("/api/v1/opengraph")]
|
||||
public class OpenGraphController : Controller
|
||||
public class OpenGraphController(ILogger<OpenGraphController> logger, RedisStore redisStore) : Controller
|
||||
{
|
||||
private readonly ILogger<OpenGraphController> _logger;
|
||||
private readonly RedisStore _redisStore;
|
||||
|
||||
public OpenGraphController(ILogger<OpenGraphController> logger, RedisStore redisStore)
|
||||
{
|
||||
_logger = logger;
|
||||
_redisStore = redisStore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inject opengraph tags into provided html
|
||||
/// </summary>
|
||||
@ -63,7 +54,7 @@ public class OpenGraphController : Controller
|
||||
{
|
||||
if (nid.Hrp is "nevent" or "note" or "naddr")
|
||||
{
|
||||
var ev = await _redisStore.GetEvent(nid);
|
||||
var ev = await redisStore.GetEvent(nid);
|
||||
if (ev != default)
|
||||
{
|
||||
var tags = MetaTagsToElements(await GetEventTags(ev));
|
||||
@ -75,7 +66,7 @@ public class OpenGraphController : Controller
|
||||
}
|
||||
else if (nid.Hrp is "nprofile" or "npub")
|
||||
{
|
||||
var profile = await _redisStore.GetProfile(nid.Special);
|
||||
var profile = await redisStore.GetProfile(nid.Special);
|
||||
|
||||
var meta = await GetProfileMeta(profile);
|
||||
var tags = MetaTagsToElements([
|
||||
@ -98,7 +89,7 @@ public class OpenGraphController : Controller
|
||||
}
|
||||
catch (Exception ex) when (ex is not TaskCanceledException)
|
||||
{
|
||||
_logger.LogWarning("Failed to inject event tags: {Message}", ex.ToString());
|
||||
logger.LogWarning("Failed to inject event tags: {Message}", ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,17 +100,20 @@ public class OpenGraphController : Controller
|
||||
{
|
||||
var ret = new List<KeyValuePair<string, string>>();
|
||||
|
||||
var profile = await _redisStore.GetProfile(ev.PubKey.ToHex());
|
||||
var profile = await redisStore.GetProfile(ev.PubKey.ToHex());
|
||||
var name = profile?.Name ?? "Nostrich";
|
||||
switch (ev.Kind)
|
||||
{
|
||||
case (long)NostrKind.LiveEvent:
|
||||
{
|
||||
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 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") ?? "";
|
||||
var image = ev.GetFirstTagValue("image") ?? hostProfile?.Picture ?? $"https://robohash.v0l.io/{ev.PubKey.ToHex()}.png";
|
||||
var image = ev.GetFirstTagValue("image") ??
|
||||
hostProfile?.Picture ?? $"https://robohash.v0l.io/{ev.PubKey.ToHex()}.png";
|
||||
ret.AddRange(new KeyValuePair<string, string>[]
|
||||
{
|
||||
new("og:type", "video.other"),
|
||||
@ -145,7 +139,8 @@ public class OpenGraphController : Controller
|
||||
case 1_313: // stream clip
|
||||
{
|
||||
var stream = ev.GetFirstTagValue("r")!;
|
||||
var image = ev.GetFirstTagValue("image") ?? profile?.Picture ?? $"https://robohash.v0l.io/{ev.PubKey.ToHex()}.png";
|
||||
var image = ev.GetFirstTagValue("image") ??
|
||||
profile?.Picture ?? $"https://robohash.v0l.io/{ev.PubKey.ToHex()}.png";
|
||||
ret.AddRange(new KeyValuePair<string, string>[]
|
||||
{
|
||||
new("og:type", "video.other"),
|
||||
@ -212,14 +207,23 @@ public class OpenGraphController : Controller
|
||||
|
||||
foreach (var ex in tags)
|
||||
{
|
||||
var tag = doc.CreateElement(ex.Element);
|
||||
IElement tag = doc.Head?.Children.FirstOrDefault(a =>
|
||||
{
|
||||
if (ex.Element == "meta")
|
||||
{
|
||||
var metaPropertyA = a.Attributes.FirstOrDefault(b => b.Name == "property");
|
||||
var metaPropertyB = ex.Attributes.FirstOrDefault(b => b is { Key: "property" });
|
||||
return metaPropertyA?.Value == metaPropertyB.Value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}) ?? AddNewTag(ex);
|
||||
|
||||
foreach (var attr in ex.Attributes)
|
||||
{
|
||||
tag.SetAttribute(attr.Key, attr.Value);
|
||||
}
|
||||
|
||||
doc.Head?.AppendChild(tag);
|
||||
|
||||
var isOgTitle = ex.Attributes.Any(a => a is { Key: "property", Value: "og:title" });
|
||||
if (isOgTitle && doc.Head != default)
|
||||
{
|
||||
@ -256,6 +260,15 @@ public class OpenGraphController : Controller
|
||||
descriptionTag.SetAttribute("content", ogDesc.Value);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
IElement AddNewTag(HeadElement he)
|
||||
{
|
||||
var tx = doc!.CreateElement(he.Element);
|
||||
doc.Head?.AppendChild(tx);
|
||||
return tx;
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
|
@ -35,8 +35,8 @@ public static class Program
|
||||
builder.Services.AddTransient<CacheRelay>();
|
||||
builder.Services.AddNostrEventHandlers();
|
||||
builder.Services.AddHostedService<NostrListener.NostrListenerLifetime>();
|
||||
builder.Services.AddHostedService<RelayScraperService>();
|
||||
builder.Services.AddHostedService<PubkeyStatsService>();
|
||||
//builder.Services.AddHostedService<RelayScraperService>();
|
||||
//builder.Services.AddHostedService<PubkeyStatsService>();
|
||||
|
||||
builder.Services.AddControllers().AddNewtonsoftJson(opt =>
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using NBitcoin;
|
||||
using Nostr.Client.Identifiers;
|
||||
using Nostr.Client.Json;
|
||||
using Nostr.Client.Messages;
|
||||
using Nostr.Client.Utils;
|
||||
using NostrServices.Client;
|
||||
@ -13,56 +14,67 @@ public class RedisStore
|
||||
private static readonly TimeSpan DefaultExpire = TimeSpan.FromDays(90);
|
||||
private readonly IDatabase _database;
|
||||
private readonly ConnectionMultiplexer _connection;
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public RedisStore(ConnectionMultiplexer connection)
|
||||
public RedisStore(ConnectionMultiplexer connection, HttpClient client)
|
||||
{
|
||||
_connection = connection;
|
||||
_client = client;
|
||||
_database = connection.GetDatabase();
|
||||
}
|
||||
|
||||
public async Task<bool> StoreEvent(CompactEvent ev, TimeSpan? expiry = null)
|
||||
{
|
||||
if (await _database.SetAsync(EventKey(ev.ToIdentifier()), ev, expiry ?? DefaultExpire))
|
||||
{
|
||||
return await _database.SetAddAsync(UserEventsKey(ev.PubKey.ToHex()), ev.Id);
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<CompactEvent?> GetEvent(NostrIdentifier id)
|
||||
{
|
||||
var ek = EventKey(id);
|
||||
var ev = await _database.GetAsync<CompactEvent>(ek);
|
||||
if (ev != default)
|
||||
try
|
||||
{
|
||||
await _database.KeyExpireAsync(ek, DefaultExpire);
|
||||
var ev = await _client.GetAsync($"https://nostr-rs.api.v0l.io/event/{id.ToBech32()}");
|
||||
if (ev.IsSuccessStatusCode)
|
||||
{
|
||||
var obj = NostrJson.Deserialize<NostrEvent>(await ev.Content.ReadAsStringAsync());
|
||||
if (obj != default)
|
||||
{
|
||||
return CompactEvent.FromNostrEvent(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return ev;
|
||||
return default;
|
||||
}
|
||||
|
||||
public async Task<bool> StoreProfile(CompactProfile meta, TimeSpan? expiry = null)
|
||||
{
|
||||
var oldProfile = await GetProfile(meta.PubKey.ToHex());
|
||||
if ((oldProfile?.Created ?? new DateTime()) < meta.Created)
|
||||
{
|
||||
return await _database.SetAsync(ProfileKey(meta.PubKey.ToHex()), meta, expiry ?? DefaultExpire);
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<CompactProfile?> GetProfile(string id)
|
||||
{
|
||||
var pk = ProfileKey(id);
|
||||
var profile = await _database.GetAsync<CompactProfile>(pk);
|
||||
if (profile != default)
|
||||
try
|
||||
{
|
||||
await _database.KeyExpireAsync(pk, DefaultExpire);
|
||||
var ev = await _client.GetAsync($"https://nostr-rs.api.v0l.io/event/0/{id}");
|
||||
if (ev.IsSuccessStatusCode)
|
||||
{
|
||||
var evo = NostrJson.Deserialize<NostrEvent>(await ev.Content.ReadAsStringAsync());
|
||||
if (evo != default)
|
||||
{
|
||||
return CompactProfile.FromNostrEvent(evo);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return profile;
|
||||
return default;
|
||||
}
|
||||
|
||||
public async Task<HashSet<Uri>> GetKnownRelays()
|
||||
@ -95,13 +107,17 @@ public class RedisStore
|
||||
if ((old?.Created ?? 0) < relays.Created)
|
||||
{
|
||||
var removed = old?.Relays.Where(a => relays.Relays.All(b => b.Url != a.Url));
|
||||
var added = old == default ? relays.Relays : relays.Relays.Where(a => old.Relays.All(b => b.Url != a.Url)).ToList();
|
||||
var added = old == default
|
||||
? relays.Relays
|
||||
: relays.Relays.Where(a => old.Relays.All(b => b.Url != a.Url)).ToList();
|
||||
if (removed != default)
|
||||
{
|
||||
await Task.WhenAll(removed.Select(a => _database.SetRemoveAsync(RelayUsersKey(a.Url), Convert.FromHexString(pubkey))));
|
||||
await Task.WhenAll(removed.Select(a =>
|
||||
_database.SetRemoveAsync(RelayUsersKey(a.Url), Convert.FromHexString(pubkey))));
|
||||
}
|
||||
|
||||
await Task.WhenAll(added.Select(a => _database.SetAddAsync(RelayUsersKey(a.Url), Convert.FromHexString(pubkey))));
|
||||
await Task.WhenAll(added.Select(a =>
|
||||
_database.SetAddAsync(RelayUsersKey(a.Url), Convert.FromHexString(pubkey))));
|
||||
await _database.SetAddAsync("relays", added.Select(a => (RedisValue)a.Url.ToString()).ToArray());
|
||||
|
||||
return await _database.SetAsync(UserRelaysKey(pubkey), relays, DefaultExpire);
|
||||
@ -136,16 +152,22 @@ public class RedisStore
|
||||
|
||||
public async Task<List<RelayDistance>> FindCloseRelays(double lat, double lon, int radius = 50_000, int count = 10)
|
||||
{
|
||||
var ret = new List<RelayDistance>();
|
||||
var geoRelays = await _database.GeoSearchAsync(RelayPositionKey(), lon, lat, new GeoSearchCircle(radius), count);
|
||||
var ret = new Dictionary<Uri, RelayDistance>();
|
||||
var geoRelays =
|
||||
await _database.GeoSearchAsync(RelayPositionKey(), lon, lat, new GeoSearchCircle(radius), count * 2);
|
||||
foreach (var gr in geoRelays)
|
||||
{
|
||||
if (ret.Count == count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var id = ((string)gr.Member!).Split('\x1');
|
||||
var u = new Uri(id[0]);
|
||||
var info = await GetRelay(u);
|
||||
if (info != default)
|
||||
if (info != default && !ret.ContainsKey(u))
|
||||
{
|
||||
ret.Add(new()
|
||||
ret.Add(u, new()
|
||||
{
|
||||
Distance = gr.Distance.HasValue ? (long)gr.Distance.Value : 0,
|
||||
Relay = info,
|
||||
@ -154,7 +176,7 @@ public class RedisStore
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return ret.Values.ToList();
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<string> EnumerateProfiles()
|
||||
|
BIN
NostrServices/wwwroot/cyberpunks/robohash_00001_.webp
Normal file
After Width: | Height: | Size: 199 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00002_.webp
Normal file
After Width: | Height: | Size: 203 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00003_.webp
Normal file
After Width: | Height: | Size: 203 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00004_.webp
Normal file
After Width: | Height: | Size: 265 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00005_.webp
Normal file
After Width: | Height: | Size: 194 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00006_.webp
Normal file
After Width: | Height: | Size: 195 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00007_.webp
Normal file
After Width: | Height: | Size: 266 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00008_.webp
Normal file
After Width: | Height: | Size: 227 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00009_.webp
Normal file
After Width: | Height: | Size: 282 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00010_.webp
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00011_.webp
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00012_.webp
Normal file
After Width: | Height: | Size: 284 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00013_.webp
Normal file
After Width: | Height: | Size: 210 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00014_.webp
Normal file
After Width: | Height: | Size: 204 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00015_.webp
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00016_.webp
Normal file
After Width: | Height: | Size: 239 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00017_.webp
Normal file
After Width: | Height: | Size: 254 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00018_.webp
Normal file
After Width: | Height: | Size: 251 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00019_.webp
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00020_.webp
Normal file
After Width: | Height: | Size: 289 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00021_.webp
Normal file
After Width: | Height: | Size: 198 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00022_.webp
Normal file
After Width: | Height: | Size: 210 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00023_.webp
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00024_.webp
Normal file
After Width: | Height: | Size: 182 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00025_.webp
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00026_.webp
Normal file
After Width: | Height: | Size: 176 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00027_.webp
Normal file
After Width: | Height: | Size: 208 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00028_.webp
Normal file
After Width: | Height: | Size: 176 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00029_.webp
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00030_.webp
Normal file
After Width: | Height: | Size: 144 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00031_.webp
Normal file
After Width: | Height: | Size: 204 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00032_.webp
Normal file
After Width: | Height: | Size: 195 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00033_.webp
Normal file
After Width: | Height: | Size: 217 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00034_.webp
Normal file
After Width: | Height: | Size: 185 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00035_.webp
Normal file
After Width: | Height: | Size: 298 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00036_.webp
Normal file
After Width: | Height: | Size: 366 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00037_.webp
Normal file
After Width: | Height: | Size: 330 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00038_.webp
Normal file
After Width: | Height: | Size: 355 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00039_.webp
Normal file
After Width: | Height: | Size: 301 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00040_.webp
Normal file
After Width: | Height: | Size: 317 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00041_.webp
Normal file
After Width: | Height: | Size: 349 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00042_.webp
Normal file
After Width: | Height: | Size: 341 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00043_.webp
Normal file
After Width: | Height: | Size: 281 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00044_.webp
Normal file
After Width: | Height: | Size: 304 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00045_.webp
Normal file
After Width: | Height: | Size: 428 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00046_.webp
Normal file
After Width: | Height: | Size: 204 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00047_.webp
Normal file
After Width: | Height: | Size: 211 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00048_.webp
Normal file
After Width: | Height: | Size: 212 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00049_.webp
Normal file
After Width: | Height: | Size: 238 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00050_.webp
Normal file
After Width: | Height: | Size: 221 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00051_.webp
Normal file
After Width: | Height: | Size: 248 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00052_.webp
Normal file
After Width: | Height: | Size: 243 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00053_.webp
Normal file
After Width: | Height: | Size: 204 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00054_.webp
Normal file
After Width: | Height: | Size: 251 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00055_.webp
Normal file
After Width: | Height: | Size: 225 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00056_.webp
Normal file
After Width: | Height: | Size: 220 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00057_.webp
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00058_.webp
Normal file
After Width: | Height: | Size: 268 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00059_.webp
Normal file
After Width: | Height: | Size: 245 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00060_.webp
Normal file
After Width: | Height: | Size: 237 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00061_.webp
Normal file
After Width: | Height: | Size: 219 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00062_.webp
Normal file
After Width: | Height: | Size: 227 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00063_.webp
Normal file
After Width: | Height: | Size: 201 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00064_.webp
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00065_.webp
Normal file
After Width: | Height: | Size: 282 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00066_.webp
Normal file
After Width: | Height: | Size: 172 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00067_.webp
Normal file
After Width: | Height: | Size: 207 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00068_.webp
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00069_.webp
Normal file
After Width: | Height: | Size: 188 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00070_.webp
Normal file
After Width: | Height: | Size: 162 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00071_.webp
Normal file
After Width: | Height: | Size: 235 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00072_.webp
Normal file
After Width: | Height: | Size: 238 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00073_.webp
Normal file
After Width: | Height: | Size: 220 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00074_.webp
Normal file
After Width: | Height: | Size: 299 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00075_.webp
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00076_.webp
Normal file
After Width: | Height: | Size: 261 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00077_.webp
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00078_.webp
Normal file
After Width: | Height: | Size: 296 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00079_.webp
Normal file
After Width: | Height: | Size: 251 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00080_.webp
Normal file
After Width: | Height: | Size: 290 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00081_.webp
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00082_.webp
Normal file
After Width: | Height: | Size: 272 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00083_.webp
Normal file
After Width: | Height: | Size: 220 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00084_.webp
Normal file
After Width: | Height: | Size: 315 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00085_.webp
Normal file
After Width: | Height: | Size: 220 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00086_.webp
Normal file
After Width: | Height: | Size: 209 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00087_.webp
Normal file
After Width: | Height: | Size: 187 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00088_.webp
Normal file
After Width: | Height: | Size: 149 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00089_.webp
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00090_.webp
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00091_.webp
Normal file
After Width: | Height: | Size: 178 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00092_.webp
Normal file
After Width: | Height: | Size: 208 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00093_.webp
Normal file
After Width: | Height: | Size: 219 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00094_.webp
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
NostrServices/wwwroot/cyberpunks/robohash_00095_.webp
Normal file
After Width: | Height: | Size: 225 KiB |