Geohash-specific relays (#306)

* per-relay-chat

* normalize relay URL

* turn off default relays

* add relay list

* fetch relay list from the internet
This commit is contained in:
callebtc
2025-08-24 12:52:31 +02:00
committed by GitHub
parent cb7b01ad81
commit 23c397fe7b
6 changed files with 774 additions and 13 deletions
+293
View File
@@ -0,0 +1,293 @@
Relay URL,Latitude,Longitude
relay.damus.io,37.7621,-122.3971
nostr-pub.wellorder.net,45.5229,-122.9898
nostr.mom,50.4779,12.3713
nostr.slothy.win,37.7621,-122.3971
nostr.einundzwanzig.space,50.1155,8.6842
nos.lol,50.4779,12.3713
relay.nostr.band,60.1695,24.9354
no.str.cr,9.9339,-84.0849
nostr.massmux.com,50.1155,8.6842
nostr-relay.schnitzel.world,39.0437,-77.4875
relay.nostr.com.au,37.7621,-122.3971
knostr.neutrine.com,48.8534,2.3488
nostr.nodeofsven.com,47.4875,8.2965
nostr.vulpem.com,49.4542,11.0775
nostr-verif.slothy.win,37.7621,-122.3971
relay.lexingtonbitcoin.org,37.7621,-122.3971
nostr-1.nbo.angani.co,-1.2615,36.7903
relay.wellorder.net,45.5229,-122.9898
nostr.easydns.ca,43.7064,-79.3986
relay.dwadziesciajeden.pl,52.2298,21.0118
nostr.data.haus,50.4779,12.3713
nostr.einundzwanzig.space,50.1155,8.6842
nostr.mom,50.4779,12.3713
nos.lol,50.4779,12.3713
relay.nostr.band,60.1695,24.9354
nostr.massmux.com,50.1155,8.6842
nostr.vulpem.com,49.4542,11.0775
relay.damus.io,37.7621,-122.3971
nostr-pub.wellorder.net,45.5229,-122.9898
no.str.cr,9.9339,-84.0849
relay.dwadziesciajeden.pl,52.2298,21.0118
nostr.data.haus,50.4779,12.3713
relay.wellorder.net,45.5229,-122.9898
relay.nostromo.social,49.4542,11.0775
offchain.pub,34.0522,-118.2437
relay.nostr.wirednet.jp,35.9356,139.3044
relay.nostrcheck.me,37.7621,-122.3971
nostrue.com,40.8043,-74.0121
nostr-relay.schnitzel.world,39.0437,-77.4875
nproxy.kristapsk.lv,60.1695,24.9354
nostr.spaceshell.xyz,37.7621,-122.3971
nostr-dev.wellorder.net,45.5229,-122.9898
nostr-verified.wellorder.net,45.5229,-122.9898
nostr.roundrockbitcoiners.com,40.8043,-74.0121
slick.mjex.me,39.0437,-77.4875
nostr.yael.at,52.3740,4.8897
relay.primal.net,37.7621,-122.3971
nostr.oxtr.dev,50.4779,12.3713
nostr.21crypto.ch,46.5160,6.6328
nostr.liberty.fans,38.8003,-90.6265
nostr-02.dorafactory.org,1.2897,103.8501
relay.hodl.ar,-32.9468,-60.6393
nostr.middling.mydns.jp,35.8089,140.1185
nostr.namek.link,37.7621,-122.3971
nostrja-kari.heguro.com,37.7621,-122.3971
nostr.hifish.org,47.3667,8.5500
nostr.rikmeijer.nl,50.4779,12.3713
black.nostrcity.club,41.8119,-87.6873
nostr.hekster.org,37.3924,-121.9623
relay.wavlake.com,41.2619,-95.8608
nostr.sagaciousd.com,49.2497,-123.1193
nostr.fbxl.net,43.7064,-79.3986
ithurtswhenip.ee,50.7990,-1.0913
relay2.nostrchat.io,49.4542,11.0775
relay1.nostrchat.io,60.1695,24.9354
nostr-01.yakihonne.com,1.3215,103.6957
nostr.sathoarder.com,48.5839,7.7455
nostr.overmind.lol,37.7621,-122.3971
relay.verified-nostr.com,37.7621,-122.3971
purplerelay.com,50.1155,8.6842
relay.orangepill.ovh,49.0127,1.9694
nostr-relay.psfoundation.info,39.0437,-77.4875
soloco.nl,37.7621,-122.3971
relay.froth.zone,60.1695,24.9354
nostr.stakey.net,52.5250,5.7181
nostr.2b9t.xyz,34.0522,-118.2437
pyramid.fiatjaf.com,50.1155,8.6842
a.nos.lol,50.4779,12.3713
relay.magiccity.live,25.8130,-80.2320
nostr.notribe.net,40.8344,-74.1377
freelay.sovbit.host,64.1355,-21.8954
relay.credenso.cafe,43.4254,-80.5112
nostr.huszonegy.world,47.4984,19.0404
multiplexer.huszonegy.world,47.4984,19.0404
bucket.coracle.social,37.7621,-122.3971
nostr.kungfu-g.rip,33.7865,-84.4454
relay.artx.market,43.7064,-79.3986
relay.notoshi.win,13.3622,100.9835
vitor.nostr1.com,40.7143,-74.0060
nostr-02.yakihonne.com,1.3215,103.6957
nostr-03.dorafactory.org,1.2897,103.8501
n.ok0.org,-36.8485,174.7635
nostr.0x7e.xyz,47.5056,8.7241
relay.nostr.net,50.4779,12.3713
strfry.openhoofd.nl,51.5717,3.7042
relay.fountain.fm,39.0997,-94.5786
relay.usefusion.ai,38.7135,-78.1594
relay.varke.eu,52.6958,6.1944
nostr.satstralia.com,64.1355,-21.8954
relay.13room.space,37.7621,-122.3971
nostr.myshosholoza.co.za,52.3710,4.9042
nostr.carroarmato0.be,50.8517,3.6089
nostr.dbtc.link,37.7621,-122.3971
orangepiller.org,60.1695,24.9354
adre.su,59.9386,30.3141
relay.sincensura.org,37.7621,-122.3971
relay.freeplace.nl,52.3740,4.8897
bostr.bitcointxoko.com,64.1355,-21.8954
nostr.plantroon.com,50.1025,8.6299
srtrelay.c-stellar.net,37.7621,-122.3971
nostr.jfischer.org,49.4453,11.0222
nostr.novacisko.cz,52.2298,21.0118
relay.lumina.rocks,49.4453,11.0222
nostr.tavux.tech,50.9519,1.8563
relay.nostrhub.fr,50.1155,8.6842
relay.agorist.space,52.3740,4.8897
chorus.pjv.me,45.5229,-122.9898
relay.cosmicbolt.net,37.3924,-121.9623
santo.iguanatech.net,40.8344,-74.1377
relay.tagayasu.xyz,45.4112,-75.6981
relay.mostro.network,40.8344,-74.1377
relay.zone667.com,60.1695,24.9354
relay5.bitransfer.org,37.7621,-122.3971
relay.illuminodes.com,47.6062,-122.3321
relay2.angor.io,50.1155,8.6842
relay.satsdays.com,1.2897,103.8501
relay.angor.io,50.1155,8.6842
orangesync.tech,50.9333,6.9500
nostr-relay.cbrx.io,37.7621,-122.3971
relay.21e6.cz,50.0880,14.4208
nostr.chaima.info,50.1155,8.6842
relay.satlantis.io,32.8546,-79.9748
relay.digitalezukunft.cyou,45.5088,-73.5878
relay.tapestry.ninja,40.8043,-74.0121
relay.minibolt.info,37.7621,-122.3971
nostr.bilthon.dev,25.8130,-80.2320
nostr.makibisskey.work,37.7621,-122.3971
relay.mattybs.lol,37.7621,-122.3971
noxir.kpherox.dev,34.8436,135.5084
sendit.nosflare.com,37.7621,-122.3971
relay.coinos.io,37.7621,-122.3971
relay.nostraddress.com,37.7621,-122.3971
wot.nostr.party,36.1659,-86.7844
nostrelites.org,41.8500,-87.6500
relay.nostriot.com,43.7064,-79.3986
prl.plus,55.7522,37.6156
zap.watch,45.5088,-73.5878
wot.codingarena.top,50.4779,12.3713
nostr.azzamo.net,52.2284,21.0522
wot.sudocarlos.com,43.7064,-79.3986
relay.lnfi.network,45.6241,8.7851
wot.nostr.net,37.7621,-122.3971
relay.nostrdice.com,-33.8678,151.2073
wot.sebastix.social,51.3700,6.1681
wheat.happytavern.co,37.7621,-122.3971
relay.sigit.io,50.4779,12.3713
strfry.bonsai.com,37.8716,-122.2728
travis-shears-nostr-relay-v2.fly.dev,41.8119,-87.6873
satsage.xyz,37.3924,-121.9623
relay.degmods.com,50.4779,12.3713
nostr.community.ath.cx,45.5088,-73.5878
nostr.coincrowd.fund,39.0437,-77.4875
strfry.shock.network,41.8847,-88.2040
cyberspace.nostr1.com,40.7143,-74.0060
relay02.lnfi.network,39.0997,-94.5786
nostr-rs-relay.dev.fedibtc.com,39.0437,-77.4875
relay.davidebtc.me,50.1155,8.6842
wot.dtonon.com,37.7621,-122.3971
relay.goodmorningbitcoin.com,37.7621,-122.3971
articles.layer3.news,37.3394,-121.8950
bostr.syobon.net,37.7621,-122.3971
nostr.agentcampfire.com,52.3740,4.8897
nostr.thebiglake.org,32.7244,-96.6755
schnorr.me,37.7621,-122.3971
relay.wolfcoil.com,35.6090,139.7302
nostr.camalolo.com,24.1469,120.6839
nostr.tac.lol,47.4740,-122.2610
dev-relay.lnfi.network,39.0997,-94.5786
relay.bitcoinveneto.org,64.1355,-21.8954
nostr.red5d.dev,37.7621,-122.3971
relay-testnet.k8s.layer3.news,37.3394,-121.8950
promenade.fiatjaf.com,50.1155,8.6842
nostrelay.memory-art.xyz,37.7621,-122.3971
inbox.azzamo.net,52.2284,21.0522
social.proxymana.net,60.1695,24.9354
relay.netstr.io,53.3331,-6.2489
premium.primal.net,37.7621,-122.3971
nostr.lojong.info,37.7621,-122.3971
nostr-rs-relay-ishosta.phamthanh.me,37.7621,-122.3971
relay.stream.labs.h3.se,59.3294,18.0687
tollbooth.stens.dev,51.4566,7.0123
relay.chakany.systems,37.7621,-122.3971
relay.mwaters.net,50.9519,1.8563
nostr-relay.shirogaku.xyz,37.7621,-122.3971
kitchen.zap.cooking,37.7621,-122.3971
relay.arx-ccn.com,50.4779,12.3713
relay.fr13nd5.com,50.1155,8.6842
nostr.tegila.com.br,39.0437,-77.4875
relay.jeffg.fyi,43.7064,-79.3986
relay.bullishbounty.com,37.7621,-122.3971
nostr.spicyz.io,37.7621,-122.3971
relay04.lnfi.network,39.0997,-94.5786
vidono.apps.slidestr.net,48.8534,2.3488
relay03.lnfi.network,39.0997,-94.5786
communities.nos.social,40.8344,-74.1377
relay.evanverma.com,40.8344,-74.1377
nostrelay.circum.space,51.4566,7.0123
wot.brightbolt.net,47.6928,-116.7850
relayrs.notoshi.win,37.7621,-122.3971
fenrir-s.notoshi.win,37.7621,-122.3971
relay.nsnip.io,60.1695,24.9354
x.kojira.io,37.7621,-122.3971
relay.hasenpfeffr.com,39.0437,-77.4875
relay01.lnfi.network,39.0997,-94.5786
nostr.rtvslawenia.com,49.4542,11.0775
relay.g1sms.fr,43.9298,2.1480
nostr.kalf.org,52.3740,4.8897
nostr.rblb.it,37.7621,-122.3971
nostr.4rs.nl,49.4453,11.0222
relay.vrtmrz.net,37.7621,-122.3971
nostr.hoppe-relay.it.com,45.5946,-121.1787
relay-rpi.edufeed.org,49.4453,11.0222
relay.copylaradio.com,50.7990,-1.0913
relay.ru.ac.th,13.7540,100.5014
relay.bitcoinartclock.com,50.4779,12.3713
wot.downisontheup.ca,47.6062,-122.3321
nostr.coincards.com,43.7064,-79.3986
relay.etch.social,41.2619,-95.8608
relay.mess.ch,47.1345,9.0964
relay.holzeis.me,37.7621,-122.3971
relay-admin.thaliyal.com,40.8220,-74.4488
nostr.thaliyal.com,40.8220,-74.4488
strfry.felixzieger.de,50.1025,8.6299
nostr.smut.cloud,37.7621,-122.3971
r.bitcoinhold.net,37.7621,-122.3971
nostr.blankfors.se,60.1695,24.9354
portal-relay.pareto.space,49.4453,11.0222
relay.getsafebox.app,43.7064,-79.3986
relay.anzenkodo.workers.dev,37.7621,-122.3971
relay.nostrhub.tech,49.4453,11.0222
nostr.prl.plus,52.3740,4.8897
nostr-2.21crypto.ch,46.5160,6.6328
nostr.zenon.network,40.7143,-74.0060
nostr-relay.amethyst.name,35.7721,-78.6386
relayone.geektank.ai,17.1210,-61.8433
fanfares.nostr1.com,40.7143,-74.0060
wot.geektank.ai,17.1210,-61.8433
relay-dev.satlantis.io,40.8344,-74.1377
relay.siamdev.cc,13.9178,100.4240
relay.nosto.re,51.3700,6.1681
wot.soundhsa.com,39.0997,-94.5786
nostr.n7ekb.net,47.5707,-122.2221
relayone.soundhsa.com,39.0997,-94.5786
relay.puresignal.news,37.7621,-122.3971
relay.nostx.io,37.7621,-122.3971
nostr.now,35.6090,139.7302
relay.artiostr.ch,37.7621,-122.3971
relay.oldenburg.cool,50.1155,8.6842
theoutpost.life,64.1355,-21.8954
khatru.nostrver.se,51.3700,6.1681
relay.wavefunc.live,37.7915,-122.4018
nostr-relay.zimage.com,34.0522,-118.2437
relay.javi.space,43.4628,11.8807
bostr.shop,42.8865,-78.8784
relay.letsfo.com,52.2298,21.0118
alien.macneilmediagroup.com,37.7621,-122.3971
rn1.sotiras.org,37.7621,-122.3971
gnostr.com,42.6975,23.3241
relay.conduit.market,37.7621,-122.3971
relay.hivetalk.org,37.3924,-121.9623
nostr.l484.com,30.2960,-97.6396
relay.chorus.community,50.1155,8.6842
nostr-relay.moe.gift,37.7621,-122.3971
relay.nostrcal.com,37.7621,-122.3971
temp.iris.to,37.7621,-122.3971
librerelay.aaroniumii.com,37.7621,-122.3971
nostr-relay-1.trustlessenterprise.com,37.7621,-122.3971
relay.barine.co,37.7621,-122.3971
nostr.rohoss.com,48.1374,11.5755
wot.nostr.place,30.2672,-97.7431
relay.utxo.farm,34.7331,135.8183
relay.bankless.at,37.7621,-122.3971
relay.toastr.net,40.8043,-74.0121
nostr.excentered.com,52.5244,13.4105
relay.mccormick.cx,52.3740,4.8897
relay.cypherflow.ai,48.8534,2.3488
relay.laantungir.net,45.3134,-73.8725
nostr.veladan.dev,37.7621,-122.3971
nostr.tadryanom.me,37.7621,-122.3971
nostr-relay.online,37.7621,-122.3971
nostr.night7.space,50.4779,12.3713
dev-nostr.bityacht.io,25.0531,121.5264
1 Relay URL Latitude Longitude
2 relay.damus.io 37.7621 -122.3971
3 nostr-pub.wellorder.net 45.5229 -122.9898
4 nostr.mom 50.4779 12.3713
5 nostr.slothy.win 37.7621 -122.3971
6 nostr.einundzwanzig.space 50.1155 8.6842
7 nos.lol 50.4779 12.3713
8 relay.nostr.band 60.1695 24.9354
9 no.str.cr 9.9339 -84.0849
10 nostr.massmux.com 50.1155 8.6842
11 nostr-relay.schnitzel.world 39.0437 -77.4875
12 relay.nostr.com.au 37.7621 -122.3971
13 knostr.neutrine.com 48.8534 2.3488
14 nostr.nodeofsven.com 47.4875 8.2965
15 nostr.vulpem.com 49.4542 11.0775
16 nostr-verif.slothy.win 37.7621 -122.3971
17 relay.lexingtonbitcoin.org 37.7621 -122.3971
18 nostr-1.nbo.angani.co -1.2615 36.7903
19 relay.wellorder.net 45.5229 -122.9898
20 nostr.easydns.ca 43.7064 -79.3986
21 relay.dwadziesciajeden.pl 52.2298 21.0118
22 nostr.data.haus 50.4779 12.3713
23 nostr.einundzwanzig.space 50.1155 8.6842
24 nostr.mom 50.4779 12.3713
25 nos.lol 50.4779 12.3713
26 relay.nostr.band 60.1695 24.9354
27 nostr.massmux.com 50.1155 8.6842
28 nostr.vulpem.com 49.4542 11.0775
29 relay.damus.io 37.7621 -122.3971
30 nostr-pub.wellorder.net 45.5229 -122.9898
31 no.str.cr 9.9339 -84.0849
32 relay.dwadziesciajeden.pl 52.2298 21.0118
33 nostr.data.haus 50.4779 12.3713
34 relay.wellorder.net 45.5229 -122.9898
35 relay.nostromo.social 49.4542 11.0775
36 offchain.pub 34.0522 -118.2437
37 relay.nostr.wirednet.jp 35.9356 139.3044
38 relay.nostrcheck.me 37.7621 -122.3971
39 nostrue.com 40.8043 -74.0121
40 nostr-relay.schnitzel.world 39.0437 -77.4875
41 nproxy.kristapsk.lv 60.1695 24.9354
42 nostr.spaceshell.xyz 37.7621 -122.3971
43 nostr-dev.wellorder.net 45.5229 -122.9898
44 nostr-verified.wellorder.net 45.5229 -122.9898
45 nostr.roundrockbitcoiners.com 40.8043 -74.0121
46 slick.mjex.me 39.0437 -77.4875
47 nostr.yael.at 52.3740 4.8897
48 relay.primal.net 37.7621 -122.3971
49 nostr.oxtr.dev 50.4779 12.3713
50 nostr.21crypto.ch 46.5160 6.6328
51 nostr.liberty.fans 38.8003 -90.6265
52 nostr-02.dorafactory.org 1.2897 103.8501
53 relay.hodl.ar -32.9468 -60.6393
54 nostr.middling.mydns.jp 35.8089 140.1185
55 nostr.namek.link 37.7621 -122.3971
56 nostrja-kari.heguro.com 37.7621 -122.3971
57 nostr.hifish.org 47.3667 8.5500
58 nostr.rikmeijer.nl 50.4779 12.3713
59 black.nostrcity.club 41.8119 -87.6873
60 nostr.hekster.org 37.3924 -121.9623
61 relay.wavlake.com 41.2619 -95.8608
62 nostr.sagaciousd.com 49.2497 -123.1193
63 nostr.fbxl.net 43.7064 -79.3986
64 ithurtswhenip.ee 50.7990 -1.0913
65 relay2.nostrchat.io 49.4542 11.0775
66 relay1.nostrchat.io 60.1695 24.9354
67 nostr-01.yakihonne.com 1.3215 103.6957
68 nostr.sathoarder.com 48.5839 7.7455
69 nostr.overmind.lol 37.7621 -122.3971
70 relay.verified-nostr.com 37.7621 -122.3971
71 purplerelay.com 50.1155 8.6842
72 relay.orangepill.ovh 49.0127 1.9694
73 nostr-relay.psfoundation.info 39.0437 -77.4875
74 soloco.nl 37.7621 -122.3971
75 relay.froth.zone 60.1695 24.9354
76 nostr.stakey.net 52.5250 5.7181
77 nostr.2b9t.xyz 34.0522 -118.2437
78 pyramid.fiatjaf.com 50.1155 8.6842
79 a.nos.lol 50.4779 12.3713
80 relay.magiccity.live 25.8130 -80.2320
81 nostr.notribe.net 40.8344 -74.1377
82 freelay.sovbit.host 64.1355 -21.8954
83 relay.credenso.cafe 43.4254 -80.5112
84 nostr.huszonegy.world 47.4984 19.0404
85 multiplexer.huszonegy.world 47.4984 19.0404
86 bucket.coracle.social 37.7621 -122.3971
87 nostr.kungfu-g.rip 33.7865 -84.4454
88 relay.artx.market 43.7064 -79.3986
89 relay.notoshi.win 13.3622 100.9835
90 vitor.nostr1.com 40.7143 -74.0060
91 nostr-02.yakihonne.com 1.3215 103.6957
92 nostr-03.dorafactory.org 1.2897 103.8501
93 n.ok0.org -36.8485 174.7635
94 nostr.0x7e.xyz 47.5056 8.7241
95 relay.nostr.net 50.4779 12.3713
96 strfry.openhoofd.nl 51.5717 3.7042
97 relay.fountain.fm 39.0997 -94.5786
98 relay.usefusion.ai 38.7135 -78.1594
99 relay.varke.eu 52.6958 6.1944
100 nostr.satstralia.com 64.1355 -21.8954
101 relay.13room.space 37.7621 -122.3971
102 nostr.myshosholoza.co.za 52.3710 4.9042
103 nostr.carroarmato0.be 50.8517 3.6089
104 nostr.dbtc.link 37.7621 -122.3971
105 orangepiller.org 60.1695 24.9354
106 adre.su 59.9386 30.3141
107 relay.sincensura.org 37.7621 -122.3971
108 relay.freeplace.nl 52.3740 4.8897
109 bostr.bitcointxoko.com 64.1355 -21.8954
110 nostr.plantroon.com 50.1025 8.6299
111 srtrelay.c-stellar.net 37.7621 -122.3971
112 nostr.jfischer.org 49.4453 11.0222
113 nostr.novacisko.cz 52.2298 21.0118
114 relay.lumina.rocks 49.4453 11.0222
115 nostr.tavux.tech 50.9519 1.8563
116 relay.nostrhub.fr 50.1155 8.6842
117 relay.agorist.space 52.3740 4.8897
118 chorus.pjv.me 45.5229 -122.9898
119 relay.cosmicbolt.net 37.3924 -121.9623
120 santo.iguanatech.net 40.8344 -74.1377
121 relay.tagayasu.xyz 45.4112 -75.6981
122 relay.mostro.network 40.8344 -74.1377
123 relay.zone667.com 60.1695 24.9354
124 relay5.bitransfer.org 37.7621 -122.3971
125 relay.illuminodes.com 47.6062 -122.3321
126 relay2.angor.io 50.1155 8.6842
127 relay.satsdays.com 1.2897 103.8501
128 relay.angor.io 50.1155 8.6842
129 orangesync.tech 50.9333 6.9500
130 nostr-relay.cbrx.io 37.7621 -122.3971
131 relay.21e6.cz 50.0880 14.4208
132 nostr.chaima.info 50.1155 8.6842
133 relay.satlantis.io 32.8546 -79.9748
134 relay.digitalezukunft.cyou 45.5088 -73.5878
135 relay.tapestry.ninja 40.8043 -74.0121
136 relay.minibolt.info 37.7621 -122.3971
137 nostr.bilthon.dev 25.8130 -80.2320
138 nostr.makibisskey.work 37.7621 -122.3971
139 relay.mattybs.lol 37.7621 -122.3971
140 noxir.kpherox.dev 34.8436 135.5084
141 sendit.nosflare.com 37.7621 -122.3971
142 relay.coinos.io 37.7621 -122.3971
143 relay.nostraddress.com 37.7621 -122.3971
144 wot.nostr.party 36.1659 -86.7844
145 nostrelites.org 41.8500 -87.6500
146 relay.nostriot.com 43.7064 -79.3986
147 prl.plus 55.7522 37.6156
148 zap.watch 45.5088 -73.5878
149 wot.codingarena.top 50.4779 12.3713
150 nostr.azzamo.net 52.2284 21.0522
151 wot.sudocarlos.com 43.7064 -79.3986
152 relay.lnfi.network 45.6241 8.7851
153 wot.nostr.net 37.7621 -122.3971
154 relay.nostrdice.com -33.8678 151.2073
155 wot.sebastix.social 51.3700 6.1681
156 wheat.happytavern.co 37.7621 -122.3971
157 relay.sigit.io 50.4779 12.3713
158 strfry.bonsai.com 37.8716 -122.2728
159 travis-shears-nostr-relay-v2.fly.dev 41.8119 -87.6873
160 satsage.xyz 37.3924 -121.9623
161 relay.degmods.com 50.4779 12.3713
162 nostr.community.ath.cx 45.5088 -73.5878
163 nostr.coincrowd.fund 39.0437 -77.4875
164 strfry.shock.network 41.8847 -88.2040
165 cyberspace.nostr1.com 40.7143 -74.0060
166 relay02.lnfi.network 39.0997 -94.5786
167 nostr-rs-relay.dev.fedibtc.com 39.0437 -77.4875
168 relay.davidebtc.me 50.1155 8.6842
169 wot.dtonon.com 37.7621 -122.3971
170 relay.goodmorningbitcoin.com 37.7621 -122.3971
171 articles.layer3.news 37.3394 -121.8950
172 bostr.syobon.net 37.7621 -122.3971
173 nostr.agentcampfire.com 52.3740 4.8897
174 nostr.thebiglake.org 32.7244 -96.6755
175 schnorr.me 37.7621 -122.3971
176 relay.wolfcoil.com 35.6090 139.7302
177 nostr.camalolo.com 24.1469 120.6839
178 nostr.tac.lol 47.4740 -122.2610
179 dev-relay.lnfi.network 39.0997 -94.5786
180 relay.bitcoinveneto.org 64.1355 -21.8954
181 nostr.red5d.dev 37.7621 -122.3971
182 relay-testnet.k8s.layer3.news 37.3394 -121.8950
183 promenade.fiatjaf.com 50.1155 8.6842
184 nostrelay.memory-art.xyz 37.7621 -122.3971
185 inbox.azzamo.net 52.2284 21.0522
186 social.proxymana.net 60.1695 24.9354
187 relay.netstr.io 53.3331 -6.2489
188 premium.primal.net 37.7621 -122.3971
189 nostr.lojong.info 37.7621 -122.3971
190 nostr-rs-relay-ishosta.phamthanh.me 37.7621 -122.3971
191 relay.stream.labs.h3.se 59.3294 18.0687
192 tollbooth.stens.dev 51.4566 7.0123
193 relay.chakany.systems 37.7621 -122.3971
194 relay.mwaters.net 50.9519 1.8563
195 nostr-relay.shirogaku.xyz 37.7621 -122.3971
196 kitchen.zap.cooking 37.7621 -122.3971
197 relay.arx-ccn.com 50.4779 12.3713
198 relay.fr13nd5.com 50.1155 8.6842
199 nostr.tegila.com.br 39.0437 -77.4875
200 relay.jeffg.fyi 43.7064 -79.3986
201 relay.bullishbounty.com 37.7621 -122.3971
202 nostr.spicyz.io 37.7621 -122.3971
203 relay04.lnfi.network 39.0997 -94.5786
204 vidono.apps.slidestr.net 48.8534 2.3488
205 relay03.lnfi.network 39.0997 -94.5786
206 communities.nos.social 40.8344 -74.1377
207 relay.evanverma.com 40.8344 -74.1377
208 nostrelay.circum.space 51.4566 7.0123
209 wot.brightbolt.net 47.6928 -116.7850
210 relayrs.notoshi.win 37.7621 -122.3971
211 fenrir-s.notoshi.win 37.7621 -122.3971
212 relay.nsnip.io 60.1695 24.9354
213 x.kojira.io 37.7621 -122.3971
214 relay.hasenpfeffr.com 39.0437 -77.4875
215 relay01.lnfi.network 39.0997 -94.5786
216 nostr.rtvslawenia.com 49.4542 11.0775
217 relay.g1sms.fr 43.9298 2.1480
218 nostr.kalf.org 52.3740 4.8897
219 nostr.rblb.it 37.7621 -122.3971
220 nostr.4rs.nl 49.4453 11.0222
221 relay.vrtmrz.net 37.7621 -122.3971
222 nostr.hoppe-relay.it.com 45.5946 -121.1787
223 relay-rpi.edufeed.org 49.4453 11.0222
224 relay.copylaradio.com 50.7990 -1.0913
225 relay.ru.ac.th 13.7540 100.5014
226 relay.bitcoinartclock.com 50.4779 12.3713
227 wot.downisontheup.ca 47.6062 -122.3321
228 nostr.coincards.com 43.7064 -79.3986
229 relay.etch.social 41.2619 -95.8608
230 relay.mess.ch 47.1345 9.0964
231 relay.holzeis.me 37.7621 -122.3971
232 relay-admin.thaliyal.com 40.8220 -74.4488
233 nostr.thaliyal.com 40.8220 -74.4488
234 strfry.felixzieger.de 50.1025 8.6299
235 nostr.smut.cloud 37.7621 -122.3971
236 r.bitcoinhold.net 37.7621 -122.3971
237 nostr.blankfors.se 60.1695 24.9354
238 portal-relay.pareto.space 49.4453 11.0222
239 relay.getsafebox.app 43.7064 -79.3986
240 relay.anzenkodo.workers.dev 37.7621 -122.3971
241 relay.nostrhub.tech 49.4453 11.0222
242 nostr.prl.plus 52.3740 4.8897
243 nostr-2.21crypto.ch 46.5160 6.6328
244 nostr.zenon.network 40.7143 -74.0060
245 nostr-relay.amethyst.name 35.7721 -78.6386
246 relayone.geektank.ai 17.1210 -61.8433
247 fanfares.nostr1.com 40.7143 -74.0060
248 wot.geektank.ai 17.1210 -61.8433
249 relay-dev.satlantis.io 40.8344 -74.1377
250 relay.siamdev.cc 13.9178 100.4240
251 relay.nosto.re 51.3700 6.1681
252 wot.soundhsa.com 39.0997 -94.5786
253 nostr.n7ekb.net 47.5707 -122.2221
254 relayone.soundhsa.com 39.0997 -94.5786
255 relay.puresignal.news 37.7621 -122.3971
256 relay.nostx.io 37.7621 -122.3971
257 nostr.now 35.6090 139.7302
258 relay.artiostr.ch 37.7621 -122.3971
259 relay.oldenburg.cool 50.1155 8.6842
260 theoutpost.life 64.1355 -21.8954
261 khatru.nostrver.se 51.3700 6.1681
262 relay.wavefunc.live 37.7915 -122.4018
263 nostr-relay.zimage.com 34.0522 -118.2437
264 relay.javi.space 43.4628 11.8807
265 bostr.shop 42.8865 -78.8784
266 relay.letsfo.com 52.2298 21.0118
267 alien.macneilmediagroup.com 37.7621 -122.3971
268 rn1.sotiras.org 37.7621 -122.3971
269 gnostr.com 42.6975 23.3241
270 relay.conduit.market 37.7621 -122.3971
271 relay.hivetalk.org 37.3924 -121.9623
272 nostr.l484.com 30.2960 -97.6396
273 relay.chorus.community 50.1155 8.6842
274 nostr-relay.moe.gift 37.7621 -122.3971
275 relay.nostrcal.com 37.7621 -122.3971
276 temp.iris.to 37.7621 -122.3971
277 librerelay.aaroniumii.com 37.7621 -122.3971
278 nostr-relay-1.trustlessenterprise.com 37.7621 -122.3971
279 relay.barine.co 37.7621 -122.3971
280 nostr.rohoss.com 48.1374 11.5755
281 wot.nostr.place 30.2672 -97.7431
282 relay.utxo.farm 34.7331 135.8183
283 relay.bankless.at 37.7621 -122.3971
284 relay.toastr.net 40.8043 -74.0121
285 nostr.excentered.com 52.5244 13.4105
286 relay.mccormick.cx 52.3740 4.8897
287 relay.cypherflow.ai 48.8534 2.3488
288 relay.laantungir.net 45.3134 -73.8725
289 nostr.veladan.dev 37.7621 -122.3971
290 nostr.tadryanom.me 37.7621 -122.3971
291 nostr-relay.online 37.7621 -122.3971
292 nostr.night7.space 50.4779 12.3713
293 dev-nostr.bityacht.io 25.0531 121.5264
@@ -1,6 +1,7 @@
package com.bitchat.android
import android.app.Application
import com.bitchat.android.nostr.RelayDirectory
/**
* Main application class for bitchat Android
@@ -10,7 +11,7 @@ class BitchatApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize any global services or configurations
// For now, keep it simple
// Initialize relay directory (loads assets/nostr_relays.csv)
RelayDirectory.initialize(this)
}
}
@@ -9,6 +9,7 @@ package com.bitchat.android.geohash
object Geohash {
private val base32Chars = "0123456789bcdefghjkmnpqrstuvwxyz".toCharArray()
private val charToValue: Map<Char, Int> = base32Chars.withIndex().associate { it.value to it.index }
/**
* Encodes the provided coordinates into a geohash string.
@@ -62,4 +63,42 @@ object Geohash {
return geohash.toString()
}
/**
* Decodes a geohash string to the center latitude/longitude of its cell.
* @return Pair(latitude, longitude)
*/
fun decodeToCenter(geohash: String): Pair<Double, Double> {
if (geohash.isEmpty()) return 0.0 to 0.0
var latInterval = -90.0 to 90.0
var lonInterval = -180.0 to 180.0
var isEven = true
geohash.lowercase().forEach { ch ->
val cd = charToValue[ch] ?: return 0.0 to 0.0
for (mask in intArrayOf(16, 8, 4, 2, 1)) {
if (isEven) {
val mid = (lonInterval.first + lonInterval.second) / 2
if ((cd and mask) != 0) {
lonInterval = mid to lonInterval.second
} else {
lonInterval = lonInterval.first to mid
}
} else {
val mid = (latInterval.first + latInterval.second) / 2
if ((cd and mask) != 0) {
latInterval = mid to latInterval.second
} else {
latInterval = latInterval.first to mid
}
}
isEven = !isEven
}
}
val latCenter = (latInterval.first + latInterval.second) / 2
val lonCenter = (lonInterval.first + lonInterval.second) / 2
return latCenter to lonCenter
}
}
@@ -186,7 +186,12 @@ class NostrGeohashService(
)
val nostrRelayManager = NostrRelayManager.getInstance(application)
nostrRelayManager.sendEvent(event)
nostrRelayManager.sendEventToGeohash(
event = event,
geohash = channel.geohash,
includeDefaults = false,
nRelays = 5
)
Log.i(TAG, "📤 Sent geohash message to ${channel.geohash}: ${content.take(50)}")
@@ -532,7 +537,7 @@ class NostrGeohashService(
geohashSamplingJob = coroutineScope.launch {
val nostrRelayManager = NostrRelayManager.getInstance(application)
// Subscribe to each geohash for ephemeral events (kind 20000)
// Subscribe to each geohash for ephemeral events (kind 20000) using geohash-specific relays
geohashes.forEach { geohash ->
val filter = NostrFilter.geohashEphemeral(
geohash = geohash,
@@ -540,12 +545,15 @@ class NostrGeohashService(
limit = 200
)
nostrRelayManager.subscribe(
nostrRelayManager.subscribeForGeohash(
geohash = geohash,
filter = filter,
id = "geohash-$geohash",
handler = { event ->
handleUnifiedGeohashEvent(event, geohash)
}
},
includeDefaults = false,
nRelays = 5
)
Log.d(TAG, "Subscribed to geohash events for: $geohash")
@@ -858,7 +866,7 @@ class NostrGeohashService(
try {
val nostrRelayManager = NostrRelayManager.getInstance(application)
// Subscribe to geohash ephemeral events for this specific channel
// Subscribe to geohash ephemeral events for this specific channel using geohash-specific relays
val geohashSubId = "geohash-${channel.channel.geohash}"
currentGeohashSubscriptionId = geohashSubId
@@ -868,12 +876,15 @@ class NostrGeohashService(
limit = 200
)
nostrRelayManager.subscribe(
nostrRelayManager.subscribeForGeohash(
geohash = channel.channel.geohash,
filter = geohashFilter,
id = geohashSubId,
handler = { event ->
handleUnifiedGeohashEvent(event, channel.channel.geohash)
}
},
includeDefaults = false,
nRelays = 5
)
Log.i(TAG, "✅ Subscribed to geohash ephemeral events: #${channel.channel.geohash}")
@@ -892,12 +903,15 @@ class NostrGeohashService(
since = System.currentTimeMillis() - 86400000L // Last 24 hours
)
nostrRelayManager.subscribe(
nostrRelayManager.subscribeForGeohash(
geohash = channel.channel.geohash,
filter = dmFilter,
id = dmSubId,
handler = { giftWrap ->
handleGeohashDmEvent(giftWrap, channel.channel.geohash, dmIdentity)
}
},
includeDefaults = false,
nRelays = 5
)
Log.i(TAG, "✅ Subscribed to geohash DMs for identity: ${dmIdentity.publicKeyHex.take(16)}...")
@@ -52,6 +52,8 @@ class NostrRelayManager private constructor() {
fun registerPendingGiftWrap(id: String) {
pendingGiftWrapIDs.add(id)
}
fun defaultRelays(): List<String> = DEFAULT_RELAYS
}
/**
@@ -93,7 +95,8 @@ class NostrRelayManager private constructor() {
val filter: NostrFilter,
val handler: (NostrEvent) -> Unit,
val targetRelayUrls: Set<String>? = null, // null means all relays
val createdAt: Long = System.currentTimeMillis()
val createdAt: Long = System.currentTimeMillis(),
val originGeohash: String? = null // used for logging and grouping
)
// Event deduplication system
@@ -119,6 +122,103 @@ class NostrRelayManager private constructor() {
private val gson by lazy { NostrRequest.createGson() }
// Per-geohash relay selection
private val geohashToRelays = ConcurrentHashMap<String, Set<String>>() // geohash -> relay URLs
// --- Public API for geohash-specific operation ---
/**
* Compute and connect to relays for a given geohash (nearest + optional defaults), cache the mapping.
*/
fun ensureGeohashRelaysConnected(geohash: String, nRelays: Int = 5, includeDefaults: Boolean = false) {
try {
val nearest = RelayDirectory.closestRelaysForGeohash(geohash, nRelays)
val selected = if (includeDefaults) {
(nearest + Companion.defaultRelays()).toSet()
} else nearest.toSet()
if (selected.isEmpty()) {
Log.w(TAG, "No relays selected for geohash=$geohash")
return
}
geohashToRelays[geohash] = selected
Log.i(TAG, "🌐 Geohash $geohash using ${selected.size} relays: ${selected.joinToString()}")
ensureConnectionsFor(selected)
} catch (e: Exception) {
Log.e(TAG, "Failed to ensure relays for $geohash: ${e.message}")
}
}
/**
* Get relays mapped to a geohash (empty list if none configured).
*/
fun getRelaysForGeohash(geohash: String): List<String> {
return geohashToRelays[geohash]?.toList() ?: emptyList()
}
/**
* Subscribe with explicit geohash routing; ensures connections exist, then targets only those relays.
*/
fun subscribeForGeohash(
geohash: String,
filter: NostrFilter,
id: String = generateSubscriptionId(),
handler: (NostrEvent) -> Unit,
includeDefaults: Boolean = false,
nRelays: Int = 5
): String {
ensureGeohashRelaysConnected(geohash, nRelays, includeDefaults)
val relayUrls = getRelaysForGeohash(geohash)
Log.d(TAG, "📡 Subscribing id=$id for geohash=$geohash on ${relayUrls.size} relays")
return subscribe(
filter = filter,
id = id,
handler = handler,
targetRelayUrls = relayUrls
).also {
// update origin geohash for this subscription
activeSubscriptions[it]?.let { sub ->
activeSubscriptions[it] = sub.copy(originGeohash = geohash)
}
}
}
/**
* Send an event specifically to a geohash's relays (+ optional defaults).
*/
fun sendEventToGeohash(event: NostrEvent, geohash: String, includeDefaults: Boolean = false, nRelays: Int = 5) {
ensureGeohashRelaysConnected(geohash, nRelays, includeDefaults)
val relayUrls = getRelaysForGeohash(geohash)
if (relayUrls.isEmpty()) {
Log.w(TAG, "No target relays to send event for geohash=$geohash; falling back to defaults")
sendEvent(event, Companion.defaultRelays())
return
}
Log.v(TAG, "📤 Sending event kind=${event.kind} to ${relayUrls.size} relays for geohash=$geohash")
sendEvent(event, relayUrls)
}
// --- Internal helpers ---
private fun ensureConnectionsFor(relayUrls: Set<String>) {
// Ensure relays are tracked for UI/status
relayUrls.forEach { url ->
if (relaysList.none { it.url == url }) {
relaysList.add(Relay(url))
}
}
updateRelaysList()
scope.launch {
relayUrls.forEach { relayUrl ->
launch {
if (!connections.containsKey(relayUrl)) {
connectToRelay(relayUrl)
}
}
}
}
}
init {
// Initialize with default relays - avoid static initialization order issues
try {
@@ -548,7 +648,12 @@ class NostrRelayManager private constructor() {
val wasProcessed = eventDeduplicator.processEvent(response.event) { event ->
// Only log non-gift-wrap events to reduce noise
if (event.kind != NostrKind.GIFT_WRAP) {
Log.v(TAG, "📥 Processing new Nostr event (kind: ${event.kind}) from relay: $relayUrl")
val originGeo = activeSubscriptions[response.subscriptionId]?.originGeohash
if (originGeo != null) {
Log.v(TAG, "📥 Processing event (kind=${event.kind}) from relay=$relayUrl geo=$originGeo sub=${response.subscriptionId}")
} else {
Log.v(TAG, "📥 Processing event (kind=${event.kind}) from relay=$relayUrl sub=${response.subscriptionId}")
}
}
// Call handler for new events only
@@ -0,0 +1,309 @@
package com.bitchat.android.nostr
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.security.MessageDigest
import java.util.concurrent.TimeUnit
import kotlin.math.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
/**
* Loads relay coordinates from assets and provides nearest-relay lookup by geohash.
*/
object RelayDirectory {
private const val TAG = "RelayDirectory"
private const val ASSET_FILE_URL = "https://raw.githubusercontent.com/permissionlesstech/georelays/refs/heads/main/nostr_relays.csv"
private const val ASSET_FILE = "nostr_relays.csv"
private const val DOWNLOADED_FILE = "nostr_relays_latest.csv"
private const val PREFS_NAME = "relay_directory_prefs"
private const val KEY_LAST_UPDATE_MS = "last_update_ms"
private val ONE_DAY_MS = TimeUnit.DAYS.toMillis(1)
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val httpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.callTimeout(15, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build()
}
data class RelayInfo(
val url: String,
val latitude: Double,
val longitude: Double
)
@Volatile
private var initialized: Boolean = false
private val relays: MutableList<RelayInfo> = mutableListOf()
private val relaysLock = Any()
fun initialize(application: Application) {
if (initialized) return
synchronized(this) {
if (initialized) return
try {
val downloaded = getDownloadedFile(application)
val loadedFromDownloaded = if (downloaded.exists() && downloaded.canRead()) {
loadFromFile(downloaded, sourceLabel = "downloaded")
} else {
false
}
if (!loadedFromDownloaded) {
loadFromAssets(application)
}
initialized = true
// Trigger an immediate fetch if the data is stale (older than 24h)
ioScope.launch {
if (isStale(application)) {
fetchAndMaybeSwap(application)
}
}
// Start periodic staleness check every minute
startPeriodicRefresh(application)
} catch (e: Exception) {
Log.e(TAG, "Failed to initialize RelayDirectory: ${e.message}")
}
}
}
/**
* Return up to nRelays closest relay URLs to the geohash center.
*/
fun closestRelaysForGeohash(geohash: String, nRelays: Int): List<String> {
val snapshot = synchronized(relaysLock) { relays.toList() }
if (snapshot.isEmpty()) return emptyList()
val center = try {
val c = com.bitchat.android.geohash.Geohash.decodeToCenter(geohash)
c
} catch (e: Exception) {
Log.e(TAG, "Failed to decode geohash '$geohash': ${e.message}")
return emptyList()
}
val (lat, lon) = center
return snapshot
.asSequence()
.sortedBy { haversineMeters(lat, lon, it.latitude, it.longitude) }
.take(nRelays.coerceAtLeast(0))
.map { it.url }
.toList()
}
private fun haversineMeters(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
val R = 6371000.0 // meters
val dLat = Math.toRadians(lat2 - lat1)
val dLon = Math.toRadians(lon2 - lon1)
val a = sin(dLat / 2).pow(2.0) + cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) * sin(dLon / 2).pow(2.0)
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
return R * c
}
private fun normalizeRelayUrl(raw: String): String {
val trimmed = raw.trim()
if (trimmed.isEmpty()) return trimmed
return if ("://" in trimmed) trimmed else "wss://$trimmed"
}
// ===== Implementation details =====
private fun getPrefs(application: Application): SharedPreferences =
application.getSharedPreferences(PREFS_NAME, Application.MODE_PRIVATE)
private fun getDownloadedFile(application: Application): File =
File(application.filesDir, DOWNLOADED_FILE)
private fun isStale(application: Application): Boolean {
val last = getPrefs(application).getLong(KEY_LAST_UPDATE_MS, 0L)
val now = System.currentTimeMillis()
return now - last >= ONE_DAY_MS
}
private fun startPeriodicRefresh(application: Application) {
ioScope.launch {
while (true) {
try {
if (isStale(application)) {
fetchAndMaybeSwap(application)
}
} catch (e: Exception) {
Log.w(TAG, "Periodic refresh encountered an error: ${e.message}")
}
delay(TimeUnit.MINUTES.toMillis(1))
}
}
}
private fun fetchAndMaybeSwap(application: Application) {
try {
val tmpFile = File.createTempFile("relays_", ".csv", application.cacheDir)
val ok = downloadToFile(ASSET_FILE_URL, tmpFile)
if (!ok) {
Log.w(TAG, "Failed to fetch latest relays; keeping current list (will fallback to bundled if none)")
tmpFile.delete()
return
}
val parsed = parseCsv(FileInputStream(tmpFile))
if (parsed.isEmpty()) {
Log.w(TAG, "Downloaded relay CSV parsed to 0 entries; ignoring")
tmpFile.delete()
return
}
val dest = getDownloadedFile(application)
tmpFile.inputStream().use { input ->
FileOutputStream(dest, false).use { output ->
input.copyTo(output)
}
}
tmpFile.delete()
val hash = fileSha256Hex(dest)
val entries = parsed.size
synchronized(relaysLock) {
relays.clear()
relays.addAll(parsed)
}
getPrefs(application).edit().putLong(KEY_LAST_UPDATE_MS, System.currentTimeMillis()).apply()
Log.i(TAG, "✅ Using downloaded relay list (${dest.absolutePath}), entries=$entries, sha256=$hash, updatedAtMs=${getPrefs(application).getLong(KEY_LAST_UPDATE_MS, 0L)}")
} catch (e: Exception) {
Log.w(TAG, "Failed to fetch and swap relay list: ${e.message}")
}
}
private fun downloadToFile(url: String, dest: File): Boolean {
return try {
val req = Request.Builder().url(url).get().build()
httpClient.newCall(req).execute().use { resp ->
if (!resp.isSuccessful) {
Log.w(TAG, "HTTP ${'$'}{resp.code} when fetching $url")
return false
}
val body = resp.body ?: return false
FileOutputStream(dest).use { out ->
body.byteStream().use { input ->
input.copyTo(out)
}
}
true
}
} catch (e: Exception) {
Log.w(TAG, "Download error: ${e.message}")
false
}
}
private fun loadFromFile(file: File, sourceLabel: String): Boolean {
return try {
val list = parseCsv(FileInputStream(file))
if (list.isEmpty()) {
Log.w(TAG, "${sourceLabel} relay CSV has 0 entries; ignoring")
false
} else {
synchronized(relaysLock) {
relays.clear()
relays.addAll(list)
}
val hash = fileSha256Hex(file)
Log.i(TAG, "📄 Loaded ${list.size} relay entries from ${sourceLabel} file (${file.absolutePath}), sha256=$hash")
true
}
} catch (e: Exception) {
Log.w(TAG, "Failed loading ${sourceLabel} relay file: ${e.message}")
false
}
}
private fun loadFromAssets(application: Application) {
val list = try {
parseCsv(application.assets.open(ASSET_FILE))
} catch (e: Exception) {
Log.e(TAG, "Failed to open asset $ASSET_FILE: ${e.message}")
emptyList()
}
synchronized(relaysLock) {
relays.clear()
relays.addAll(list)
}
// Compute asset hash for logging
val hash = try {
application.assets.open(ASSET_FILE).use { input ->
streamSha256Hex(input)
}
} catch (e: Exception) {
"error:${'$'}{e.message}"
}
Log.i(TAG, "📦 Loaded ${list.size} relay entries from assets/$ASSET_FILE, sha256=$hash")
}
private fun parseCsv(input: InputStream): List<RelayInfo> {
val result = mutableListOf<RelayInfo>()
BufferedReader(InputStreamReader(input)).use { reader ->
var line: String?
while (true) {
line = reader.readLine()
if (line == null) break
val trimmed = line!!.trim()
if (trimmed.isEmpty()) continue
if (trimmed.lowercase().startsWith("relay url")) continue
val parts = trimmed.split(",")
if (parts.size < 3) continue
val url = normalizeRelayUrl(parts[0].trim())
val lat = parts[1].trim().toDoubleOrNull()
val lon = parts[2].trim().toDoubleOrNull()
if (url.isEmpty() || lat == null || lon == null) continue
result.add(RelayInfo(url = url, latitude = lat, longitude = lon))
}
}
return result
}
private fun fileSha256Hex(file: File): String = try {
FileInputStream(file).use { input ->
streamSha256Hex(input)
}
} catch (_: Exception) { "error" }
private fun streamSha256Hex(input: InputStream): String {
val digest = MessageDigest.getInstance("SHA-256")
val buf = ByteArray(8192)
var read: Int
while (true) {
read = input.read(buf)
if (read <= 0) break
digest.update(buf, 0, read)
}
val bytes = digest.digest()
return bytes.joinToString("") { b ->
val v = b.toInt() and 0xff
val s = Integer.toHexString(v)
if (s.length == 1) "0$s" else s
}
}
}