From 9e25ee4080cdacb90b0602d16efe3d263edd94d2 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 21 Sep 2025 06:21:05 +0000 Subject: [PATCH 01/14] Automated update of relay data - Sun Sep 21 06:21:05 UTC 2025 --- app/src/main/assets/nostr_relays.csv | 479 ++++++++++++++------------- 1 file changed, 245 insertions(+), 234 deletions(-) diff --git a/app/src/main/assets/nostr_relays.csv b/app/src/main/assets/nostr_relays.csv index 2f6ef4764..046ceff22 100644 --- a/app/src/main/assets/nostr_relays.csv +++ b/app/src/main/assets/nostr_relays.csv @@ -1,264 +1,275 @@ Relay URL,Latitude,Longitude -relay.laantungir.net,-19.4692,-42.5315 -relay.endfiat.money,43.6532,-79.3832 -relay.zone667.com,60.1699,24.9384 -shu04.shugur.net,25.2604,55.2989 -relay.bitcoinartclock.com,50.4754,12.3683 -relay.nostromo.social,49.4543,11.0746 -nostr.liberty.fans,36.9104,-89.5875 -roles-az-achieving-somebody.trycloudflare.com,43.6532,-79.3832 -nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874 -relay.nostr.wirednet.jp,34.706,135.493 +nostr.tac.lol,47.4748,-122.273 +relay.javi.space,43.4633,11.8796 nostr.einundzwanzig.space,50.1109,8.68213 -relay.21e6.cz,50.1682,14.0546 -relay04.lnfi.network,39.0997,-94.5786 -relay.chorus.community,50.1109,8.68213 -relay.nostr.place,32.7767,-96.797 -relay.vrtmrz.net,43.6532,-79.3832 -noxir.kpherox.dev,34.8587,135.509 -wot.nostr.net,43.6532,-79.3832 -relay.cypherflow.ai,48.8566,2.35222 -wot.sudocarlos.com,51.5072,-0.127586 -nostr.jerrynya.fun,31.2304,121.474 -nostr2.girino.org,43.6532,-79.3832 -nostrings-relay-dev.fly.dev,41.8781,-87.6298 -fanfares.nostr1.com,40.7128,-74.006 -nostr.red5d.dev,43.6532,-79.3832 -nostr.hifish.org,47.4043,8.57398 -nostr.now,36.55,139.733 -relay.nostr.band,60.1699,24.9384 -relay.wavlake.com,41.2619,-95.8608 -nostr.bilthon.dev,25.8128,-80.2377 -khatru.nostrver.se,51.8933,4.42083 -relay.bitcoindistrict.org,43.6532,-79.3832 -nostr.makibisskey.work,43.6532,-79.3832 -relay.nostraddress.com,43.6532,-79.3832 -relay.jmoose.rocks,60.1699,24.9384 +nostr.kalf.org,52.3676,4.90414 +wot.dtonon.com,43.6532,-79.3832 +nostr-01.yakihonne.com,1.32123,103.695 +wot.basspistol.org,49.4521,11.0767 +relay.puresignal.news,43.6532,-79.3832 +nostrelites.org,41.8781,-87.6298 +nostr-relay.nextblockvending.com,47.674,-122.122 +relay03.lnfi.network,39.0997,-94.5786 +nproxy.kristapsk.lv,60.1699,24.9384 +relay.toastr.net,40.8054,-74.0241 relay.davidebtc.me,51.5072,-0.127586 -a.nos.lol,50.4754,12.3683 -nostr.tadryanom.me,43.6532,-79.3832 -relay.nostrdice.com,-33.8688,151.209 -relay.lumina.rocks,49.0291,8.35695 -relay.goodmorningbitcoin.com,43.6532,-79.3832 -nostr.rtvslawenia.com,49.4543,11.0746 +wot.nostr.net,43.6532,-79.3832 relay.mattybs.lol,43.6532,-79.3832 -relay-dev.satlantis.io,40.8302,-74.1299 -nostream.breadslice.com,43.6532,-79.3832 -nostr.vulpem.com,49.4543,11.0746 -nostr.rohoss.com,50.1109,8.68213 -articles.layer3.news,37.3387,-121.885 +relay.zone667.com,60.1699,24.9384 +nostr.kungfu-g.rip,33.7946,-84.4488 +relay.mccormick.cx,52.3563,4.95714 +relay.dwadziesciajeden.pl,52.2297,21.0122 +nostr.data.haus,50.4754,12.3683 +vitor.nostr1.com,40.7128,-74.006 +purpura.cloud,43.6532,-79.3832 +relay2.angor.io,48.1046,11.6002 nos.lol,50.4754,12.3683 -relay.artx.market,43.652,-79.3633 -wot.sebastix.social,51.8933,4.42083 -alien.macneilmediagroup.com,43.6532,-79.3832 -relay.unknown.cloud,43.6532,-79.3832 -nostr.lojong.info,43.6532,-79.3832 -nostr.zenon.network,43.5009,-70.4428 -orangesync.tech,50.1109,8.68213 +nostr.rohoss.com,50.1109,8.68213 +strfry.bonsai.com,37.8715,-122.273 +relay.fountain.fm,39.0997,-94.5786 +relay.npubhaus.com,43.6532,-79.3832 +relay.nostr.wirednet.jp,34.706,135.493 +soloco.nl,43.6532,-79.3832 +shu01.shugur.net,21.4902,39.2246 nostr.davidebtc.me,51.5072,-0.127586 -internationalright-wing.org,-22.5022,-48.7114 -nostr.rikmeijer.nl,50.4754,12.3683 +pyramid.fiatjaf.com,50.1109,8.68213 +relay.ditto.pub,43.6532,-79.3832 +relay.nostr.vet,52.6467,4.7395 +relay.wavlake.com,41.2619,-95.8608 +ribo.eu.nostria.app,52.3676,4.90414 +relay.ngengine.org,43.6532,-79.3832 +relay.bitcoinveneto.org,64.1466,-21.9426 +no.str.cr,9.92857,-84.0528 +relay.primal.net,43.6532,-79.3832 ynostr.yael.at,60.1699,24.9384 -ithurtswhenip.ee,51.223,6.78245 -relay.wellorder.net,45.5201,-122.99 -nostr.sathoarder.com,48.5734,7.75211 +nostr.camalolo.com,24.1469,120.684 purplerelay.com,50.1109,8.68213 -yabu.me,35.6092,139.73 -nostr.88mph.life,43.6532,-79.3832 -nostr.overmind.lol,43.6532,-79.3832 -rnostr.breadslice.com,43.6532,-79.3832 -zap.watch,45.5029,-73.5723 -wot.basspistol.org,49.4521,11.0767 -shu01.shugur.net,21.4902,39.2246 -relay.electriclifestyle.com,26.2897,-80.1293 -relay.mccormick.cx,52.3563,4.95714 -nostr.middling.mydns.jp,35.8099,140.12 -nostr.smut.cloud,43.6532,-79.3832 -satsage.xyz,37.3986,-121.964 -srtrelay.c-stellar.net,43.6532,-79.3832 -nostr.0x7e.xyz,47.4988,8.72369 -shu02.shugur.net,21.4902,39.2246 -nostrelites.org,41.8781,-87.6298 -relay-admin.thaliyal.com,40.8218,-74.45 -wot.soundhsa.com,34.0479,-118.256 -nostrcheck.me,43.6532,-79.3832 -relay.nostrhub.tech,49.4543,11.0746 -relay.stream.labs.h3.se,59.4016,17.9455 -nostrelay.memory-art.xyz,43.6532,-79.3832 -nostr.n7ekb.net,47.4941,-122.294 -relay.nosto.re,51.8933,4.42083 -nostr.girino.org,43.6532,-79.3832 -relay.siamdev.cc,13.9178,100.424 -nostr.mehdibekhtaoui.com,49.4939,-1.54813 -orangepiller.org,60.1699,24.9384 -nostr.plantroon.com,50.1013,8.62643 -nostr-verified.wellorder.net,45.5201,-122.99 -relay.primal.net,43.6532,-79.3832 -relay.bitcoinveneto.org,64.1466,-21.9426 -relay.hasenpfeffr.com,39.0438,-77.4874 -strfry.openhoofd.nl,51.9229,4.40833 -relay.aloftus.io,34.0881,-118.379 -nostr.spaceshell.xyz,43.6532,-79.3832 -nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832 -ribo.af.nostria.app,-26.2041,28.0473 -nostr.tac.lol,47.4748,-122.273 -relay.satlantis.io,32.8769,-80.0114 -nostr.azzamo.net,52.2633,21.0283 -strfry.bonsai.com,37.8715,-122.273 -relay.agora.social,50.7383,15.0648 -nostr-relay.amethyst.name,39.0067,-77.4291 -relay.toastr.net,40.8054,-74.0241 +nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832 +relay.internationalright-wing.org,-22.5022,-48.7114 +wheat.happytavern.co,43.6532,-79.3832 +nostr.lostr.space,43.6532,-79.3832 +relay.tagayasu.xyz,43.6715,-79.38 +relay.varke.eu,52.6921,6.19372 +free.relayted.de,50.1109,8.68213 nostr.thebiglake.org,32.71,-96.6745 -nostr-relay.nextblockvending.com,47.674,-122.122 -vitor.nostr1.com,40.7057,-74.0136 -relay.btcforplebs.com,43.6532,-79.3832 -relay.g1sms.fr,43.9432,2.07537 -nostr.jfischer.org,49.0291,8.35696 -nostr.mikoshi.de,52.52,13.405 -relay.notoshi.win,13.7829,100.546 -pyramid.fiatjaf.com,50.1109,8.68213 -relay.coinos.io,43.6532,-79.3832 -relay.freeplace.nl,52.3676,4.90414 -nostr-relay.psfoundation.info,39.0438,-77.4874 -relay.copylaradio.com,51.223,6.78245 -relay.exit.pub,50.4754,12.3683 -freelay.sovbit.host,64.1476,-21.9392 -nostr.satstralia.com,64.1476,-21.9392 -nostr.l484.com,30.2944,-97.6223 -nostr.rblb.it,43.4633,11.8796 -nostr.2b9t.xyz,34.0549,-118.243 -nostr.dlsouza.lol,50.1109,8.68213 -strfry.shock.network,41.8959,-88.2169 -offchain.pub,36.1809,-115.241 -nostr-01.yakihonne.com,1.32123,103.695 -nostr.kungfu-g.rip,33.7946,-84.4488 -relay.letsfo.com,51.098,17.0321 -relay.lifpay.me,1.35208,103.82 -relay.damus.io,43.6532,-79.3832 -relay2.angor.io,48.1046,11.6002 -relayrs.notoshi.win,43.6532,-79.3832 +nostr.lojong.info,43.6532,-79.3832 +nostr.now,36.55,139.733 +relay.jmoose.rocks,60.1699,24.9384 +relay.holzeis.me,43.6532,-79.3832 +nostr.roundrockbitcoiners.com,40.8054,-74.0241 +nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874 relay2.ngengine.org,43.6532,-79.3832 -portal-relay.pareto.space,49.4543,11.0746 -inbox.azzamo.net,52.2633,21.0283 +nostr.snowbla.de,60.1699,24.9384 +4u2ni0zjbjvni.clorecloud.net,43.6532,-79.3832 +shu04.shugur.net,25.2604,55.2989 +relay.fr13nd5.com,52.5233,13.3426 +nostr.vulpem.com,49.4543,11.0746 +temp.iris.to,43.6532,-79.3832 +x.kojira.io,43.6532,-79.3832 +adre.su,59.9311,30.3609 nostr-dev.wellorder.net,45.5201,-122.99 -nostr.stakey.net,52.3676,4.90414 -relay.13room.space,43.6532,-79.3832 -relay.fountain.fm,39.0997,-94.5786 -black.nostrcity.club,41.8781,-87.6298 -nostr-2.21crypto.ch,47.4988,8.72369 -dev-nostr.bityacht.io,25.0797,121.234 +nostr.mom,50.4754,12.3683 +relay.nostr.place,32.7767,-96.797 +wot.nostr.place,30.2672,-97.7431 +nostr.carroarmato0.be,50.9928,3.26317 +nostrelay.circum.space,51.2217,6.77616 +relay.chorus.community,50.1109,8.68213 +relay.nostr.net,50.4754,12.3683 +relay.nostr-check.me,43.6532,-79.3832 +relay.nostrhub.fr,48.1046,11.6002 +relay.nostraddress.com,43.6532,-79.3832 +nostr.rblb.it,43.4633,11.8796 +nostr.red5d.dev,43.6532,-79.3832 santo.iguanatech.net,40.8302,-74.1299 -relay.angor.io,48.1046,11.6002 -relay.tagayasu.xyz,43.6715,-79.38 -relay.npubhaus.com,43.6532,-79.3832 -relay01.lnfi.network,39.0997,-94.5786 -nostr.myshosholoza.co.za,52.3676,4.90414 relay02.lnfi.network,39.0997,-94.5786 -gnostr.com,40.9017,29.1616 -nostr.sagaciousd.com,49.2827,-123.121 -nostr.night7.space,50.4754,12.3683 +relay.21e6.cz,50.1682,14.0546 +a.nos.lol,50.4754,12.3683 +shu02.shugur.net,21.4902,39.2246 schnorr.me,43.6532,-79.3832 -nostr.blankfors.se,60.1699,24.9384 -relay.mostro.network,40.8302,-74.1299 -purpura.cloud,43.6532,-79.3832 -ribo.eu.nostria.app,52.3676,4.90414 -vidono.apps.slidestr.net,48.8566,2.35222 -wheat.happytavern.co,43.6532,-79.3832 -nostr.faultables.net,43.6532,-79.3832 -relay5.bitransfer.org,43.6532,-79.3832 -relay.nostrhub.fr,48.1046,11.6002 +nostr.n7ekb.net,47.4941,-122.294 +wot.shaving.kiwi,43.6532,-79.3832 +dev-nostr.bityacht.io,25.0797,121.234 +relay.credenso.cafe,43.1149,-80.7228 +relay-testnet.k8s.layer3.news,37.3387,-121.885 +relay.mess.ch,47.3591,8.55292 +inbox.azzamo.net,52.2633,21.0283 +prl.plus,55.7623,37.6381 +yabu.me,35.6092,139.73 +relayrs.notoshi.win,43.6532,-79.3832 +premium.primal.net,43.6532,-79.3832 +nostr.coincrowd.fund,39.0438,-77.4874 +nostr.2b9t.xyz,34.0549,-118.243 nostr.thaliyal.com,40.8218,-74.45 -relay.holzeis.me,43.6532,-79.3832 -relay.nostriot.com,41.5695,-83.9786 -nostr.openhoofd.nl,51.9229,4.40833 -relay.nostr.vet,52.6467,4.7395 -nostr.camalolo.com,24.1469,120.684 +relay.exit.pub,50.4754,12.3683 +nostr.jfischer.org,49.0291,8.35696 relay.origin.land,35.6673,139.751 +nostr.myshosholoza.co.za,52.3676,4.90414 +relay.nostriot.com,41.5695,-83.9786 +relay.btcforplebs.com,43.6532,-79.3832 relay.chakany.systems,43.6532,-79.3832 -relay.0xchat.com,1.35208,103.82 -nostr.mom,50.4754,12.3683 -4u2ni0zjbjvni.clorecloud.net,43.6532,-79.3832 -prl.plus,55.7623,37.6381 -relay.moinsen.com,50.4754,12.3683 +nostr.openhoofd.nl,51.9229,4.40833 +nostrcheck.me,43.6532,-79.3832 +nostr.plantroon.com,50.1013,8.62643 +satsage.xyz,37.3986,-121.964 +nostr.faultables.net,43.6532,-79.3832 +nostr.calitabby.net,39.9268,-75.0246 +relay.freeplace.nl,52.3676,4.90414 +relay.nostrhub.tech,49.4543,11.0746 +roles-az-achieving-somebody.trycloudflare.com,43.6532,-79.3832 +relay.arx-ccn.com,50.4754,12.3683 +cyberspace.nostr1.com,40.7128,-74.006 +nostr.smut.cloud,43.6532,-79.3832 nostr-02.czas.top,53.471,9.88208 -relay.sigit.io,50.4754,12.3683 -relay.nostrcheck.me,43.6532,-79.3832 -relay03.lnfi.network,39.0997,-94.5786 -relay.sincensura.org,43.6532,-79.3832 -nostr.coincards.com,53.5501,-113.469 -nostr-03.dorafactory.org,1.35208,103.82 -relay.credenso.cafe,43.1149,-80.7228 -nostr.fbxl.net,48.3809,-89.2477 -relay.bullishbounty.com,43.6532,-79.3832 -nos.xmark.cc,50.6924,3.20113 -x.kojira.io,43.6532,-79.3832 -wot.sovbit.host,64.1466,-21.9426 -shu05.shugur.net,48.8566,2.35222 -nostr.carroarmato0.be,50.9928,3.26317 +relay.tapestry.ninja,40.8054,-74.0241 +relay.mostro.network,40.8302,-74.1299 +wot.brightbolt.net,47.6735,-116.781 +nostr.spaceshell.xyz,43.6532,-79.3832 +nostr.rikmeijer.nl,50.4754,12.3683 +relay.artx.market,43.652,-79.3633 +strfry.felixzieger.de,50.1013,8.62643 +relay.seq1.net,43.6532,-79.3832 relay.cosmicbolt.net,37.3986,-121.964 +relay.electriclifestyle.com,26.2897,-80.1293 r.bitcoinhold.net,43.6532,-79.3832 -nostr.diakod.com,43.6532,-79.3832 +nostr-relay.amethyst.name,39.0067,-77.4291 +relay.stream.labs.h3.se,59.4016,17.9455 +relay.unknown.cloud,43.6532,-79.3832 +nostr-02.yakihonne.com,1.32123,103.695 +relay.coinos.io,43.6532,-79.3832 +relay5.bitransfer.org,43.6532,-79.3832 +relay-dev.satlantis.io,40.8302,-74.1299 +nostream.breadslice.com,43.6532,-79.3832 +relay.fundstr.me,42.3601,-71.0589 +nostr.oxtr.dev,50.4754,12.3683 +nos.xmark.cc,50.6924,3.20113 +nostr.mikoshi.de,50.1109,8.68213 +relay.magiccity.live,25.8128,-80.2377 +nostr-verified.wellorder.net,45.5201,-122.99 +nostr.makibisskey.work,43.6532,-79.3832 +wot.nostr.party,36.1627,-86.7816 +relay.copylaradio.com,51.223,6.78245 +nostr.sathoarder.com,48.5734,7.75211 +relay.jeffg.fyi,43.6532,-79.3832 +relay.wellorder.net,45.5201,-122.99 +nostr.ovia.to,43.6532,-79.3832 +black.nostrcity.club,41.8781,-87.6298 +relay.nostrcheck.me,43.6532,-79.3832 nostr-relay.cbrx.io,43.6532,-79.3832 -nostr.coincrowd.fund,39.0438,-77.4874 -cyberspace.nostr1.com,40.7128,-74.006 -relay.barine.co,43.6532,-79.3832 -relay.orangepill.ovh,49.1689,-0.358841 -no.str.cr,9.92857,-84.0528 -nostr.casa21.space,43.6532,-79.3832 +nostr-03.dorafactory.org,1.35208,103.82 +nostr-relay.zimage.com,34.282,-118.439 +relay.sigit.io,50.4754,12.3683 +relay.laantungir.net,-19.4692,-42.5315 +relay-admin.thaliyal.com,40.8218,-74.45 relay.mwaters.net,50.9871,2.12554 -relay.magiccity.live,25.8128,-80.2377 -relayone.soundhsa.com,34.0479,-118.256 -slick.mjex.me,39.048,-77.4817 -relay.utxo.farm,35.6916,139.768 -theoutpost.life,64.1476,-21.9392 -nostr.hekster.org,37.3986,-121.964 -strfry.felixzieger.de,50.1013,8.62643 -relay.mess.ch,47.3591,8.55292 -wot.codingarena.top,50.4754,12.3683 -nostrelay.circum.space,51.2217,6.77616 -nostr-relay.online,43.6532,-79.3832 -temp.iris.to,43.6532,-79.3832 +relay.lumina.rocks,49.0291,8.35695 +nostr.satstralia.com,64.1476,-21.9392 +nostr.sagaciousd.com,49.2827,-123.121 +relay.bullishbounty.com,43.6532,-79.3832 wot.dergigi.com,64.1476,-21.9392 -wot.brightbolt.net,47.6735,-116.781 -nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832 -wot.nostr.place,30.2672,-97.7431 -ribo.us.nostria.app,41.5868,-93.625 -relay.nostr.net,50.4754,12.3683 -nostr-02.dorafactory.org,1.35208,103.82 -relay.tapestry.ninja,40.8054,-74.0241 -adre.su,59.9311,30.3609 -librerelay.aaroniumii.com,43.6532,-79.3832 -nostr-pub.wellorder.net,45.5201,-122.99 -kitchen.zap.cooking,43.6532,-79.3832 +relay.vrtmrz.net,43.6532,-79.3832 +nostr.0x7e.xyz,47.4988,8.72369 +articles.layer3.news,37.3387,-121.885 +relay.digitalezukunft.cyou,45.5019,-73.5674 +nostr.notribe.net,40.8302,-74.1299 nostr.21crypto.ch,47.4988,8.72369 -nostr-02.yakihonne.com,1.32123,103.695 -relay.javi.space,43.4633,11.8796 -nostr.ser1.net,12.9716,77.5946 -relay-rpi.edufeed.org,49.4543,11.0746 -premium.primal.net,43.6532,-79.3832 -relay.degmods.com,50.4754,12.3683 -relay.arx-ccn.com,50.4754,12.3683 -nostr.chaima.info,51.223,6.78245 +relay.lifpay.me,1.35208,103.82 +relay.g1sms.fr,43.9432,2.07537 +relay.snort.social,43.6532,-79.3832 +relay.getsafebox.app,43.6532,-79.3832 +relay.moinsen.com,50.4754,12.3683 +nostr.blankfors.se,60.1699,24.9384 +nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832 +orangesync.tech,50.1109,8.68213 +relay.ru.ac.th,13.7584,100.622 +orangepiller.org,60.1699,24.9384 +relay.nostr.band,60.1699,24.9384 +wot.soundhsa.com,34.0479,-118.256 +khatru.nostrver.se,51.8933,4.42083 +nostr.hifish.org,47.4043,8.57398 +freelay.sovbit.host,64.1476,-21.9392 +nostr.bilthon.dev,25.8128,-80.2377 +nostr.night7.space,50.4754,12.3683 relay.illuminodes.com,47.6061,-122.333 -relay.nostx.io,43.6532,-79.3832 -relay.puresignal.news,43.6532,-79.3832 +relayone.soundhsa.com,34.0479,-118.256 +nostr.azzamo.net,52.2633,21.0283 fenrir-s.notoshi.win,43.6532,-79.3832 -relay.getsafebox.app,43.6532,-79.3832 -relay.conduit.market,43.6532,-79.3832 -relay.jeffg.fyi,43.6532,-79.3832 -nproxy.kristapsk.lv,60.1699,24.9384 +nostr.spicyz.io,43.6532,-79.3832 +nostrelay.memory-art.xyz,43.6532,-79.3832 +nostr.coincards.com,53.5501,-113.469 +alien.macneilmediagroup.com,43.6532,-79.3832 +nostrings-relay-dev.fly.dev,41.8781,-87.6298 +relay.satlantis.io,32.8769,-80.0114 +srtrelay.c-stellar.net,43.6532,-79.3832 +relay.agora.social,50.7383,15.0648 +relay.bitcoindistrict.org,43.6532,-79.3832 +relay.0xchat.com,1.35208,103.82 +wot.utxo.one,43.6532,-79.3832 +relay.bitcoinartclock.com,50.4754,12.3683 +nostr.zenon.network,43.5009,-70.4428 +wot.codingarena.top,50.4754,12.3683 +nostr-02.dorafactory.org,1.35208,103.82 +relay.nostromo.social,49.4543,11.0746 +theoutpost.life,64.1476,-21.9392 +strfry.shock.network,41.8959,-88.2169 +strfry.openhoofd.nl,51.9229,4.40833 +itanostr.space,52.2931,4.79099 +relay.barine.co,43.6532,-79.3832 +nostr.middling.mydns.jp,35.8099,140.12 relay.olas.app,50.4754,12.3683 -relay.dwadziesciajeden.pl,52.2297,21.0122 -relay-testnet.k8s.layer3.news,37.3387,-121.885 -nostr.pleb.one,38.6327,-90.1961 -relay.digitalezukunft.cyou,45.5019,-73.5674 +nostr.girino.org,43.6532,-79.3832 +nostr-relay.online,43.6532,-79.3832 relay.evanverma.com,40.8302,-74.1299 -wot.dtonon.com,43.6532,-79.3832 -relay.seq1.net,43.6532,-79.3832 -nostr.kalf.org,52.3676,4.90414 -nostr.snowbla.de,60.1699,24.9384 -nostr.spicyz.io,43.6532,-79.3832 -nostr-relay.zimage.com,34.282,-118.439 +relay.angor.io,48.1046,11.6002 +relay.nostrdice.com,-33.8688,151.209 +ribo.us.nostria.app,41.5868,-93.625 +relay01.lnfi.network,39.0997,-94.5786 +nostr.namek.link,43.6532,-79.3832 +relay.aloftus.io,34.0881,-118.379 +ithurtswhenip.ee,51.223,6.78245 +ribo.af.nostria.app,-26.2041,28.0473 +shu05.shugur.net,48.8566,2.35222 +noxir.kpherox.dev,34.8587,135.509 +relay.degmods.com,50.4754,12.3683 +relay.goodmorningbitcoin.com,43.6532,-79.3832 +nostr.4rs.nl,49.0291,8.35696 +nostr-relay.psfoundation.info,39.0438,-77.4874 +relay.hasenpfeffr.com,39.0438,-77.4874 +fanfares.nostr1.com,40.7128,-74.006 +relay.nosto.re,51.8933,4.42083 +nostr.liberty.fans,36.9104,-89.5875 +nostr-2.21crypto.ch,47.4988,8.72369 +relay-rpi.edufeed.org,49.4543,11.0746 +offchain.pub,36.1809,-115.241 +nostr.88mph.life,43.6532,-79.3832 +nostr.luisschwab.net,43.6532,-79.3832 +nostr.casa21.space,43.6532,-79.3832 +nostr.hekster.org,37.3986,-121.964 +relay.utxo.farm,35.6916,139.768 +zap.watch,45.5029,-73.5723 +nostr-pub.wellorder.net,45.5201,-122.99 +wot.sovbit.host,64.1466,-21.9426 +relay.endfiat.money,43.6532,-79.3832 +nostr2.girino.org,43.6532,-79.3832 +nostr.jerrynya.fun,31.2304,121.474 nostr.spacecitynode.com,29.7057,-95.2706 +relay.siamdev.cc,13.9178,100.424 +relay.notoshi.win,13.7829,100.546 +relay.nostx.io,43.6532,-79.3832 +relay.cypherflow.ai,48.8566,2.35222 +relay.letsfo.com,51.098,17.0321 +librerelay.aaroniumii.com,43.6532,-79.3832 +gnostr.com,40.9017,29.1616 +nostr.pleb.one,38.6327,-90.1961 +nostr.mehdibekhtaoui.com,49.4939,-1.54813 +nostr.tadryanom.me,43.6532,-79.3832 +relay.orangepill.ovh,49.1689,-0.358841 +nostr.stakey.net,52.3676,4.90414 +nostr.rtvslawenia.com,49.4543,11.0746 +relay.damus.io,43.6532,-79.3832 dev-relay.lnfi.network,39.0997,-94.5786 -itanostr.space,52.2931,4.79099 +slick.mjex.me,39.048,-77.4817 +wot.sudocarlos.com,51.5072,-0.127586 +relay04.lnfi.network,39.0997,-94.5786 +relay.13room.space,43.6532,-79.3832 +relay.conduit.market,43.6532,-79.3832 +nostr.chaima.info,51.223,6.78245 From 95358ac34a0d14abacc58c68ae6752e1102af889 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 28 Sep 2025 06:20:40 +0000 Subject: [PATCH 02/14] Automated update of relay data - Sun Sep 28 06:20:40 UTC 2025 --- app/src/main/assets/nostr_relays.csv | 486 +++++++++++++-------------- 1 file changed, 242 insertions(+), 244 deletions(-) diff --git a/app/src/main/assets/nostr_relays.csv b/app/src/main/assets/nostr_relays.csv index 046ceff22..3b7006d86 100644 --- a/app/src/main/assets/nostr_relays.csv +++ b/app/src/main/assets/nostr_relays.csv @@ -1,275 +1,273 @@ Relay URL,Latitude,Longitude -nostr.tac.lol,47.4748,-122.273 -relay.javi.space,43.4633,11.8796 -nostr.einundzwanzig.space,50.1109,8.68213 -nostr.kalf.org,52.3676,4.90414 -wot.dtonon.com,43.6532,-79.3832 -nostr-01.yakihonne.com,1.32123,103.695 -wot.basspistol.org,49.4521,11.0767 -relay.puresignal.news,43.6532,-79.3832 -nostrelites.org,41.8781,-87.6298 -nostr-relay.nextblockvending.com,47.674,-122.122 -relay03.lnfi.network,39.0997,-94.5786 -nproxy.kristapsk.lv,60.1699,24.9384 -relay.toastr.net,40.8054,-74.0241 -relay.davidebtc.me,51.5072,-0.127586 -wot.nostr.net,43.6532,-79.3832 -relay.mattybs.lol,43.6532,-79.3832 -relay.zone667.com,60.1699,24.9384 -nostr.kungfu-g.rip,33.7946,-84.4488 -relay.mccormick.cx,52.3563,4.95714 -relay.dwadziesciajeden.pl,52.2297,21.0122 -nostr.data.haus,50.4754,12.3683 -vitor.nostr1.com,40.7128,-74.006 -purpura.cloud,43.6532,-79.3832 -relay2.angor.io,48.1046,11.6002 -nos.lol,50.4754,12.3683 -nostr.rohoss.com,50.1109,8.68213 -strfry.bonsai.com,37.8715,-122.273 -relay.fountain.fm,39.0997,-94.5786 -relay.npubhaus.com,43.6532,-79.3832 -relay.nostr.wirednet.jp,34.706,135.493 -soloco.nl,43.6532,-79.3832 -shu01.shugur.net,21.4902,39.2246 -nostr.davidebtc.me,51.5072,-0.127586 -pyramid.fiatjaf.com,50.1109,8.68213 -relay.ditto.pub,43.6532,-79.3832 -relay.nostr.vet,52.6467,4.7395 -relay.wavlake.com,41.2619,-95.8608 -ribo.eu.nostria.app,52.3676,4.90414 -relay.ngengine.org,43.6532,-79.3832 -relay.bitcoinveneto.org,64.1466,-21.9426 -no.str.cr,9.92857,-84.0528 +relay.satlantis.io,32.8769,-80.0114 relay.primal.net,43.6532,-79.3832 -ynostr.yael.at,60.1699,24.9384 -nostr.camalolo.com,24.1469,120.684 +premium.primal.net,43.6532,-79.3832 purplerelay.com,50.1109,8.68213 -nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832 -relay.internationalright-wing.org,-22.5022,-48.7114 -wheat.happytavern.co,43.6532,-79.3832 -nostr.lostr.space,43.6532,-79.3832 -relay.tagayasu.xyz,43.6715,-79.38 +nostr.ovia.to,43.6532,-79.3832 +relay.nostr.wirednet.jp,34.706,135.493 +relay.vrtmrz.net,43.6532,-79.3832 +relay.wellorder.net,45.5201,-122.99 +relay.nostriot.com,41.5695,-83.9786 +nostr.snowbla.de,60.1699,24.9384 +fanfares.nostr1.com,40.7057,-74.0136 +relay.ditto.pub,43.6532,-79.3832 relay.varke.eu,52.6921,6.19372 -free.relayted.de,50.1109,8.68213 -nostr.thebiglake.org,32.71,-96.6745 -nostr.lojong.info,43.6532,-79.3832 +ithurtswhenip.ee,51.223,6.78245 +offchain.pub,36.1809,-115.241 +wot.nostr.party,36.1627,-86.7816 +zap.watch,45.5029,-73.5723 +orangepiller.org,60.1699,24.9384 +nostr.camalolo.com,24.1469,120.684 +relay.coinos.io,43.6532,-79.3832 nostr.now,36.55,139.733 -relay.jmoose.rocks,60.1699,24.9384 -relay.holzeis.me,43.6532,-79.3832 -nostr.roundrockbitcoiners.com,40.8054,-74.0241 -nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874 -relay2.ngengine.org,43.6532,-79.3832 -nostr.snowbla.de,60.1699,24.9384 -4u2ni0zjbjvni.clorecloud.net,43.6532,-79.3832 -shu04.shugur.net,25.2604,55.2989 -relay.fr13nd5.com,52.5233,13.3426 -nostr.vulpem.com,49.4543,11.0746 temp.iris.to,43.6532,-79.3832 -x.kojira.io,43.6532,-79.3832 -adre.su,59.9311,30.3609 -nostr-dev.wellorder.net,45.5201,-122.99 -nostr.mom,50.4754,12.3683 -relay.nostr.place,32.7767,-96.797 +soloco.nl,43.6532,-79.3832 +nostr.blankfors.se,60.1699,24.9384 +relay.nostx.io,43.6532,-79.3832 wot.nostr.place,30.2672,-97.7431 -nostr.carroarmato0.be,50.9928,3.26317 -nostrelay.circum.space,51.2217,6.77616 -relay.chorus.community,50.1109,8.68213 -relay.nostr.net,50.4754,12.3683 -relay.nostr-check.me,43.6532,-79.3832 -relay.nostrhub.fr,48.1046,11.6002 -relay.nostraddress.com,43.6532,-79.3832 -nostr.rblb.it,43.4633,11.8796 -nostr.red5d.dev,43.6532,-79.3832 -santo.iguanatech.net,40.8302,-74.1299 -relay02.lnfi.network,39.0997,-94.5786 -relay.21e6.cz,50.1682,14.0546 -a.nos.lol,50.4754,12.3683 -shu02.shugur.net,21.4902,39.2246 -schnorr.me,43.6532,-79.3832 -nostr.n7ekb.net,47.4941,-122.294 -wot.shaving.kiwi,43.6532,-79.3832 -dev-nostr.bityacht.io,25.0797,121.234 -relay.credenso.cafe,43.1149,-80.7228 -relay-testnet.k8s.layer3.news,37.3387,-121.885 +relay.damus.io,43.6532,-79.3832 +nostr.sagaciousd.com,49.2827,-123.121 relay.mess.ch,47.3591,8.55292 -inbox.azzamo.net,52.2633,21.0283 -prl.plus,55.7623,37.6381 -yabu.me,35.6092,139.73 -relayrs.notoshi.win,43.6532,-79.3832 -premium.primal.net,43.6532,-79.3832 -nostr.coincrowd.fund,39.0438,-77.4874 -nostr.2b9t.xyz,34.0549,-118.243 -nostr.thaliyal.com,40.8218,-74.45 -relay.exit.pub,50.4754,12.3683 -nostr.jfischer.org,49.0291,8.35696 -relay.origin.land,35.6673,139.751 -nostr.myshosholoza.co.za,52.3676,4.90414 -relay.nostriot.com,41.5695,-83.9786 -relay.btcforplebs.com,43.6532,-79.3832 +relay.snort.social,43.6532,-79.3832 +strfry.bonsai.com,37.8715,-122.273 +relay.chorus.community,50.1109,8.68213 +black.nostrcity.club,41.8781,-87.6298 +relay.conduit.market,43.6532,-79.3832 +relay01.lnfi.network,39.0997,-94.5786 +relay.nostr.vet,52.6467,4.7395 +relay.etch.social,41.2619,-95.8608 relay.chakany.systems,43.6532,-79.3832 -nostr.openhoofd.nl,51.9229,4.40833 -nostrcheck.me,43.6532,-79.3832 -nostr.plantroon.com,50.1013,8.62643 -satsage.xyz,37.3986,-121.964 -nostr.faultables.net,43.6532,-79.3832 -nostr.calitabby.net,39.9268,-75.0246 -relay.freeplace.nl,52.3676,4.90414 -relay.nostrhub.tech,49.4543,11.0746 -roles-az-achieving-somebody.trycloudflare.com,43.6532,-79.3832 -relay.arx-ccn.com,50.4754,12.3683 -cyberspace.nostr1.com,40.7128,-74.006 -nostr.smut.cloud,43.6532,-79.3832 -nostr-02.czas.top,53.471,9.88208 -relay.tapestry.ninja,40.8054,-74.0241 -relay.mostro.network,40.8302,-74.1299 -wot.brightbolt.net,47.6735,-116.781 -nostr.spaceshell.xyz,43.6532,-79.3832 -nostr.rikmeijer.nl,50.4754,12.3683 -relay.artx.market,43.652,-79.3633 -strfry.felixzieger.de,50.1013,8.62643 -relay.seq1.net,43.6532,-79.3832 -relay.cosmicbolt.net,37.3986,-121.964 -relay.electriclifestyle.com,26.2897,-80.1293 -r.bitcoinhold.net,43.6532,-79.3832 -nostr-relay.amethyst.name,39.0067,-77.4291 -relay.stream.labs.h3.se,59.4016,17.9455 -relay.unknown.cloud,43.6532,-79.3832 -nostr-02.yakihonne.com,1.32123,103.695 -relay.coinos.io,43.6532,-79.3832 -relay5.bitransfer.org,43.6532,-79.3832 -relay-dev.satlantis.io,40.8302,-74.1299 +relay-rpi.edufeed.org,49.4543,11.0746 +relay.bullishbounty.com,43.6532,-79.3832 nostream.breadslice.com,43.6532,-79.3832 +wot.basspistol.org,49.4521,11.0767 +nostr.fbxl.net,48.3809,-89.2477 +inbox.azzamo.net,52.2633,21.0283 +nostr.88mph.life,43.6532,-79.3832 +wot.dergigi.com,64.1476,-21.9392 +relay.nosto.re,51.8933,4.42083 +nostr.n7ekb.net,47.4941,-122.294 +relay.nostr.net,50.4754,12.3683 +relay.tagayasu.xyz,43.6715,-79.38 relay.fundstr.me,42.3601,-71.0589 -nostr.oxtr.dev,50.4754,12.3683 -nos.xmark.cc,50.6924,3.20113 -nostr.mikoshi.de,50.1109,8.68213 -relay.magiccity.live,25.8128,-80.2377 +shu05.shugur.net,48.8566,2.35222 +nostrelay.memory-art.xyz,43.6532,-79.3832 +shu04.shugur.net,25.2604,55.2989 nostr-verified.wellorder.net,45.5201,-122.99 -nostr.makibisskey.work,43.6532,-79.3832 -wot.nostr.party,36.1627,-86.7816 +relay.usefusion.ai,39.0438,-77.4874 +relay.fountain.fm,39.0997,-94.5786 +nostr-dev.wellorder.net,45.5201,-122.99 +nostr-relay.nextblockvending.com,47.674,-122.122 +nostr.zenon.network,43.5009,-70.4428 +nostr.oxtr.dev,50.4754,12.3683 +relay2.ngengine.org,43.6532,-79.3832 +nr.yay.so,46.2126,6.1154 +strfry.shock.network,41.8959,-88.2169 +relay.stream.labs.h3.se,59.4016,17.9455 +relay.orangepill.ovh,49.1689,-0.358841 +nostr.pleb.one,38.6327,-90.1961 +nostr.jfischer.org,49.0291,8.35696 +relay.nostr.band,60.1699,24.9384 +relayone.soundhsa.com,34.0479,-118.256 +nostr.21crypto.ch,47.4988,8.72369 relay.copylaradio.com,51.223,6.78245 -nostr.sathoarder.com,48.5734,7.75211 -relay.jeffg.fyi,43.6532,-79.3832 -relay.wellorder.net,45.5201,-122.99 -nostr.ovia.to,43.6532,-79.3832 -black.nostrcity.club,41.8781,-87.6298 -relay.nostrcheck.me,43.6532,-79.3832 -nostr-relay.cbrx.io,43.6532,-79.3832 +relay.puresignal.news,43.6532,-79.3832 +strfry.openhoofd.nl,51.9229,4.40833 nostr-03.dorafactory.org,1.35208,103.82 -nostr-relay.zimage.com,34.282,-118.439 -relay.sigit.io,50.4754,12.3683 -relay.laantungir.net,-19.4692,-42.5315 -relay-admin.thaliyal.com,40.8218,-74.45 -relay.mwaters.net,50.9871,2.12554 -relay.lumina.rocks,49.0291,8.35695 -nostr.satstralia.com,64.1476,-21.9392 -nostr.sagaciousd.com,49.2827,-123.121 -relay.bullishbounty.com,43.6532,-79.3832 -wot.dergigi.com,64.1476,-21.9392 -relay.vrtmrz.net,43.6532,-79.3832 -nostr.0x7e.xyz,47.4988,8.72369 -articles.layer3.news,37.3387,-121.885 -relay.digitalezukunft.cyou,45.5019,-73.5674 -nostr.notribe.net,40.8302,-74.1299 -nostr.21crypto.ch,47.4988,8.72369 -relay.lifpay.me,1.35208,103.82 -relay.g1sms.fr,43.9432,2.07537 -relay.snort.social,43.6532,-79.3832 -relay.getsafebox.app,43.6532,-79.3832 -relay.moinsen.com,50.4754,12.3683 -nostr.blankfors.se,60.1699,24.9384 -nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832 -orangesync.tech,50.1109,8.68213 -relay.ru.ac.th,13.7584,100.622 -orangepiller.org,60.1699,24.9384 -relay.nostr.band,60.1699,24.9384 -wot.soundhsa.com,34.0479,-118.256 -khatru.nostrver.se,51.8933,4.42083 -nostr.hifish.org,47.4043,8.57398 -freelay.sovbit.host,64.1476,-21.9392 -nostr.bilthon.dev,25.8128,-80.2377 -nostr.night7.space,50.4754,12.3683 +ribo.us.nostria.app,41.5868,-93.625 relay.illuminodes.com,47.6061,-122.333 -relayone.soundhsa.com,34.0479,-118.256 -nostr.azzamo.net,52.2633,21.0283 -fenrir-s.notoshi.win,43.6532,-79.3832 -nostr.spicyz.io,43.6532,-79.3832 -nostrelay.memory-art.xyz,43.6532,-79.3832 +nostr.girino.org,43.6532,-79.3832 +yabu.me,35.6092,139.73 +relay.lightning.pub,41.8959,-88.2169 +noxir.kpherox.dev,34.8587,135.509 nostr.coincards.com,53.5501,-113.469 -alien.macneilmediagroup.com,43.6532,-79.3832 -nostrings-relay-dev.fly.dev,41.8781,-87.6298 -relay.satlantis.io,32.8769,-80.0114 -srtrelay.c-stellar.net,43.6532,-79.3832 +wot.nostr.net,43.6532,-79.3832 +nostrcheck.me,43.6532,-79.3832 +nostr.thebiglake.org,32.71,-96.6745 +nostr.rblb.it,43.4633,11.8796 +articles.layer3.news,37.3387,-121.885 +cyberspace.nostr1.com,40.7128,-74.006 +relay.21e6.cz,50.1682,14.0546 relay.agora.social,50.7383,15.0648 -relay.bitcoindistrict.org,43.6532,-79.3832 -relay.0xchat.com,1.35208,103.82 -wot.utxo.one,43.6532,-79.3832 -relay.bitcoinartclock.com,50.4754,12.3683 -nostr.zenon.network,43.5009,-70.4428 -wot.codingarena.top,50.4754,12.3683 -nostr-02.dorafactory.org,1.35208,103.82 -relay.nostromo.social,49.4543,11.0746 -theoutpost.life,64.1476,-21.9392 -strfry.shock.network,41.8959,-88.2169 -strfry.openhoofd.nl,51.9229,4.40833 -itanostr.space,52.2931,4.79099 -relay.barine.co,43.6532,-79.3832 nostr.middling.mydns.jp,35.8099,140.12 -relay.olas.app,50.4754,12.3683 -nostr.girino.org,43.6532,-79.3832 -nostr-relay.online,43.6532,-79.3832 -relay.evanverma.com,40.8302,-74.1299 -relay.angor.io,48.1046,11.6002 -relay.nostrdice.com,-33.8688,151.209 -ribo.us.nostria.app,41.5868,-93.625 -relay01.lnfi.network,39.0997,-94.5786 -nostr.namek.link,43.6532,-79.3832 relay.aloftus.io,34.0881,-118.379 -ithurtswhenip.ee,51.223,6.78245 -ribo.af.nostria.app,-26.2041,28.0473 -shu05.shugur.net,48.8566,2.35222 -noxir.kpherox.dev,34.8587,135.509 -relay.degmods.com,50.4754,12.3683 +shu01.shugur.net,21.4902,39.2246 +relay.zone667.com,60.1699,24.9384 +wot.sudocarlos.com,51.5072,-0.127586 +relay.nostrhub.tech,49.4543,11.0746 +relay-admin.thaliyal.com,40.8218,-74.45 +relay.toastr.net,40.8054,-74.0241 +relay.olas.app,50.4754,12.3683 +schnorr.me,43.6532,-79.3832 +nostr.einundzwanzig.space,50.1109,8.68213 relay.goodmorningbitcoin.com,43.6532,-79.3832 -nostr.4rs.nl,49.0291,8.35696 -nostr-relay.psfoundation.info,39.0438,-77.4874 -relay.hasenpfeffr.com,39.0438,-77.4874 -fanfares.nostr1.com,40.7128,-74.006 -relay.nosto.re,51.8933,4.42083 -nostr.liberty.fans,36.9104,-89.5875 +relay.holzeis.me,43.6532,-79.3832 +no.str.cr,9.92857,-84.0528 +nostr.thaliyal.com,40.8218,-74.45 +nostr.faultables.net,43.6532,-79.3832 +nostrelites.org,41.8781,-87.6298 nostr-2.21crypto.ch,47.4988,8.72369 -relay-rpi.edufeed.org,49.4543,11.0746 -offchain.pub,36.1809,-115.241 -nostr.88mph.life,43.6532,-79.3832 +wot.sovbit.host,64.1466,-21.9426 +relay.nostrcheck.me,43.6532,-79.3832 +nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832 +nostr.myshosholoza.co.za,52.3676,4.90414 +nostr-relay.zimage.com,34.282,-118.439 +nostr.carroarmato0.be,50.9928,3.26317 +relay.endfiat.money,43.6532,-79.3832 +relay.mostro.network,40.8302,-74.1299 +relay.lumina.rocks,49.0291,8.35695 +nostr.mikoshi.de,50.1109,8.68213 nostr.luisschwab.net,43.6532,-79.3832 -nostr.casa21.space,43.6532,-79.3832 +nostr-02.yakihonne.com,1.32123,103.695 +dizzyspells.nostr1.com,40.7128,-74.006 +relay.bitcoinveneto.org,64.1466,-21.9426 +nostr.liberty.fans,36.9104,-89.5875 +relay.freeplace.nl,52.3676,4.90414 +nostr.kungfu-g.rip,33.7946,-84.4488 +x.kojira.io,43.6532,-79.3832 +relay5.bitransfer.org,43.6532,-79.3832 +pyramid.fiatjaf.com,50.1109,8.68213 nostr.hekster.org,37.3986,-121.964 +nostr-relay.psfoundation.info,39.0438,-77.4874 +relay.fr13nd5.com,52.5233,13.3426 +relay.ngengine.org,43.6532,-79.3832 +relay03.lnfi.network,39.0997,-94.5786 +khatru.nostrver.se,51.8933,4.42083 +wheat.happytavern.co,43.6532,-79.3832 +nproxy.kristapsk.lv,60.1699,24.9384 relay.utxo.farm,35.6916,139.768 -zap.watch,45.5029,-73.5723 -nostr-pub.wellorder.net,45.5201,-122.99 -wot.sovbit.host,64.1466,-21.9426 -relay.endfiat.money,43.6532,-79.3832 -nostr2.girino.org,43.6532,-79.3832 -nostr.jerrynya.fun,31.2304,121.474 -nostr.spacecitynode.com,29.7057,-95.2706 relay.siamdev.cc,13.9178,100.424 +nostr.0x7e.xyz,47.4988,8.72369 +nostr.jerrynya.fun,31.2304,121.474 +librerelay.aaroniumii.com,43.6532,-79.3832 +relay.javi.space,43.4633,11.8796 +nostr.night7.space,50.4754,12.3683 +relay-dev.satlantis.io,40.8302,-74.1299 +relay.nostromo.social,49.4543,11.0746 +relay.nostraddress.com,43.6532,-79.3832 +strfry.felixzieger.de,50.1013,8.62643 +relay.mccormick.cx,52.3563,4.95714 +r.lostr.net,52.3676,4.90414 +relay.electriclifestyle.com,26.2897,-80.1293 +nostr.satstralia.com,64.1476,-21.9392 +nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874 +purpura.cloud,43.6532,-79.3832 +relay.dwadziesciajeden.pl,52.2297,21.0122 +relay.tapestry.ninja,40.8054,-74.0241 +adre.su,59.9311,30.3609 +nostr.chaima.info,51.223,6.78245 +a.nos.lol,50.4754,12.3683 +relay.laantungir.net,-19.4692,-42.5315 +nostr-02.dorafactory.org,1.35208,103.82 +nos.xmark.cc,50.6924,3.20113 +nostr.spicyz.io,43.6532,-79.3832 +relay.wavlake.com,41.2619,-95.8608 +relay.nostrhub.fr,48.1046,11.6002 +ribo.eu.nostria.app,52.3676,4.90414 +relay.btcforplebs.com,43.6532,-79.3832 +nostr.tac.lol,47.4748,-122.273 +nostr.makibisskey.work,43.6532,-79.3832 +nostr-relay.amethyst.name,39.0067,-77.4291 +relay.libernet.app,43.6532,-79.3832 +relay.digitalezukunft.cyou,45.5019,-73.5674 +nostr.casa21.space,43.6532,-79.3832 +relayone.geektank.ai,18.2148,-63.0574 +ynostr.yael.at,60.1699,24.9384 +shu02.shugur.net,21.4902,39.2246 +vitor.nostr1.com,40.7057,-74.0136 +wot.codingarena.top,50.4754,12.3683 +relay.g1sms.fr,43.9432,2.07537 +r.bitcoinhold.net,43.6532,-79.3832 +wot.utxo.one,43.6532,-79.3832 +nostr.mehdibekhtaoui.com,49.4939,-1.54813 +relay.seq1.net,43.6532,-79.3832 +relay.credenso.cafe,43.1149,-80.7228 +relay.nostr.place,32.7767,-96.797 +nostr.plantroon.com,50.1013,8.62643 +relay.0xchat.com,1.35208,103.82 +wot.dtonon.com,43.6532,-79.3832 +nostrelay.circum.space,51.2217,6.77616 +santo.iguanatech.net,40.8302,-74.1299 +nostr.kalf.org,52.3676,4.90414 +relay.artx.market,43.652,-79.3633 +relay.jeffg.fyi,43.6532,-79.3832 +theoutpost.life,64.1476,-21.9392 +orangesync.tech,50.1109,8.68213 +wot.sebastix.social,51.8933,4.42083 relay.notoshi.win,13.7829,100.546 -relay.nostx.io,43.6532,-79.3832 -relay.cypherflow.ai,48.8566,2.35222 +dev-relay.lnfi.network,39.0997,-94.5786 +nos.lol,50.4754,12.3683 +relay.bitcoindistrict.org,43.6532,-79.3832 +relay.magiccity.live,25.8128,-80.2377 +relay.npubhaus.com,43.6532,-79.3832 +nostr.vulpem.com,49.4543,11.0746 +nostr-relay.cbrx.io,43.6532,-79.3832 +relay.origin.land,35.6673,139.751 +nostr.bilthon.dev,25.8128,-80.2377 relay.letsfo.com,51.098,17.0321 -librerelay.aaroniumii.com,43.6532,-79.3832 +nostr.4rs.nl,49.0291,8.35696 +relay02.lnfi.network,39.0997,-94.5786 +free.relayted.de,50.1109,8.68213 +nostr.spaceshell.xyz,43.6532,-79.3832 +relay04.lnfi.network,39.0997,-94.5786 +relay.wolfcoil.com,35.6092,139.73 +relay.mwaters.net,50.9871,2.12554 +relayrs.notoshi.win,43.6532,-79.3832 +nostr2.girino.org,43.6532,-79.3832 +relay.hasenpfeffr.com,39.0438,-77.4874 gnostr.com,40.9017,29.1616 -nostr.pleb.one,38.6327,-90.1961 -nostr.mehdibekhtaoui.com,49.4939,-1.54813 -nostr.tadryanom.me,43.6532,-79.3832 -relay.orangepill.ovh,49.1689,-0.358841 -nostr.stakey.net,52.3676,4.90414 +relay.basspistol.org,46.2044,6.14316 +relay.internationalright-wing.org,-22.5022,-48.7114 +prl.plus,55.7623,37.6381 +relay.getsafebox.app,43.6532,-79.3832 +nostr.smut.cloud,43.6532,-79.3832 +relay.barine.co,43.6532,-79.3832 +alien.macneilmediagroup.com,43.6532,-79.3832 +nostr.davidebtc.me,51.5072,-0.127586 +fenrir-s.notoshi.win,43.6532,-79.3832 +satsage.xyz,37.3986,-121.964 +nostr-relay.online,43.6532,-79.3832 +nostr.lostr.space,43.6532,-79.3832 +nostr.red5d.dev,43.6532,-79.3832 +relay.guggero.org,47.3769,8.54169 nostr.rtvslawenia.com,49.4543,11.0746 -relay.damus.io,43.6532,-79.3832 -dev-relay.lnfi.network,39.0997,-94.5786 +ribo.af.nostria.app,-26.2041,28.0473 +relay.nostrdice.com,-33.8688,151.209 +relay.arx-ccn.com,50.4754,12.3683 +srtrelay.c-stellar.net,43.6532,-79.3832 +wot.soundhsa.com,34.0479,-118.256 +nostr.sathoarder.com,48.5734,7.75211 +wot.brightbolt.net,47.6735,-116.781 +nostr.data.haus,50.4754,12.3683 +relay.jmoose.rocks,60.1699,24.9384 slick.mjex.me,39.048,-77.4817 -wot.sudocarlos.com,51.5072,-0.127586 -relay04.lnfi.network,39.0997,-94.5786 +nostr.2b9t.xyz,34.0549,-118.243 +relay.cypherflow.ai,48.8566,2.35222 +nostr.overmind.lol,43.6532,-79.3832 +relay.mattybs.lol,43.6532,-79.3832 +nostr.lojong.info,43.6532,-79.3832 +relay.davidebtc.me,51.5072,-0.127586 +relay-testnet.k8s.layer3.news,37.3387,-121.885 +nostr.stakey.net,52.3676,4.90414 relay.13room.space,43.6532,-79.3832 -relay.conduit.market,43.6532,-79.3832 -nostr.chaima.info,51.223,6.78245 +strfry.elswa-dev.online,48.8566,2.35222 +relay.bitcoinartclock.com,50.4754,12.3683 +nostr.tadryanom.me,43.6532,-79.3832 +nostr-pub.wellorder.net,45.5201,-122.99 +nostr.hifish.org,47.4043,8.57398 +4u2ni0zjbjvni.clorecloud.net,43.6532,-79.3832 +nostr.mom,50.4754,12.3683 +dev-nostr.bityacht.io,25.0797,121.234 +relay.evanverma.com,40.8302,-74.1299 +nostr.rikmeijer.nl,50.4754,12.3683 +nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832 +nostr.azzamo.net,52.2633,21.0283 +nostr-01.yakihonne.com,1.32123,103.695 +relay.moinsen.com,50.4754,12.3683 +nostr.notribe.net,40.8302,-74.1299 +nostr.coincrowd.fund,39.0438,-77.4874 +relay.degmods.com,50.4754,12.3683 +relay.angor.io,48.1046,11.6002 +relay.sigit.io,50.4754,12.3683 +relay2.angor.io,48.1046,11.6002 +relay.cosmicbolt.net,37.3986,-121.964 From 696f698046724b467da465fd49945e69779aec8c Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 30 Sep 2025 12:52:56 +0400 Subject: [PATCH 03/14] refactor: new close button like ios(but not liquid glass) --- .../java/com/bitchat/android/ui/AboutSheet.kt | 36 +++++++++++++------ .../android/ui/LocationChannelsSheet.kt | 14 +++----- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt b/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt index bce3ef1c2..08fde4279 100644 --- a/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bluetooth +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Public import androidx.compose.material.icons.filled.Security @@ -550,24 +551,39 @@ fun AboutSheet( .height(64.dp) .background(MaterialTheme.colorScheme.background.copy(alpha = topBarAlpha)) ) { - TextButton( + CloseButton( onClick = onDismiss, - modifier = Modifier + modifier = modifier .align(Alignment.CenterEnd) - .padding(horizontal = 16.dp) - ) { - Text( - text = "Close", - style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold), - color = MaterialTheme.colorScheme.onBackground - ) - } + .padding(horizontal = 16.dp), + ) } } } } } +@Composable +fun CloseButton( + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + IconButton( + onClick = onClick, + modifier = modifier + .size(32.dp), + colors = IconButtonDefaults.iconButtonColors( + contentColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f), + containerColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.1f) + ) + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Close", + modifier = Modifier.size(18.dp) + ) + } +} /** * Password prompt dialog for password-protected channels * Kept as dialog since it requires user input diff --git a/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt b/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt index fd8ad1eb7..693ae3f11 100644 --- a/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt @@ -551,18 +551,12 @@ fun LocationChannelsSheet( .height(56.dp) .background(MaterialTheme.colorScheme.background.copy(alpha = topBarAlpha)) ) { - TextButton( + CloseButton( onClick = onDismiss, - modifier = Modifier + modifier = modifier .align(Alignment.CenterEnd) - .padding(horizontal = 16.dp) - ) { - Text( - text = "Close", - style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold), - color = MaterialTheme.colorScheme.onBackground - ) - } + .padding(horizontal = 16.dp), + ) } } } From 16d67d306be362ae251316b55c485be60a73361c Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 30 Sep 2025 16:31:47 +0400 Subject: [PATCH 04/14] feat: Migrate from Parcelable to Kotlinx Serialization Replaced `Parcelable` with `kotlinx.serialization.Serializable` across data models to improve serialization performance and maintainability. - Replaced the `kotlin-parcelize` plugin with `kotlin-serialization`. - Added `kotlinx-serialization-json` dependency. - Updated `IdentityAnnouncement`, `BitchatMessage`, `BitchatPacket`, `NoisePayload`, `PrivateMessagePacket`, and `ReadReceipt` to use `@Serializable`. - Introduced a new `Serializers.kt` file with custom serializers for `Date`, `ByteArray`, `UByte`, and `ULong` to ensure compatibility. --- app/build.gradle.kts | 5 +- .../bitchat/android/model/BitchatMessage.kt | 37 +++++----- .../android/model/IdentityAnnouncement.kt | 8 +-- .../bitchat/android/model/NoiseEncrypted.kt | 16 ++--- .../android/protocol/BinaryProtocol.kt | 7 +- .../com/bitchat/android/util/Serializers.kt | 72 +++++++++++++++++++ build.gradle.kts | 1 + gradle/libs.versions.toml | 8 ++- 8 files changed, 117 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/com/bitchat/android/util/Serializers.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 30ecc6cc1..769cf8bd7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,7 +1,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) - alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.compose) } @@ -89,6 +89,9 @@ dependencies { // Coroutines implementation(libs.kotlinx.coroutines.android) + // Serialization + implementation(libs.kotlinx.serialization.json) + // Bluetooth implementation(libs.nordic.ble) diff --git a/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt b/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt index 8e1731b1a..e514ead2f 100644 --- a/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt +++ b/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt @@ -1,14 +1,14 @@ package com.bitchat.android.model -import android.os.Parcelable -import com.google.gson.GsonBuilder -import kotlinx.parcelize.Parcelize +import com.bitchat.android.util.DateSerializer +import com.bitchat.android.util.ByteArraySerializer +import kotlinx.serialization.Serializable import java.nio.ByteBuffer import java.nio.ByteOrder import java.util.* -@Parcelize -enum class BitchatMessageType : Parcelable { +@Serializable +enum class BitchatMessageType { Message, Audio, Image, @@ -18,23 +18,24 @@ enum class BitchatMessageType : Parcelable { /** * Delivery status for messages - exact same as iOS version */ -sealed class DeliveryStatus : Parcelable { - @Parcelize +@Serializable +sealed class DeliveryStatus { + @Serializable object Sending : DeliveryStatus() - @Parcelize + @Serializable object Sent : DeliveryStatus() - @Parcelize - data class Delivered(val to: String, val at: Date) : DeliveryStatus() + @Serializable + data class Delivered(val to: String, @Serializable(with = DateSerializer::class) val at: Date) : DeliveryStatus() - @Parcelize - data class Read(val by: String, val at: Date) : DeliveryStatus() + @Serializable + data class Read(val by: String, @Serializable(with = DateSerializer::class) val at: Date) : DeliveryStatus() - @Parcelize + @Serializable data class Failed(val reason: String) : DeliveryStatus() - @Parcelize + @Serializable data class PartiallyDelivered(val reached: Int, val total: Int) : DeliveryStatus() fun getDisplayText(): String { @@ -52,13 +53,13 @@ sealed class DeliveryStatus : Parcelable { /** * BitchatMessage - 100% compatible with iOS version */ -@Parcelize +@Serializable data class BitchatMessage( val id: String = UUID.randomUUID().toString().uppercase(), val sender: String, val content: String, val type: BitchatMessageType = BitchatMessageType.Message, - val timestamp: Date, + @kotlinx.serialization.Serializable(with = DateSerializer::class) val timestamp: Date, val isRelay: Boolean = false, val originalSender: String? = null, val isPrivate: Boolean = false, @@ -66,11 +67,11 @@ data class BitchatMessage( val senderPeerID: String? = null, val mentions: List? = null, val channel: String? = null, - val encryptedContent: ByteArray? = null, + @Serializable(with = ByteArraySerializer::class) val encryptedContent: ByteArray? = null, val isEncrypted: Boolean = false, val deliveryStatus: DeliveryStatus? = null, val powDifficulty: Int? = null -) : Parcelable { +) { /** * Convert message to binary payload format - exactly same as iOS version diff --git a/app/src/main/java/com/bitchat/android/model/IdentityAnnouncement.kt b/app/src/main/java/com/bitchat/android/model/IdentityAnnouncement.kt index 2dfbe9c23..2a37ba174 100644 --- a/app/src/main/java/com/bitchat/android/model/IdentityAnnouncement.kt +++ b/app/src/main/java/com/bitchat/android/model/IdentityAnnouncement.kt @@ -1,19 +1,17 @@ package com.bitchat.android.model -import android.os.Parcelable -import kotlinx.parcelize.Parcelize -import com.bitchat.android.util.* +import kotlinx.serialization.Serializable /** * Identity announcement structure with TLV encoding * Compatible with iOS AnnouncementPacket TLV format */ -@Parcelize +@Serializable data class IdentityAnnouncement( val nickname: String, val noisePublicKey: ByteArray, // Noise static public key (Curve25519.KeyAgreement) val signingPublicKey: ByteArray // Ed25519 public key for signing -) : Parcelable { +) { /** * TLV types matching iOS implementation diff --git a/app/src/main/java/com/bitchat/android/model/NoiseEncrypted.kt b/app/src/main/java/com/bitchat/android/model/NoiseEncrypted.kt index 7f691a9cc..0a321ff42 100644 --- a/app/src/main/java/com/bitchat/android/model/NoiseEncrypted.kt +++ b/app/src/main/java/com/bitchat/android/model/NoiseEncrypted.kt @@ -1,7 +1,7 @@ package com.bitchat.android.model -import android.os.Parcelable -import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable + /** * Noise encrypted payload types and handling - 100% compatible with iOS SimplifiedBluetoothService @@ -35,11 +35,11 @@ enum class NoisePayloadType(val value: UByte) { * Helper class for creating and parsing Noise payloads * Matches iOS NoisePayload helper exactly */ -@Parcelize +@Serializable data class NoisePayload( val type: NoisePayloadType, val data: ByteArray -) : Parcelable { +) { /** * Encode payload with type prefix - exactly like iOS @@ -97,11 +97,11 @@ data class NoisePayload( /** * Private message packet with TLV encoding - matches iOS PrivateMessagePacket exactly */ -@Parcelize +@Serializable data class PrivateMessagePacket( val messageID: String, val content: String -) : Parcelable { +) { /** * TLV types matching iOS implementation exactly @@ -197,8 +197,8 @@ data class PrivateMessagePacket( /** * Read receipt data class for transport compatibility */ -@Parcelize +@Serializable data class ReadReceipt( val originalMessageID: String, val readerPeerID: String? = null -) : Parcelable +) diff --git a/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt b/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt index 692d95133..023811bdd 100644 --- a/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt +++ b/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt @@ -1,10 +1,9 @@ package com.bitchat.android.protocol -import android.os.Parcelable -import kotlinx.parcelize.Parcelize import java.nio.ByteBuffer import java.nio.ByteOrder import android.util.Log +import kotlinx.serialization.Serializable /** * Message types - exact same as iOS version with Noise Protocol support @@ -50,7 +49,7 @@ object SpecialRecipients { * - Payload: Variable length (includes original size if compressed) * - Signature: 64 bytes (if hasSignature flag set) */ -@Parcelize +@Serializable data class BitchatPacket( val version: UByte = 1u, val type: UByte, @@ -60,7 +59,7 @@ data class BitchatPacket( val payload: ByteArray, var signature: ByteArray? = null, // Changed from val to var for packet signing var ttl: UByte -) : Parcelable { +) { constructor( type: UByte, diff --git a/app/src/main/java/com/bitchat/android/util/Serializers.kt b/app/src/main/java/com/bitchat/android/util/Serializers.kt new file mode 100644 index 000000000..de6a540c1 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/util/Serializers.kt @@ -0,0 +1,72 @@ +package com.bitchat.android.util + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.util.* +import android.util.Base64 + +//TODO DELETE WHEN MIGRATE TO KOTLINX DATE TIME + +/** + * Serializer for Date objects using milliseconds since epoch + */ +object DateSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: Date) { + encoder.encodeLong(value.time) + } + + override fun deserialize(decoder: Decoder): Date { + return Date(decoder.decodeLong()) + } +} + +/** + * Serializer for ByteArray using Base64 encoding + */ +object ByteArraySerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ByteArray", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: ByteArray) { + encoder.encodeString(Base64.encodeToString(value, Base64.NO_WRAP)) + } + + override fun deserialize(decoder: Decoder): ByteArray { + return Base64.decode(decoder.decodeString(), Base64.NO_WRAP) + } +} + +/** + * Serializer for UByte values + */ +object UByteSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UByte", PrimitiveKind.INT) + + override fun serialize(encoder: Encoder, value: UByte) { + encoder.encodeInt(value.toInt()) + } + + override fun deserialize(decoder: Decoder): UByte { + return decoder.decodeInt().toUByte() + } +} + +/** + * Serializer for ULong values + */ +object ULongSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ULong", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: ULong) { + encoder.encodeLong(value.toLong()) + } + + override fun deserialize(decoder: Decoder): ULong { + return decoder.decodeLong().toULong() + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 440826843..e7f5ce3ae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.kotlin.serialization) apply false } tasks.whenTaskAdded { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e6dda224a..62409acb0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,9 @@ gson = "2.13.1" # Coroutines kotlinx-coroutines = "1.10.2" +# Serialization +kotlinx-serialization = "1.9.0" + # Bluetooth nordic-ble = "2.6.1" @@ -91,6 +94,9 @@ gson = { module = "com.google.code.gson:gson", version.ref = "gson" } # Coroutines kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } +# Serialization +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } + # Bluetooth nordic-ble = { module = "no.nordicsemi.android:ble", version.ref = "nordic-ble" } @@ -121,7 +127,7 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -kotlin-parcelize = { id = "kotlin-parcelize" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } [bundles] From ee0883508628100658c507e2ffc3a54f1c1a3889 Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 30 Sep 2025 17:13:21 +0400 Subject: [PATCH 05/14] Merge branch 'permissionlesstech:main' into main --- .../com/bitchat/android/nostr/NostrFilter.kt | 250 +++++++++--------- 1 file changed, 127 insertions(+), 123 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrFilter.kt b/app/src/main/java/com/bitchat/android/nostr/NostrFilter.kt index 0525d1842..a21d9adb6 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrFilter.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrFilter.kt @@ -61,136 +61,140 @@ data class NostrFilter( fun forEvents(ids: List): NostrFilter { return NostrFilter(ids = ids) } - } - - /** - * Custom JSON serializer to handle tag filters properly - */ - class FilterSerializer : JsonSerializer { - override fun serialize(src: NostrFilter, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - val jsonObject = JsonObject() - - // Standard fields - src.ids?.let { jsonObject.add("ids", context.serialize(it)) } - src.authors?.let { jsonObject.add("authors", context.serialize(it)) } - src.kinds?.let { jsonObject.add("kinds", context.serialize(it)) } - src.since?.let { jsonObject.addProperty("since", it) } - src.until?.let { jsonObject.addProperty("until", it) } - src.limit?.let { jsonObject.addProperty("limit", it) } - - // Tag filters with # prefix - src.tagFilters?.forEach { (tag, values) -> - jsonObject.add("#$tag", context.serialize(values)) - } - - return jsonObject - } - } - - /** - * Create builder for complex filters - */ - class Builder { - private var ids: List? = null - private var authors: List? = null - private var kinds: List? = null - private var since: Int? = null - private var until: Int? = null - private var limit: Int? = null - private val tagFilters = mutableMapOf>() - - fun ids(vararg ids: String) = apply { this.ids = ids.toList() } - fun authors(vararg authors: String) = apply { this.authors = authors.toList() } - fun kinds(vararg kinds: Int) = apply { this.kinds = kinds.toList() } - fun since(timestamp: Long) = apply { this.since = (timestamp / 1000).toInt() } - fun until(timestamp: Long) = apply { this.until = (timestamp / 1000).toInt() } - fun limit(count: Int) = apply { this.limit = count } - - fun tagP(vararg pubkeys: String) = apply { tagFilters["p"] = pubkeys.toList() } - fun tagE(vararg eventIds: String) = apply { tagFilters["e"] = eventIds.toList() } - fun tagG(vararg geohashes: String) = apply { tagFilters["g"] = geohashes.toList() } - fun tag(name: String, vararg values: String) = apply { tagFilters[name] = values.toList() } - - fun build(): NostrFilter { - return NostrFilter( - ids = ids, - authors = authors, - kinds = kinds, - since = since, - until = until, - limit = limit, - tagFilters = tagFilters.toMap() - ) - } - } - - /** - * Check if this filter matches an event - */ - fun matches(event: NostrEvent): Boolean { - // Check IDs - if (ids != null && !ids.contains(event.id)) { - return false - } - - // Check authors - if (authors != null && !authors.contains(event.pubkey)) { - return false - } - - // Check kinds - if (kinds != null && !kinds.contains(event.kind)) { - return false - } - - // Check time bounds - if (since != null && event.createdAt < since) { - return false + + /** + * Convert NostrFilter to JsonElement for serialization + */ + fun toJsonElement(filter: NostrFilter): JsonElement { + return filter.toJsonElement() } - - if (until != null && event.createdAt > until) { - return false + + /** + * Create builder for complex filters + */ + class Builder { + private var ids: List? = null + private var authors: List? = null + private var kinds: List? = null + private var since: Int? = null + private var until: Int? = null + private var limit: Int? = null + private val tagFilters = mutableMapOf>() + + fun ids(vararg ids: String) = apply { this.ids = ids.toList() } + fun authors(vararg authors: String) = apply { this.authors = authors.toList() } + fun kinds(vararg kinds: Int) = apply { this.kinds = kinds.toList() } + fun since(timestamp: Long) = apply { this.since = (timestamp / 1000).toInt() } + fun until(timestamp: Long) = apply { this.until = (timestamp / 1000).toInt() } + fun limit(count: Int) = apply { this.limit = count } + + fun tagP(vararg pubkeys: String) = apply { tagFilters["p"] = pubkeys.toList() } + fun tagE(vararg eventIds: String) = apply { tagFilters["e"] = eventIds.toList() } + fun tagG(vararg geohashes: String) = apply { tagFilters["g"] = geohashes.toList() } + fun tag(name: String, vararg values: String) = + apply { tagFilters[name] = values.toList() } + + fun build(): NostrFilter { + return NostrFilter( + ids = ids, + authors = authors, + kinds = kinds, + since = since, + until = until, + limit = limit, + tagFilters = tagFilters.toMap() + ) + } } - - // Check tag filters - if (tagFilters != null) { - for ((tagName, requiredValues) in tagFilters) { - val eventTags = event.tags.filter { it.isNotEmpty() && it[0] == tagName } - val eventValues = eventTags.mapNotNull { tag -> - if (tag.size > 1) tag[1] else null - } - - val hasMatch = requiredValues.any { requiredValue -> - eventValues.contains(requiredValue) + + /** + * Convert this filter to JsonElement for serialization + */ + fun toJsonElement(): JsonElement { + return buildJsonObject { + // Standard fields + ids?.let { put("ids", JsonArray(it.map { JsonPrimitive(it) })) } + authors?.let { put("authors", JsonArray(it.map { JsonPrimitive(it) })) } + kinds?.let { put("kinds", JsonArray(it.map { JsonPrimitive(it) })) } + since?.let { put("since", JsonPrimitive(it)) } + until?.let { put("until", JsonPrimitive(it)) } + limit?.let { put("limit", JsonPrimitive(it)) } + + // Tag filters with # prefix + tagFilters?.forEach { (tag, values) -> + put("#$tag", JsonArray(values.map { JsonPrimitive(it) })) } - - if (!hasMatch) { - return false + } + } + + /** + * Check if this filter matches an event + */ + fun matches(event: NostrEvent): Boolean { + // Check IDs + if (ids != null && !ids.contains(event.id)) { + return false + } + + // Check authors + if (authors != null && !authors.contains(event.pubkey)) { + return false + } + + // Check kinds + if (kinds != null && !kinds.contains(event.kind)) { + return false + } + + // Check time bounds + if (since != null && event.createdAt < since) { + return false + } + + if (until != null && event.createdAt > until) { + return false + } + + // Check tag filters + if (tagFilters != null) { + for ((tagName, requiredValues) in tagFilters) { + val eventTags = event.tags.filter { it.isNotEmpty() && it[0] == tagName } + val eventValues = eventTags.mapNotNull { tag -> + if (tag.size > 1) tag[1] else null + } + + val hasMatch = requiredValues.any { requiredValue -> + eventValues.contains(requiredValue) + } + + if (!hasMatch) { + return false + } } } + + return true } - - return true - } - - /** - * Get debug description - */ - fun getDebugDescription(): String { - val parts = mutableListOf() - - ids?.let { parts.add("ids=${it.size}") } - authors?.let { parts.add("authors=${it.size}") } - kinds?.let { parts.add("kinds=$it") } - since?.let { parts.add("since=$it") } - until?.let { parts.add("until=$it") } - limit?.let { parts.add("limit=$it") } - tagFilters?.let { filters -> - filters.forEach { (tag, values) -> - parts.add("#$tag=${values.size}") + + /** + * Get debug description + */ + fun getDebugDescription(): String { + val parts = mutableListOf() + + ids?.let { parts.add("ids=${it.size}") } + authors?.let { parts.add("authors=${it.size}") } + kinds?.let { parts.add("kinds=$it") } + since?.let { parts.add("since=$it") } + until?.let { parts.add("until=$it") } + limit?.let { parts.add("limit=$it") } + tagFilters?.let { filters -> + filters.forEach { (tag, values) -> + parts.add("#$tag=${values.size}") + } } + + return "NostrFilter(${parts.joinToString(", ")})" } - - return "NostrFilter(${parts.joinToString(", ")})" } } From 95e08d4aff1cee20be9920cd5773f4f915e29541 Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 30 Sep 2025 18:07:58 +0400 Subject: [PATCH 06/14] feat: Add JsonUtil to replace Gson Introduces `JsonUtil`, a Kotlin object that serves as a wrapper around the `kotlinx.serialization` library. This utility provides methods for serializing objects to JSON and deserializing JSON strings to objects, including safe versions that return null on error. It is configured to be lenient and ignore unknown keys. --- .../java/com/bitchat/android/util/JsonUtil.kt | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 app/src/main/java/com/bitchat/android/util/JsonUtil.kt diff --git a/app/src/main/java/com/bitchat/android/util/JsonUtil.kt b/app/src/main/java/com/bitchat/android/util/JsonUtil.kt new file mode 100644 index 000000000..168810ea0 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/util/JsonUtil.kt @@ -0,0 +1,67 @@ +package com.bitchat.android.util + +import kotlinx.serialization.json.Json +import kotlinx.serialization.KSerializer + +/** + * JSON utility using kotlinx.serialization to replace Gson + */ +object JsonUtil { + + val json = Json { + ignoreUnknownKeys = true + isLenient = true + encodeDefaults = true + prettyPrint = false + } + + /** + * Serialize object to JSON string + */ + inline fun toJson(value: T): String { + return json.encodeToString(value) + } + + /** + * Serialize object to JSON string with custom serializer + */ + fun toJson(serializer: KSerializer, value: T): String { + return json.encodeToString(serializer, value) + } + + /** + * Deserialize JSON string to object + */ + inline fun fromJson(jsonString: String): T { + return json.decodeFromString(jsonString) + } + + /** + * Deserialize JSON string to object with custom serializer + */ + fun fromJson(serializer: KSerializer, jsonString: String): T { + return json.decodeFromString(serializer, jsonString) + } + + /** + * Safe deserialization that returns null on error + */ + inline fun fromJsonOrNull(jsonString: String): T? { + return try { + json.decodeFromString(jsonString) + } catch (e: Exception) { + null + } + } + + /** + * Safe deserialization with custom serializer that returns null on error + */ + fun fromJsonOrNull(serializer: KSerializer, jsonString: String): T? { + return try { + json.decodeFromString(serializer, jsonString) + } catch (e: Exception) { + null + } + } +} \ No newline at end of file From 045db11506bbd64664115b59c45a61819fa02016 Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 30 Sep 2025 18:10:14 +0400 Subject: [PATCH 07/14] feat: Migrate from Gson to Kotlinx Serialization Replaced Gson with `kotlinx.serialization` for JSON handling across the application. This improves performance and ensures better integration with Kotlin. - Updated `NostrEvent`, `NostrFilter`, `BitchatMessage`, `IdentityAnnouncement`, `BitchatPacket`, and `SeenMessageStore` data classes to use `@Serializable`. - Replaced `@SerializedName` with `@SerialName`. - Created custom `ByteArraySerializer`, `UByteSerializer`, and `ULongSerializer` for handling specific types in `BitchatPacket` and `IdentityAnnouncement`. - Introduced a `JsonUtil` helper to centralize serialization logic. - Removed the Gson dependency and related imports. - Refactored `NostrFilter` to move helper functions and the `Builder` class into a companion object. --- .../bitchat/android/model/BitchatMessage.kt | 2 +- .../android/model/IdentityAnnouncement.kt | 5 +- .../com/bitchat/android/nostr/NostrEvent.kt | 22 +- .../com/bitchat/android/nostr/NostrFilter.kt | 201 +++++++++--------- .../android/protocol/BinaryProtocol.kt | 19 +- .../android/services/SeenMessageStore.kt | 10 +- 6 files changed, 129 insertions(+), 130 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt b/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt index e514ead2f..895c2fb5c 100644 --- a/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt +++ b/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt @@ -59,7 +59,7 @@ data class BitchatMessage( val sender: String, val content: String, val type: BitchatMessageType = BitchatMessageType.Message, - @kotlinx.serialization.Serializable(with = DateSerializer::class) val timestamp: Date, + @Serializable(with = DateSerializer::class) val timestamp: Date, val isRelay: Boolean = false, val originalSender: String? = null, val isPrivate: Boolean = false, diff --git a/app/src/main/java/com/bitchat/android/model/IdentityAnnouncement.kt b/app/src/main/java/com/bitchat/android/model/IdentityAnnouncement.kt index 2a37ba174..f47056ea7 100644 --- a/app/src/main/java/com/bitchat/android/model/IdentityAnnouncement.kt +++ b/app/src/main/java/com/bitchat/android/model/IdentityAnnouncement.kt @@ -1,6 +1,7 @@ package com.bitchat.android.model import kotlinx.serialization.Serializable +import com.bitchat.android.util.ByteArraySerializer /** * Identity announcement structure with TLV encoding @@ -9,8 +10,8 @@ import kotlinx.serialization.Serializable @Serializable data class IdentityAnnouncement( val nickname: String, - val noisePublicKey: ByteArray, // Noise static public key (Curve25519.KeyAgreement) - val signingPublicKey: ByteArray // Ed25519 public key for signing + @Serializable(with = ByteArraySerializer::class) val noisePublicKey: ByteArray, // Noise static public key (Curve25519.KeyAgreement) + @Serializable(with = ByteArraySerializer::class) val signingPublicKey: ByteArray // Ed25519 public key for signing ) { /** diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt b/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt index 785b41f37..c7535306b 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt @@ -1,18 +1,19 @@ package com.bitchat.android.nostr -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import com.bitchat.android.util.JsonUtil import java.security.MessageDigest /** * Nostr Event structure following NIP-01 * Compatible with iOS implementation */ +@Serializable data class NostrEvent( var id: String = "", val pubkey: String, - @SerializedName("created_at") val createdAt: Int, + @SerialName("created_at") val createdAt: Int, val kind: Int, val tags: List>, val content: String, @@ -43,12 +44,7 @@ data class NostrEvent( * Create from JSON string */ fun fromJsonString(jsonString: String): NostrEvent? { - return try { - val gson = Gson() - gson.fromJson(jsonString, NostrEvent::class.java) - } catch (e: Exception) { - null - } + return JsonUtil.fromJsonOrNull(jsonString) } /** @@ -131,8 +127,7 @@ data class NostrEvent( ) // Convert to JSON without escaping slashes (compact format) - val gson = GsonBuilder().disableHtmlEscaping().create() - val jsonString = gson.toJson(serialized) + val jsonString = JsonUtil.toJson(serialized) // SHA256 hash of the JSON string val digest = MessageDigest.getInstance("SHA-256") @@ -161,8 +156,7 @@ data class NostrEvent( * Convert to JSON string */ fun toJsonString(): String { - val gson = Gson() - return gson.toJson(this) + return JsonUtil.toJson(this) } /** diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrFilter.kt b/app/src/main/java/com/bitchat/android/nostr/NostrFilter.kt index a21d9adb6..2f24f041c 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrFilter.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrFilter.kt @@ -1,13 +1,13 @@ package com.bitchat.android.nostr -import com.google.gson.* -import com.google.gson.annotations.SerializedName -import java.lang.reflect.Type +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.* /** * Nostr event filter for subscriptions * Compatible with iOS implementation */ +@Serializable data class NostrFilter( val ids: List? = null, val authors: List? = null, @@ -17,6 +17,102 @@ data class NostrFilter( val limit: Int? = null, private val tagFilters: Map>? = null ) { + /** + * Convert NostrFilter to JsonElement for serialization + */ + fun toJsonElement(filter: NostrFilter): JsonElement { + return filter.toJsonElement() + } + + /** + * Convert this filter to JsonElement for serialization + */ + fun toJsonElement(): JsonElement { + return buildJsonObject { + // Standard fields + ids?.let { put("ids", JsonArray(it.map { JsonPrimitive(it) })) } + authors?.let { put("authors", JsonArray(it.map { JsonPrimitive(it) })) } + kinds?.let { put("kinds", JsonArray(it.map { JsonPrimitive(it) })) } + since?.let { put("since", JsonPrimitive(it)) } + until?.let { put("until", JsonPrimitive(it)) } + limit?.let { put("limit", JsonPrimitive(it)) } + + // Tag filters with # prefix + tagFilters?.forEach { (tag, values) -> + put("#$tag", JsonArray(values.map { JsonPrimitive(it) })) + } + } + } + + /** + * Check if this filter matches an event + */ + fun matches(event: NostrEvent): Boolean { + // Check IDs + if (ids != null && !ids.contains(event.id)) { + return false + } + + // Check authors + if (authors != null && !authors.contains(event.pubkey)) { + return false + } + + // Check kinds + if (kinds != null && !kinds.contains(event.kind)) { + return false + } + + // Check time bounds + if (since != null && event.createdAt < since) { + return false + } + + if (until != null && event.createdAt > until) { + return false + } + + // Check tag filters + if (tagFilters != null) { + for ((tagName, requiredValues) in tagFilters) { + val eventTags = event.tags.filter { it.isNotEmpty() && it[0] == tagName } + val eventValues = eventTags.mapNotNull { tag -> + if (tag.size > 1) tag[1] else null + } + + val hasMatch = requiredValues.any { requiredValue -> + eventValues.contains(requiredValue) + } + + if (!hasMatch) { + return false + } + } + } + + return true + } + + /** + * Get debug description + */ + fun getDebugDescription(): String { + val parts = mutableListOf() + + ids?.let { parts.add("ids=${it.size}") } + authors?.let { parts.add("authors=${it.size}") } + kinds?.let { parts.add("kinds=$it") } + since?.let { parts.add("since=$it") } + until?.let { parts.add("until=$it") } + limit?.let { parts.add("limit=$it") } + tagFilters?.let { filters -> + filters.forEach { (tag, values) -> + parts.add("#$tag=${values.size}") + } + } + + return "NostrFilter(${parts.joinToString(", ")})" + } companion object { /** @@ -54,7 +150,7 @@ data class NostrFilter( limit = limit ) } - + /** * Create filter for specific event IDs */ @@ -62,13 +158,6 @@ data class NostrFilter( return NostrFilter(ids = ids) } - /** - * Convert NostrFilter to JsonElement for serialization - */ - fun toJsonElement(filter: NostrFilter): JsonElement { - return filter.toJsonElement() - } - /** * Create builder for complex filters */ @@ -106,95 +195,5 @@ data class NostrFilter( ) } } - - /** - * Convert this filter to JsonElement for serialization - */ - fun toJsonElement(): JsonElement { - return buildJsonObject { - // Standard fields - ids?.let { put("ids", JsonArray(it.map { JsonPrimitive(it) })) } - authors?.let { put("authors", JsonArray(it.map { JsonPrimitive(it) })) } - kinds?.let { put("kinds", JsonArray(it.map { JsonPrimitive(it) })) } - since?.let { put("since", JsonPrimitive(it)) } - until?.let { put("until", JsonPrimitive(it)) } - limit?.let { put("limit", JsonPrimitive(it)) } - - // Tag filters with # prefix - tagFilters?.forEach { (tag, values) -> - put("#$tag", JsonArray(values.map { JsonPrimitive(it) })) - } - } - } - - /** - * Check if this filter matches an event - */ - fun matches(event: NostrEvent): Boolean { - // Check IDs - if (ids != null && !ids.contains(event.id)) { - return false - } - - // Check authors - if (authors != null && !authors.contains(event.pubkey)) { - return false - } - - // Check kinds - if (kinds != null && !kinds.contains(event.kind)) { - return false - } - - // Check time bounds - if (since != null && event.createdAt < since) { - return false - } - - if (until != null && event.createdAt > until) { - return false - } - - // Check tag filters - if (tagFilters != null) { - for ((tagName, requiredValues) in tagFilters) { - val eventTags = event.tags.filter { it.isNotEmpty() && it[0] == tagName } - val eventValues = eventTags.mapNotNull { tag -> - if (tag.size > 1) tag[1] else null - } - - val hasMatch = requiredValues.any { requiredValue -> - eventValues.contains(requiredValue) - } - - if (!hasMatch) { - return false - } - } - } - - return true - } - - /** - * Get debug description - */ - fun getDebugDescription(): String { - val parts = mutableListOf() - - ids?.let { parts.add("ids=${it.size}") } - authors?.let { parts.add("authors=${it.size}") } - kinds?.let { parts.add("kinds=$it") } - since?.let { parts.add("since=$it") } - until?.let { parts.add("until=$it") } - limit?.let { parts.add("limit=$it") } - tagFilters?.let { filters -> - filters.forEach { (tag, values) -> - parts.add("#$tag=${values.size}") - } - } - - return "NostrFilter(${parts.joinToString(", ")})" - } } } diff --git a/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt b/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt index 023811bdd..545b7d345 100644 --- a/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt +++ b/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt @@ -4,6 +4,9 @@ import java.nio.ByteBuffer import java.nio.ByteOrder import android.util.Log import kotlinx.serialization.Serializable +import com.bitchat.android.util.ByteArraySerializer +import com.bitchat.android.util.UByteSerializer +import com.bitchat.android.util.ULongSerializer /** * Message types - exact same as iOS version with Noise Protocol support @@ -51,14 +54,14 @@ object SpecialRecipients { */ @Serializable data class BitchatPacket( - val version: UByte = 1u, - val type: UByte, - val senderID: ByteArray, - val recipientID: ByteArray? = null, - val timestamp: ULong, - val payload: ByteArray, - var signature: ByteArray? = null, // Changed from val to var for packet signing - var ttl: UByte + @Serializable(with = UByteSerializer::class) val version: UByte = 1u, + @Serializable(with = UByteSerializer::class) val type: UByte, + @Serializable(with = ByteArraySerializer::class) val senderID: ByteArray, + @Serializable(with = ByteArraySerializer::class) val recipientID: ByteArray? = null, + @Serializable(with = ULongSerializer::class) val timestamp: ULong, + @Serializable(with = ByteArraySerializer::class) val payload: ByteArray, + @Serializable(with = ByteArraySerializer::class) var signature: ByteArray? = null, // Changed from val to var for packet signing + @Serializable(with = UByteSerializer::class) var ttl: UByte ) { constructor( diff --git a/app/src/main/java/com/bitchat/android/services/SeenMessageStore.kt b/app/src/main/java/com/bitchat/android/services/SeenMessageStore.kt index 469525d74..bf8c0df4d 100644 --- a/app/src/main/java/com/bitchat/android/services/SeenMessageStore.kt +++ b/app/src/main/java/com/bitchat/android/services/SeenMessageStore.kt @@ -3,7 +3,8 @@ package com.bitchat.android.services import android.content.Context import android.util.Log import com.bitchat.android.identity.SecureIdentityStateManager -import com.google.gson.Gson +import kotlinx.serialization.Serializable +import com.bitchat.android.util.JsonUtil /** * Persistent store for message IDs we've already acknowledged (DELIVERED) or READ. @@ -23,7 +24,7 @@ class SeenMessageStore private constructor(private val context: Context) { } } - private val gson = Gson() + private val secure = SecureIdentityStateManager(context) private val delivered = LinkedHashSet(MAX_IDS) @@ -61,7 +62,7 @@ class SeenMessageStore private constructor(private val context: Context) { @Synchronized private fun load() { try { val json = secure.getSecureValue(STORAGE_KEY) ?: return - val data = gson.fromJson(json, StorePayload::class.java) ?: return + val data = JsonUtil.fromJsonOrNull(json) ?: return delivered.clear(); read.clear() data.delivered.takeLast(MAX_IDS).forEach { delivered.add(it) } data.read.takeLast(MAX_IDS).forEach { read.add(it) } @@ -74,13 +75,14 @@ class SeenMessageStore private constructor(private val context: Context) { @Synchronized private fun persist() { try { val payload = StorePayload(delivered.toList(), read.toList()) - val json = gson.toJson(payload) + val json = JsonUtil.toJson(payload) secure.storeSecureValue(STORAGE_KEY, json) } catch (e: Exception) { Log.e(TAG, "Failed to persist SeenMessageStore: ${e.message}") } } + @Serializable private data class StorePayload( val delivered: List = emptyList(), val read: List = emptyList() From 866b8dae34ba74122c814addd04cd2b01ca373c4 Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 30 Sep 2025 18:11:20 +0400 Subject: [PATCH 08/14] Refactor: Migrate Nostr JSON handling from Gson to kotlinx.serialization This commit replaces the Gson library with kotlinx.serialization for all JSON processing related to Nostr messages (requests, responses, and protocol operations). Key changes include: - Removing custom `JsonSerializer` implementations. - Updating `NostrRequest.toJson` to use `buildJsonArray` from kotlinx.serialization. - Modifying `NostrResponse.fromJsonArray` and other parsing logic to use kotlinx.serialization's `JsonElement` API instead of Gson's. - Removing the Gson instance from `NostrRelayManager` and `NostrProtocol`. --- .../bitchat/android/nostr/NostrProtocol.kt | 57 +++++---- .../android/nostr/NostrRelayManager.kt | 21 ++-- .../com/bitchat/android/nostr/NostrRequest.kt | 115 +++++++----------- 3 files changed, 85 insertions(+), 108 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt b/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt index 26f3c241b..d1bd3c16f 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt @@ -1,8 +1,8 @@ package com.bitchat.android.nostr import android.util.Log -import com.google.gson.Gson -import com.google.gson.JsonParser +import kotlinx.serialization.json.* +import com.bitchat.android.util.JsonUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -13,7 +13,7 @@ import kotlinx.coroutines.withContext object NostrProtocol { private const val TAG = "NostrProtocol" - private val gson = Gson() + /** * Create NIP-17 private message gift-wrap (receiver copy only per iOS) @@ -161,7 +161,7 @@ object NostrProtocol { senderPrivateKey: String, senderPublicKey: String ): NostrEvent { - val rumorJSON = gson.toJson(rumor) + val rumorJSON = JsonUtil.toJson(rumor) val encrypted = NostrCrypto.encryptNIP44( plaintext = rumorJSON, @@ -185,7 +185,7 @@ object NostrProtocol { seal: NostrEvent, recipientPubkey: String ): NostrEvent { - val sealJSON = gson.toJson(seal) + val sealJSON = JsonUtil.toJson(seal) // Create new ephemeral key for gift wrap val (wrapPrivateKey, wrapPublicKey) = NostrCrypto.generateKeyPair() @@ -223,21 +223,21 @@ object NostrProtocol { recipientPrivateKeyHex = recipientPrivateKey ) - val jsonElement = JsonParser.parseString(decrypted) - if (!jsonElement.isJsonObject) { + val jsonElement = JsonUtil.json.parseToJsonElement(decrypted) + if (jsonElement !is JsonObject) { Log.w(TAG, "Decrypted gift wrap is not a JSON object") return null } - val jsonObject = jsonElement.asJsonObject + val jsonObject = jsonElement val seal = NostrEvent( - id = jsonObject.get("id")?.asString ?: "", - pubkey = jsonObject.get("pubkey")?.asString ?: "", - createdAt = jsonObject.get("created_at")?.asInt ?: 0, - kind = jsonObject.get("kind")?.asInt ?: 0, - tags = parseTagsFromJson(jsonObject.get("tags")?.asJsonArray) ?: emptyList(), - content = jsonObject.get("content")?.asString ?: "", - sig = jsonObject.get("sig")?.asString + id = jsonObject["id"]?.jsonPrimitive?.content ?: "", + pubkey = jsonObject["pubkey"]?.jsonPrimitive?.content ?: "", + createdAt = jsonObject["created_at"]?.jsonPrimitive?.int ?: 0, + kind = jsonObject["kind"]?.jsonPrimitive?.int ?: 0, + tags = parseTagsFromJson(jsonObject["tags"]?.jsonArray) ?: emptyList(), + content = jsonObject["content"]?.jsonPrimitive?.content ?: "", + sig = jsonObject["sig"]?.jsonPrimitive?.content ) Log.v(TAG, "Unwrapped seal with kind: ${seal.kind}") @@ -259,21 +259,21 @@ object NostrProtocol { recipientPrivateKeyHex = recipientPrivateKey ) - val jsonElement = JsonParser.parseString(decrypted) - if (!jsonElement.isJsonObject) { + val jsonElement = JsonUtil.json.parseToJsonElement(decrypted) + if (jsonElement !is JsonObject) { Log.w(TAG, "Decrypted seal is not a JSON object") return null } - val jsonObject = jsonElement.asJsonObject + val jsonObject = jsonElement NostrEvent( - id = jsonObject.get("id")?.asString ?: "", - pubkey = jsonObject.get("pubkey")?.asString ?: "", - createdAt = jsonObject.get("created_at")?.asInt ?: 0, - kind = jsonObject.get("kind")?.asInt ?: 0, - tags = parseTagsFromJson(jsonObject.get("tags")?.asJsonArray) ?: emptyList(), - content = jsonObject.get("content")?.asString ?: "", - sig = jsonObject.get("sig")?.asString + id = jsonObject["id"]?.jsonPrimitive?.content ?: "", + pubkey = jsonObject["pubkey"]?.jsonPrimitive?.content ?: "", + createdAt = jsonObject["created_at"]?.jsonPrimitive?.int ?: 0, + kind = jsonObject["kind"]?.jsonPrimitive?.int ?: 0, + tags = parseTagsFromJson(jsonObject["tags"]?.jsonArray) ?: emptyList(), + content = jsonObject["content"]?.jsonPrimitive?.content ?: "", + sig = jsonObject["sig"]?.jsonPrimitive?.content ) } catch (e: Exception) { Log.w(TAG, "Failed to open seal: ${e.message}") @@ -281,14 +281,13 @@ object NostrProtocol { } } - private fun parseTagsFromJson(tagsArray: com.google.gson.JsonArray?): List>? { + private fun parseTagsFromJson(tagsArray: JsonArray?): List>? { if (tagsArray == null) return emptyList() return try { tagsArray.map { tagElement -> - if (tagElement.isJsonArray) { - val tagArray = tagElement.asJsonArray - tagArray.map { it.asString } + if (tagElement is JsonArray) { + tagElement.map { it.jsonPrimitive.content } } else { emptyList() } diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt b/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt index ace6e57b5..964033933 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt @@ -3,9 +3,8 @@ package com.bitchat.android.nostr import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonParser +import kotlinx.serialization.json.* +import com.bitchat.android.util.JsonUtil import kotlinx.coroutines.* import okhttp3.* import java.util.concurrent.ConcurrentHashMap @@ -117,7 +116,7 @@ class NostrRelayManager private constructor() { private val httpClient: OkHttpClient get() = com.bitchat.android.net.OkHttpProvider.webSocketClient() - private val gson by lazy { NostrRequest.createGson() } + // Per-geohash relay selection private val geohashToRelays = ConcurrentHashMap>() // geohash -> relay URLs @@ -331,7 +330,7 @@ class NostrRelayManager private constructor() { */ private fun sendSubscriptionToRelays(subscriptionInfo: SubscriptionInfo) { val request = NostrRequest.Subscribe(subscriptionInfo.id, listOf(subscriptionInfo.filter)) - val message = gson.toJson(request, NostrRequest::class.java) + val message = NostrRequest.toJson(request) // DEBUG: Log the actual serialized message format Log.v(TAG, "🔍 DEBUG: Serialized subscription message: $message") @@ -383,7 +382,7 @@ class NostrRelayManager private constructor() { Log.d(TAG, "🚫 Unsubscribing from subscription: $id") val request = NostrRequest.Close(id) - val message = gson.toJson(request, NostrRequest::class.java) + val message = NostrRequest.toJson(request) scope.launch { connections.forEach { (relayUrl, webSocket) -> @@ -631,7 +630,7 @@ class NostrRelayManager private constructor() { private fun sendToRelay(event: NostrEvent, webSocket: WebSocket, relayUrl: String) { try { val request = NostrRequest.Event(event) - val message = gson.toJson(request, NostrRequest::class.java) + val message = NostrRequest.toJson(request) Log.v(TAG, "📤 Sending Nostr event (kind: ${event.kind}) to relay: $relayUrl") @@ -651,13 +650,13 @@ class NostrRelayManager private constructor() { private fun handleMessage(message: String, relayUrl: String) { try { - val jsonElement = JsonParser.parseString(message) - if (!jsonElement.isJsonArray) { + val jsonElement = JsonUtil.json.parseToJsonElement(message) + if (jsonElement !is JsonArray) { Log.w(TAG, "Received non-array message from $relayUrl") return } - val response = NostrResponse.fromJsonArray(jsonElement.asJsonArray) + val response = NostrResponse.fromJsonArray(jsonElement) when (response) { is NostrResponse.Event -> { @@ -828,7 +827,7 @@ class NostrRelayManager private constructor() { subscriptionsToRestore.forEach { subscriptionInfo -> try { val request = NostrRequest.Subscribe(subscriptionInfo.id, listOf(subscriptionInfo.filter)) - val message = gson.toJson(request, NostrRequest::class.java) + val message = NostrRequest.toJson(request) val success = webSocket.send(message) if (success) { diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrRequest.kt b/app/src/main/java/com/bitchat/android/nostr/NostrRequest.kt index 668e046e8..b669d1270 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrRequest.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrRequest.kt @@ -1,7 +1,7 @@ package com.bitchat.android.nostr -import com.google.gson.* -import java.lang.reflect.Type +import kotlinx.serialization.json.* +import com.bitchat.android.util.JsonUtil /** * Nostr protocol request messages @@ -27,54 +27,34 @@ sealed class NostrRequest { */ data class Close(val subscriptionId: String) : NostrRequest() - /** - * Custom JSON serializer for NostrRequest - */ - class RequestSerializer : JsonSerializer { - override fun serialize(src: NostrRequest, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - val array = JsonArray() - - when (src) { - is Event -> { - array.add("EVENT") - array.add(context.serialize(src.event)) - } - - is Subscribe -> { - array.add("REQ") - array.add(src.subscriptionId) - src.filters.forEach { filter -> - array.add(context.serialize(filter, NostrFilter::class.java)) - } - } - - is Close -> { - array.add("CLOSE") - array.add(src.subscriptionId) - } - } - - return array - } - } - companion object { - /** - * Create Gson instance with proper serializers - */ - fun createGson(): Gson { - return GsonBuilder() - .registerTypeAdapter(NostrRequest::class.java, RequestSerializer()) - .registerTypeAdapter(NostrFilter::class.java, NostrFilter.FilterSerializer()) - .disableHtmlEscaping() - .create() - } - /** * Serialize request to JSON string */ fun toJson(request: NostrRequest): String { - return createGson().toJson(request) + val jsonArray = buildJsonArray { + when (request) { + is Event -> { + add("EVENT") + add(Json.encodeToJsonElement(request.event)) + } + + is Subscribe -> { + add("REQ") + add(request.subscriptionId) + request.filters.forEach { filter -> + add(filter.toJsonElement()) + } + } + + is Close -> { + add("CLOSE") + add(request.subscriptionId) + } + } + } + + return JsonUtil.json.encodeToString(JsonArray.serializer(), jsonArray) } } } @@ -129,11 +109,11 @@ sealed class NostrResponse { */ fun fromJsonArray(jsonArray: JsonArray): NostrResponse { return try { - when (val type = jsonArray[0].asString) { + when (val type = jsonArray[0].jsonPrimitive.content) { "EVENT" -> { - if (jsonArray.size() >= 3) { - val subscriptionId = jsonArray[1].asString - val eventJson = jsonArray[2].asJsonObject + if (jsonArray.size >= 3) { + val subscriptionId = jsonArray[1].jsonPrimitive.content + val eventJson = jsonArray[2].jsonObject val event = parseEventFromJson(eventJson) Event(subscriptionId, event) } else { @@ -142,8 +122,8 @@ sealed class NostrResponse { } "EOSE" -> { - if (jsonArray.size() >= 2) { - val subscriptionId = jsonArray[1].asString + if (jsonArray.size >= 2) { + val subscriptionId = jsonArray[1].jsonPrimitive.content EndOfStoredEvents(subscriptionId) } else { Unknown(jsonArray.toString()) @@ -151,11 +131,11 @@ sealed class NostrResponse { } "OK" -> { - if (jsonArray.size() >= 3) { - val eventId = jsonArray[1].asString - val accepted = jsonArray[2].asBoolean - val message = if (jsonArray.size() >= 4) { - jsonArray[3].asString + if (jsonArray.size >= 3) { + val eventId = jsonArray[1].jsonPrimitive.content + val accepted = jsonArray[2].jsonPrimitive.boolean + val message = if (jsonArray.size >= 4) { + jsonArray[3].jsonPrimitive.content } else null Ok(eventId, accepted, message) } else { @@ -164,8 +144,8 @@ sealed class NostrResponse { } "NOTICE" -> { - if (jsonArray.size() >= 2) { - val message = jsonArray[1].asString + if (jsonArray.size >= 2) { + val message = jsonArray[1].jsonPrimitive.content Notice(message) } else { Unknown(jsonArray.toString()) @@ -181,13 +161,13 @@ sealed class NostrResponse { private fun parseEventFromJson(jsonObject: JsonObject): NostrEvent { return NostrEvent( - id = jsonObject.get("id")?.asString ?: "", - pubkey = jsonObject.get("pubkey")?.asString ?: "", - createdAt = jsonObject.get("created_at")?.asInt ?: 0, - kind = jsonObject.get("kind")?.asInt ?: 0, - tags = parseTagsFromJson(jsonObject.get("tags")?.asJsonArray), - content = jsonObject.get("content")?.asString ?: "", - sig = jsonObject.get("sig")?.asString + id = jsonObject["id"]?.jsonPrimitive?.content ?: "", + pubkey = jsonObject["pubkey"]?.jsonPrimitive?.content ?: "", + createdAt = jsonObject["created_at"]?.jsonPrimitive?.int ?: 0, + kind = jsonObject["kind"]?.jsonPrimitive?.int ?: 0, + tags = parseTagsFromJson(jsonObject["tags"]?.jsonArray), + content = jsonObject["content"]?.jsonPrimitive?.content ?: "", + sig = jsonObject["sig"]?.jsonPrimitive?.content ) } @@ -196,9 +176,8 @@ sealed class NostrResponse { return try { tagsArray.map { tagElement -> - if (tagElement.isJsonArray) { - val tagArray = tagElement.asJsonArray - tagArray.map { it.asString } + if (tagElement is JsonArray) { + tagElement.map { it.jsonPrimitive.content } } else { emptyList() } From d54fc17949d60b9f934ef13a13aa8d2cd1eba517 Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 30 Sep 2025 18:14:19 +0400 Subject: [PATCH 09/14] Refactor: Use kotlinx.serialization for NoisePayload Replaced Gson with kotlinx.serialization for `NoisePayload` and `NoiseChannelEncryption`. This change introduces a custom `ByteArraySerializer` to handle the serialization of the `data` field in `NoisePayload` and updates the channel key packet processing to use the new JSON utility. --- .../com/bitchat/android/model/NoiseEncrypted.kt | 3 ++- .../bitchat/android/noise/NoiseChannelEncryption.kt | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/model/NoiseEncrypted.kt b/app/src/main/java/com/bitchat/android/model/NoiseEncrypted.kt index 0a321ff42..55367ecfd 100644 --- a/app/src/main/java/com/bitchat/android/model/NoiseEncrypted.kt +++ b/app/src/main/java/com/bitchat/android/model/NoiseEncrypted.kt @@ -1,6 +1,7 @@ package com.bitchat.android.model import kotlinx.serialization.Serializable +import com.bitchat.android.util.ByteArraySerializer /** @@ -38,7 +39,7 @@ enum class NoisePayloadType(val value: UByte) { @Serializable data class NoisePayload( val type: NoisePayloadType, - val data: ByteArray + @Serializable(with = ByteArraySerializer::class) val data: ByteArray ) { /** diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt b/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt index dce6dd938..a4a6ba0e1 100644 --- a/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt +++ b/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt @@ -1,6 +1,8 @@ package com.bitchat.android.noise import android.util.Log +import com.bitchat.android.util.JsonUtil +import kotlinx.serialization.json.jsonObject import java.security.MessageDigest import java.util.concurrent.ConcurrentHashMap import javax.crypto.Cipher @@ -210,7 +212,7 @@ class NoiseChannelEncryption { ) // Simple JSON encoding for now (could be replaced with more efficient format) - val json = com.google.gson.Gson().toJson(packet) + val json = JsonUtil.toJson(packet) json.toByteArray(Charsets.UTF_8) } catch (e: Exception) { Log.e(TAG, "Failed to create channel key packet: ${e.message}") @@ -225,7 +227,14 @@ class NoiseChannelEncryption { fun processChannelKeyPacket(data: ByteArray): Pair? { return try { val json = String(data, Charsets.UTF_8) - val packet = com.google.gson.Gson().fromJson(json, Map::class.java) as Map + val packet = try { + JsonUtil.json.parseToJsonElement(json).jsonObject.mapValues { + when (val value = it.value) { + is kotlinx.serialization.json.JsonPrimitive -> if (value.isString) value.content else value.toString() + else -> value.toString() + } + } + } catch (e: Exception) { return null } val channel = packet["channel"] as? String val password = packet["password"] as? String From d49ea7fbcc2c93d83ce16537688fd7c7f42b7cc4 Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 30 Sep 2025 18:15:07 +0400 Subject: [PATCH 10/14] Refactor: Replace Gson with Kotlinx Serialization Replaced all instances of the Gson library with the `kotlinx.serialization` library for JSON handling. - Introduced a new `JsonUtil` helper class for serialization and deserialization. - Updated `GeohashBookmarksStore`, `LocationChannelManager`, `FavoritesPersistenceService`, and `DataManager` to use `JsonUtil`. - Added the `@Serializable` annotation to `FavoriteRelationshipData`. - Ensured safer JSON parsing by handling potential nulls and exceptions during deserialization. --- .../favorites/FavoritesPersistenceService.kt | 36 ++++++++++-------- .../android/geohash/GeohashBookmarksStore.kt | 38 ++++++++++--------- .../android/geohash/LocationChannelManager.kt | 18 ++++++--- .../com/bitchat/android/ui/DataManager.kt | 13 +++++-- 4 files changed, 63 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/favorites/FavoritesPersistenceService.kt b/app/src/main/java/com/bitchat/android/favorites/FavoritesPersistenceService.kt index 12cd3e9f4..0c05a7b72 100644 --- a/app/src/main/java/com/bitchat/android/favorites/FavoritesPersistenceService.kt +++ b/app/src/main/java/com/bitchat/android/favorites/FavoritesPersistenceService.kt @@ -3,8 +3,10 @@ package com.bitchat.android.favorites import android.content.Context import android.util.Log import com.bitchat.android.identity.SecureIdentityStateManager -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer +import com.bitchat.android.util.JsonUtil import java.util.* /** @@ -82,7 +84,7 @@ class FavoritesPersistenceService private constructor(private val context: Conte } private val stateManager = SecureIdentityStateManager(context) - private val gson = Gson() + private val favorites = mutableMapOf() // noiseHex -> relationship // NEW: Index by current mesh peerID (16-hex) for direct lookup when sending Nostr DMs from mesh context private val peerIdIndex = mutableMapOf() // peerID (lowercase 16-hex) -> npub @@ -258,14 +260,14 @@ class FavoritesPersistenceService private constructor(private val context: Conte try { val favoritesJson = stateManager.getSecureValue(FAVORITES_KEY) if (favoritesJson != null) { - val type = object : TypeToken>() {}.type - val data: Map = gson.fromJson(favoritesJson, type) - - favorites.clear() - data.forEach { (key, relationshipData) -> - favorites[key] = relationshipData.toFavoriteRelationship() + val data = JsonUtil.fromJsonOrNull(MapSerializer(String.serializer(), FavoriteRelationshipData.serializer()), favoritesJson) + if (data != null) { + favorites.clear() + data.forEach { (key, relationshipData) -> + favorites[key] = relationshipData.toFavoriteRelationship() + } + Log.d(TAG, "Loaded ${favorites.size} favorite relationships") } - Log.d(TAG, "Loaded ${favorites.size} favorite relationships") } } catch (e: Exception) { Log.e(TAG, "Failed to load favorites: ${e.message}") @@ -277,7 +279,7 @@ class FavoritesPersistenceService private constructor(private val context: Conte val data = favorites.mapValues { (_, relationship) -> FavoriteRelationshipData.fromFavoriteRelationship(relationship) } - val favoritesJson = gson.toJson(data) + val favoritesJson = JsonUtil.toJson(MapSerializer(String.serializer(), FavoriteRelationshipData.serializer()), data) stateManager.storeSecureValue(FAVORITES_KEY, favoritesJson) Log.d(TAG, "Saved ${favorites.size} favorite relationships") } catch (e: Exception) { @@ -289,10 +291,11 @@ class FavoritesPersistenceService private constructor(private val context: Conte try { val json = stateManager.getSecureValue(PEERID_INDEX_KEY) if (json != null) { - val type = object : TypeToken>() {}.type - val data: Map = gson.fromJson(json, type) - peerIdIndex.clear() - peerIdIndex.putAll(data) + val data = JsonUtil.fromJsonOrNull(MapSerializer(String.serializer(), String.serializer()), json) + if (data != null) { + peerIdIndex.clear() + peerIdIndex.putAll(data) + } Log.d(TAG, "Loaded ${peerIdIndex.size} peerID→npub mappings") } } catch (e: Exception) { @@ -302,7 +305,7 @@ class FavoritesPersistenceService private constructor(private val context: Conte private fun savePeerIdIndex() { try { - val json = gson.toJson(peerIdIndex) + val json = JsonUtil.toJson(MapSerializer(String.serializer(), String.serializer()), peerIdIndex) stateManager.storeSecureValue(PEERID_INDEX_KEY, json) Log.d(TAG, "Saved ${peerIdIndex.size} peerID→npub mappings") } catch (e: Exception) { @@ -336,6 +339,7 @@ class FavoritesPersistenceService private constructor(private val context: Conte } /** Serializable data for JSON storage */ +@Serializable private data class FavoriteRelationshipData( val peerNoisePublicKeyHex: String, val peerNostrPublicKey: String?, diff --git a/app/src/main/java/com/bitchat/android/geohash/GeohashBookmarksStore.kt b/app/src/main/java/com/bitchat/android/geohash/GeohashBookmarksStore.kt index b498dd833..c30ca71b9 100644 --- a/app/src/main/java/com/bitchat/android/geohash/GeohashBookmarksStore.kt +++ b/app/src/main/java/com/bitchat/android/geohash/GeohashBookmarksStore.kt @@ -7,8 +7,10 @@ import android.location.LocationManager import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer +import com.bitchat.android.util.JsonUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -41,7 +43,7 @@ class GeohashBookmarksStore private constructor(private val context: Context) { } } - private val gson = Gson() + private val prefs = context.getSharedPreferences("geohash_prefs", Context.MODE_PRIVATE) private val membership = mutableSetOf() @@ -96,19 +98,20 @@ class GeohashBookmarksStore private constructor(private val context: Context) { try { val arrJson = prefs.getString(STORE_KEY, null) if (!arrJson.isNullOrEmpty()) { - val listType = object : TypeToken>() {}.type - val arr = gson.fromJson>(arrJson, listType) - val seen = mutableSetOf() - val ordered = mutableListOf() - arr.forEach { raw -> + val arr = JsonUtil.fromJsonOrNull(ListSerializer(String.serializer()), arrJson) + if (arr != null) { + val seen = mutableSetOf() + val ordered = mutableListOf() + arr.forEach { raw -> val gh = normalize(raw) if (gh.isNotEmpty() && !seen.contains(gh)) { seen.add(gh) ordered.add(gh) } + } + membership.clear(); membership.addAll(seen) + _bookmarks.postValue(ordered) } - membership.clear(); membership.addAll(seen) - _bookmarks.postValue(ordered) } } catch (e: Exception) { Log.e(TAG, "Failed to load bookmarks: ${e.message}") @@ -116,9 +119,10 @@ class GeohashBookmarksStore private constructor(private val context: Context) { try { val namesJson = prefs.getString(NAMES_STORE_KEY, null) if (!namesJson.isNullOrEmpty()) { - val mapType = object : TypeToken>() {}.type - val dict = gson.fromJson>(namesJson, mapType) - _bookmarkNames.postValue(dict) + val dict = JsonUtil.fromJsonOrNull(MapSerializer(String.serializer(), String.serializer()), namesJson) + if (dict != null) { + _bookmarkNames.postValue(dict) + } } } catch (e: Exception) { Log.e(TAG, "Failed to load bookmark names: ${e.message}") @@ -127,14 +131,14 @@ class GeohashBookmarksStore private constructor(private val context: Context) { private fun persist() { try { - val json = gson.toJson(_bookmarks.value ?: emptyList()) + val json = JsonUtil.toJson(ListSerializer(String.serializer()), _bookmarks.value ?: emptyList()) prefs.edit().putString(STORE_KEY, json).apply() } catch (_: Exception) {} } private fun persistNames() { try { - val json = gson.toJson(_bookmarkNames.value ?: emptyMap()) + val json = JsonUtil.toJson(MapSerializer(String.serializer(), String.serializer()), _bookmarkNames.value ?: emptyMap()) prefs.edit().putString(NAMES_STORE_KEY, json).apply() } catch (_: Exception) {} } @@ -235,14 +239,14 @@ class GeohashBookmarksStore private constructor(private val context: Context) { private fun persist(list: List) { try { - val json = gson.toJson(list) + val json = JsonUtil.toJson(ListSerializer(String.serializer()), list) prefs.edit().putString(STORE_KEY, json).apply() } catch (_: Exception) {} } private fun persistNames(map: Map) { try { - val json = gson.toJson(map) + val json = JsonUtil.toJson(MapSerializer(String.serializer(), String.serializer()), map) prefs.edit().putString(NAMES_STORE_KEY, json).apply() } catch (_: Exception) {} } diff --git a/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt b/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt index 5f0ed4394..7f474d2a6 100644 --- a/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt +++ b/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt @@ -14,7 +14,8 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.* import java.util.* -import com.google.gson.Gson +import kotlinx.serialization.json.* +import com.bitchat.android.util.JsonUtil import com.google.gson.JsonSyntaxException /** @@ -49,7 +50,7 @@ class LocationChannelManager private constructor(private val context: Context) { private var lastLocation: Location? = null private var refreshTimer: Job? = null private var isGeocoding: Boolean = false - private val gson = Gson() + private var dataManager: com.bitchat.android.ui.DataManager? = null // Published state for UI bindings (matching iOS @Published properties) @@ -537,10 +538,10 @@ class LocationChannelManager private constructor(private val context: Context) { try { val channelData = when (channel) { is ChannelID.Mesh -> { - gson.toJson(mapOf("type" to "mesh")) + JsonUtil.toJson(mapOf("type" to "mesh")) } is ChannelID.Location -> { - gson.toJson(mapOf( + JsonUtil.toJson(mapOf( "type" to "location", "level" to channel.channel.level.name, "precision" to channel.channel.level.precision, @@ -563,7 +564,14 @@ class LocationChannelManager private constructor(private val context: Context) { try { val channelData = dataManager?.loadLastGeohashChannel() if (channelData != null) { - val channelMap = gson.fromJson(channelData, Map::class.java) as? Map + val channelMap = try { + JsonUtil.json.parseToJsonElement(channelData).jsonObject.mapValues { + when (val value = it.value) { + is JsonPrimitive -> if (value.isString) value.content else value.toString() + else -> value.toString() + } + } + } catch (e: Exception) { null } if (channelMap != null) { val channel = when (channelMap["type"] as? String) { "mesh" -> ChannelID.Mesh diff --git a/app/src/main/java/com/bitchat/android/ui/DataManager.kt b/app/src/main/java/com/bitchat/android/ui/DataManager.kt index b338c8645..26d07bc80 100644 --- a/app/src/main/java/com/bitchat/android/ui/DataManager.kt +++ b/app/src/main/java/com/bitchat/android/ui/DataManager.kt @@ -3,7 +3,8 @@ package com.bitchat.android.ui import android.content.Context import android.content.SharedPreferences import android.util.Log -import com.google.gson.Gson +import kotlinx.serialization.json.* +import com.bitchat.android.util.JsonUtil import kotlin.random.Random /** @@ -16,7 +17,7 @@ class DataManager(private val context: Context) { } private val prefs: SharedPreferences = context.getSharedPreferences("bitchat_prefs", Context.MODE_PRIVATE) - private val gson = Gson() + // Channel-related maps that need to persist state private val _channelCreators = mutableMapOf() @@ -85,7 +86,11 @@ class DataManager(private val context: Context) { // Load channel creators val creatorsJson = prefs.getString("channel_creators", "{}") try { - val creatorsMap = gson.fromJson(creatorsJson, Map::class.java) as? Map + val creatorsMap = try { + JsonUtil.json.parseToJsonElement(creatorsJson ?: "{}").jsonObject.mapValues { + it.value.jsonPrimitive.content + } + } catch (e: Exception) { null } creatorsMap?.let { _channelCreators.putAll(it) } } catch (e: Exception) { // Ignore parsing errors @@ -105,7 +110,7 @@ class DataManager(private val context: Context) { prefs.edit().apply { putStringSet("joined_channels", joinedChannels) putStringSet("password_protected_channels", passwordProtectedChannels) - putString("channel_creators", gson.toJson(_channelCreators)) + putString("channel_creators", JsonUtil.toJson(_channelCreators)) apply() } } From af46f69411de7cbc8a70a76da4f564aba9d14871 Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 30 Sep 2025 18:15:25 +0400 Subject: [PATCH 11/14] Refactor: Remove Gson dependency The Gson JSON library is no longer in use and has been removed from the project's dependencies. --- app/build.gradle.kts | 5 +---- gradle/libs.versions.toml | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 769cf8bd7..6e0fd764c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -82,10 +82,7 @@ dependencies { // Cryptography implementation(libs.bundles.cryptography) - - // JSON - implementation(libs.gson) - + // Coroutines implementation(libs.kotlinx.coroutines.android) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 62409acb0..84bce465b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,8 +25,7 @@ accompanist-permissions = "0.37.3" bouncycastle = "1.70" tink-android = "1.10.0" -# JSON -gson = "2.13.1" + # Coroutines kotlinx-coroutines = "1.10.2" @@ -88,8 +87,6 @@ accompanist-permissions = { module = "com.google.accompanist:accompanist-permiss bouncycastle-bcprov = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bouncycastle" } google-tink-android = { module = "com.google.crypto.tink:tink-android", version.ref = "tink-android" } -# JSON -gson = { module = "com.google.code.gson:gson", version.ref = "gson" } # Coroutines kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } From df5375f8c8668ccca3eb0824ae92f4dd544a46e5 Mon Sep 17 00:00:00 2001 From: yet300 Date: Sat, 25 Oct 2025 11:50:39 +0400 Subject: [PATCH 12/14] Refactor: Replace CloseButton with TextButton in bottom sheets Replaced the custom `CloseButton` composable with a standard `TextButton` in the `AboutSheet` and `LocationChannelsSheet`. This change removes the `CloseButton` implementation and updates the top bar of both sheets to use a `TextButton` with the text "Close" for dismissing the view. --- .../java/com/bitchat/android/ui/AboutSheet.kt | 25 ++----------------- .../android/ui/LocationChannelsSheet.kt | 4 +-- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt b/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt index 784e97feb..3dde31a0c 100644 --- a/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt @@ -552,9 +552,9 @@ fun AboutSheet( .height(64.dp) .background(MaterialTheme.colorScheme.background.copy(alpha = topBarAlpha)) ) { - CloseButton( + TextButton( onClick = onDismiss, - modifier = modifier + modifier = Modifier .align(Alignment.CenterEnd) .padding(horizontal = 16.dp) ) { @@ -570,27 +570,6 @@ fun AboutSheet( } } -@Composable -fun CloseButton( - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - IconButton( - onClick = onClick, - modifier = modifier - .size(32.dp), - colors = IconButtonDefaults.iconButtonColors( - contentColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f), - containerColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.1f) - ) - ) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = "Close", - modifier = Modifier.size(18.dp) - ) - } -} /** * Password prompt dialog for password-protected channels * Kept as dialog since it requires user input diff --git a/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt b/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt index fc828c265..251c8af90 100644 --- a/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt @@ -551,9 +551,9 @@ fun LocationChannelsSheet( .height(56.dp) .background(MaterialTheme.colorScheme.background.copy(alpha = topBarAlpha)) ) { - CloseButton( + TextButton( onClick = onDismiss, - modifier = modifier + modifier = Modifier .align(Alignment.CenterEnd) .padding(horizontal = 16.dp) ) { From 4e8c69e94e1ca94581393f15a98e82d25109c230 Mon Sep 17 00:00:00 2001 From: yet300 Date: Mon, 24 Nov 2025 11:15:54 +0400 Subject: [PATCH 13/14] Fix serialization of Any types in NostrEvent, LocationChannelManager, and NoiseChannelEncryption --- .../android/geohash/LocationChannelManager.kt | 20 ++++++++------ .../android/noise/NoiseChannelEncryption.kt | 14 +++++----- .../com/bitchat/android/nostr/NostrEvent.kt | 26 ++++++++++++------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt b/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt index 3dab57b62..3a7a5ae2d 100644 --- a/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt +++ b/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt @@ -545,16 +545,20 @@ class LocationChannelManager private constructor(private val context: Context) { try { val channelData = when (channel) { is ChannelID.Mesh -> { - JsonUtil.toJson(mapOf("type" to "mesh")) + val jsonObject = buildJsonObject { + put("type", "mesh") + } + JsonUtil.json.encodeToString(JsonObject.serializer(), jsonObject) } is ChannelID.Location -> { - JsonUtil.toJson(mapOf( - "type" to "location", - "level" to channel.channel.level.name, - "precision" to channel.channel.level.precision, - "geohash" to channel.channel.geohash, - "displayName" to channel.channel.level.displayName - )) + val jsonObject = buildJsonObject { + put("type", "location") + put("level", channel.channel.level.name) + put("precision", channel.channel.level.precision) + put("geohash", channel.channel.geohash) + put("displayName", channel.channel.level.displayName) + } + JsonUtil.json.encodeToString(JsonObject.serializer(), jsonObject) } } dataManager?.saveLastGeohashChannel(channelData) diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt b/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt index a4a6ba0e1..1bc5c11d1 100644 --- a/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt +++ b/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt @@ -2,7 +2,9 @@ package com.bitchat.android.noise import android.util.Log import com.bitchat.android.util.JsonUtil +import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.put import java.security.MessageDigest import java.util.concurrent.ConcurrentHashMap import javax.crypto.Cipher @@ -205,14 +207,14 @@ class NoiseChannelEncryption { fun createChannelKeyPacket(password: String, channel: String): ByteArray? { return try { // Create key packet with channel and password - val packet = mapOf( - "channel" to channel, - "password" to password, - "timestamp" to System.currentTimeMillis() - ) + val jsonObject = buildJsonObject { + put("channel", channel) + put("password", password) + put("timestamp", System.currentTimeMillis()) + } // Simple JSON encoding for now (could be replaced with more efficient format) - val json = JsonUtil.toJson(packet) + val json = JsonUtil.json.encodeToString(kotlinx.serialization.json.JsonObject.serializer(), jsonObject) json.toByteArray(Charsets.UTF_8) } catch (e: Exception) { Log.e(TAG, "Failed to create channel key packet: ${e.message}") diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt b/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt index c7535306b..c29665d07 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt @@ -2,6 +2,8 @@ package com.bitchat.android.nostr import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray import com.bitchat.android.util.JsonUtil import java.security.MessageDigest @@ -117,17 +119,23 @@ data class NostrEvent( */ private fun calculateEventId(): Pair { // Create serialized array for hashing according to NIP-01 - val serialized = listOf( - 0, - pubkey, - createdAt, - kind, - tags, - content - ) + val jsonArray = buildJsonArray { + add(0) + add(pubkey) + add(createdAt) + add(kind) + add(buildJsonArray { + tags.forEach { tag -> + add(buildJsonArray { + tag.forEach { add(it) } + }) + } + }) + add(content) + } // Convert to JSON without escaping slashes (compact format) - val jsonString = JsonUtil.toJson(serialized) + val jsonString = JsonUtil.json.encodeToString(kotlinx.serialization.json.JsonArray.serializer(), jsonArray) // SHA256 hash of the JSON string val digest = MessageDigest.getInstance("SHA-256") From efa5e9ff4fdbdcbdb0b338997ed10ed497d38e08 Mon Sep 17 00:00:00 2001 From: yet300 Date: Tue, 25 Nov 2025 15:35:29 +0400 Subject: [PATCH 14/14] fix: Improve geohash channel deserialization - Replace manual JSON parsing with `kotlinx.serialization` primitives for improved safety and reliability. - Use `contentOrNull` and `doubleOrNull` for safer extraction of values from the persisted `JsonElement`. - Remove an unnecessary `try-catch` block for `JsonSyntaxException`, as the new parsing method handles potential errors more gracefully. - Simplify the code by directly accessing the `jsonObject` instead of converting it to an intermediate map. --- .../android/geohash/LocationChannelManager.kt | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt b/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt index 3a7a5ae2d..04da5e339 100644 --- a/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt +++ b/app/src/main/java/com/bitchat/android/geohash/LocationChannelManager.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.* import java.util.* import kotlinx.serialization.json.* import com.bitchat.android.util.JsonUtil -import com.google.gson.JsonSyntaxException /** * Manages location permissions, one-shot location retrieval, and computing geohash channels. @@ -575,22 +574,17 @@ class LocationChannelManager private constructor(private val context: Context) { try { val channelData = dataManager?.loadLastGeohashChannel() if (channelData != null) { - val channelMap = try { - JsonUtil.json.parseToJsonElement(channelData).jsonObject.mapValues { - when (val value = it.value) { - is JsonPrimitive -> if (value.isString) value.content else value.toString() - else -> value.toString() - } - } + val channelObject = try { + JsonUtil.json.parseToJsonElement(channelData).jsonObject } catch (e: Exception) { null } - if (channelMap != null) { - val channel = when (channelMap["type"] as? String) { + if (channelObject != null) { + val channel = when (channelObject["type"]?.jsonPrimitive?.contentOrNull) { "mesh" -> ChannelID.Mesh "location" -> { - val levelName = channelMap["level"] as? String - val precision = (channelMap["precision"] as? Double)?.toInt() - val geohash = channelMap["geohash"] as? String - val displayName = channelMap["displayName"] as? String + val levelName = channelObject["level"]?.jsonPrimitive?.contentOrNull + val precision = channelObject["precision"]?.jsonPrimitive?.doubleOrNull?.toInt() + val geohash = channelObject["geohash"]?.jsonPrimitive?.contentOrNull + val displayName = channelObject["displayName"]?.jsonPrimitive?.contentOrNull if (levelName != null && precision != null && geohash != null && displayName != null) { try { @@ -607,7 +601,7 @@ class LocationChannelManager private constructor(private val context: Context) { } } else -> { - Log.w(TAG, "Unknown channel type in persisted data: ${channelMap["type"]}") + Log.w(TAG, "Unknown channel type in persisted data: ${channelObject["type"]}") null } } @@ -627,9 +621,6 @@ class LocationChannelManager private constructor(private val context: Context) { Log.d(TAG, "No persisted channel found, defaulting to Mesh") _selectedChannel.postValue(ChannelID.Mesh) } - } catch (e: JsonSyntaxException) { - Log.e(TAG, "Failed to parse persisted channel data: ${e.message}") - _selectedChannel.postValue(ChannelID.Mesh) } catch (e: Exception) { Log.e(TAG, "Failed to load persisted channel: ${e.message}") _selectedChannel.postValue(ChannelID.Mesh)