-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 16:00:47
Block 881218
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ cbab7074:f9f0bd61
2025-01-28 16:00:47
Block 881218
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 16:00:47
Block 881218
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 1ae011cb:1257a556
2025-01-28 16:00:47
Block 881218
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 04c915da:3dfbecc9
2025-01-28 15:59:10
AT THIS PRICE BETTER OFF WITH A BERETTA 1301 IMO
-
![](/static/nostr-icon-purple-64x64.png)
@ f8e6c643:03328ca9
2025-01-28 15:58:12
https://m.primal.net/OEbY.jpg
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:55:46
Block 881218
4 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 3f770d65:7a745b24
2025-01-28 15:52:39
Either nostr:nprofile1qqs2hr9cpe0y9fwytl8s5mpf0e6ckyf6sld22q5tzzezhzkl2w2a2qspp4mhxue69uhkummn9ekx7mqpzemhxue69uhkummnw3ezuenfd3khwetz9ecxcqgswaehxw309ahx7um5wghxcctwvsegthkd or nostr:nprofile1qqsdhcrqt2w8x9et446j8ge8kgmd2h4ykc6wsrnc4yqnmdu3lr74ktqpzemhxue69uhkzarvv9ejumn0wd68ytnvv9hxgqg4waehxw309a5xjum59ehx7um5wghxcctwvsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k75f8czm gave them too me in Prague last year.
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:50:46
Block 881218
3 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 15:50:46
Block 881218
3 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 3f770d65:7a745b24
2025-01-28 15:50:08
With nostr:nprofile1qqsyv47lazt9h6ycp2fsw270khje5egjgsrdkrupjg27u796g7f5k0spzemhxue69uhk2er9dchxummnw3ezumrpdejz7qgwwaehxw309ahx7uewd3hkctcprdmhxue69uhkummnw3ez6vfwde3x7tnpdenkzmnf9e3k75xqss4's help, I too can post sexy #footstr photos, nostr:nprofile1qqst7gmku9a6fmpxn5g0ejvk536xk3g322lfqv06frn5257au4fxhnspzdmhxue69uhhwmm59e6hg7r09ehkuef0eyu698. 🫂💜🤙🏻 https://nostr-relay.derekross.me/ba7c0c0e8709b3c6a4bbca0ec2638a4d534901458a3dcaeaaa8fdfbdfb25bc13.jpg
nostr:nevent1qqszys369fd3ag00ze2f6r3clhn5m8ghgl73zxdrurg4jzahrqnaq8spzdmhxue69uhhwmm59e6hg7r09ehkuef0qgst7gmku9a6fmpxn5g0ejvk536xk3g322lfqv06frn5257au4fxhnsrqsqqqqqpuzczm2
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:45:46
Block 881218
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 15:40:46
Block 881218
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:40:46
Block 881218
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 04c915da:3dfbecc9
2025-01-28 15:39:24
https://cdn.satellite.earth/2589748ac03942aa4fb25b9ddb8f4f29504996508a472ee4dad0538971876ce9.mp4
nostr:nevent1qvzqqqqqqypzqpxfzhdwlm3cx9l6wdzyft8w8y9gy607tqgtyfq7tekaxs7lhmxfqqsvpqv8wuwwmnfk7c690vqx2vx3sw9m6teshraqlnxp0x2chhayg4srvpcjd
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:35:46
Block 881217
3 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:30:47
Block 881217
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ cbab7074:f9f0bd61
2025-01-28 15:30:47
Block 881217
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 15:30:47
Block 881217
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f8e6c643:03328ca9
2025-01-28 15:26:34
https://m.primal.net/OEZg.jpg
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:25:46
Block 881215
6 - high priority
5 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:20:46
Block 881215
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 15:20:46
Block 881215
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:20:29
Block 881214
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 6389be64:ef439d32
2025-01-28 15:18:08
nostr:nprofile1qy0hwumn8ghj7cn0wd68ytnvd9nksarwd9hxwumsdaex2tnrdakj7qg5waehxw309ahx7um5wghrzumpwshx7un8qqsgqh3unz6z5gt45zqkv66wqaa6kvsnd6nv7juew654y45ezlv7x2g8508wd
What is the daily dosage of this cordyceps powder?
https://plebeian.market/products/lightningspore@denver.space/cordyceps-mycelium-powder-qfhicaunr3
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:15:46
Block 881214
7 - high priority
6 - medium priority
5 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:10:46
Block 881214
7 - high priority
6 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 15:10:46
Block 881214
7 - high priority
6 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 438ca533:e78af8aa
2025-01-28 15:07:49
O homem é construído, ganha valor com o tempo; a mulher é destruída, desvaloriza com o passar do tempo. A biologia ainda é mais cruel com elas.
Por isso homens preferem as novas e puras, para tê-las por completo.
Por isso mulheres preferem homens mais velhos, pulando a jornada para usufruir de tudo o que ele construiu.
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:05:46
Block 881214
6 - high priority
5 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 15:00:47
Block 881214
6 - high priority
5 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 15:00:47
Block 881214
6 - high priority
5 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 1ae011cb:1257a556
2025-01-28 15:00:47
Block 881214
6 - high priority
5 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ cbab7074:f9f0bd61
2025-01-28 15:00:47
Block 881214
6 - high priority
5 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ c7eda660:efd97c86
2025-01-28 14:59:35
The propaganda is unending. Save in Bitcoin.
“What is driving up prices?
The bird flu outbreak that started in 2022 is the main reason egg prices are up so much.“
https://apnews.com/article/egg-prices-bird-flu-poultry-inflation-9ea9934e20e3fe393abb1bb85aa31c30
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:55:46
Block 881214
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 8925425a:70dddd1c
2025-01-28 14:52:04
感觉今晚的警察如临大敌,我临街这一路到处都是警车,还有走来走去的民警
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 14:50:46
Block 881214
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:50:46
Block 881214
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:45:46
Block 881214
4 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 14:40:46
Block 881214
3 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:40:46
Block 881214
3 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 4a6468c5:e9061ef3
2025-01-28 14:40:45
Nothing could possibly go wrong with this plan.
-
![](/static/nostr-icon-purple-64x64.png)
@ e9691125:3320261f
2025-01-28 14:39:52
GOOD MORNING NOSTR 🫡
-
![](/static/nostr-icon-purple-64x64.png)
@ 2b33b2ac:da9f24c5
2025-01-28 14:39:04
gm #nostr https://i.nostr.build/1zLyv47zqk4Gx6GM.jpg
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:35:46
Block 881214
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 3f770d65:7a745b24
2025-01-28 14:32:55
She doesn't like the beard all that much.
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 14:30:47
Block 881213
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:30:46
Block 881213
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ cbab7074:f9f0bd61
2025-01-28 14:30:46
Block 881213
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 2bfec85a:af932171
2025-01-28 14:30:05
COME JOIN US ON nostr:nprofile1qqs8l7cf847rw8vkkhta7v8faa066r5le7dds8tgnau7ng2d74ey2cgpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhszrnhwden5te0dehhxtnvdakz7qgnwaehxw309ac82unsd3jhqct89ejhxtcymzaec. THE WATER'S FINE. #OLAS365
nostr:nevent1qqsx76h06hw6huzx7ftg8xy3tcyq5tuxvur8q0gux4jcvlslpu0vgdspz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsygptlmy95ca3ytdqgljhkx97adncmqqfnl3ajy3atms4ade6lyepwypsgqqqqq2qemlkud
-
![](/static/nostr-icon-purple-64x64.png)
@ 34d2f527:05cc8095
2025-01-28 14:28:58
ZEUS v0.9.5-beta1 is now available for testing.
Highlights
- Embedded LND: Seed: allow export of ypriv/zpriv (export to external wallets like Sparrow)
- CLNRest: add ability to use all funds for channel open
- Activity list: performance improvements
- Lurker mode improvements
- Improve display of memos and keysend messages
- Allow return key for password login
- Automatically include routing hints if node has only unannounced channels
- Wallets list: better highlighting of active mode
- Enhance on-chain receive workflow and address type selection
- Migration to new storage system
- Layout improvements
- Bug fixes
Full Release Notes https://github.com/ZeusLN/zeus/releases/tag/v0.9.5-beta1
ZEUS documentation https://docs.zeusln.app/
PGP Key https://zeusln.app/PGP.txt
Android
Universal APK https://zeusln.com/zeus-v0.9.5-beta1-universal.apk
arm64-v8a APK https://zeusln.com/zeus-v0.9.5-beta1-arm64-v8a.apk
iOS
Apple TestFlight https://testflight.apple.com/join/vVnODWoi https://image.nostr.build/b9c69343e0734264d1a969c4e00f4880024ed58a85229db12173efaf9b13b27e.png
-
![](/static/nostr-icon-purple-64x64.png)
@ 1be93261:3bf69614
2025-01-28 14:27:46
https://i.nostr.build/aLoFDtVxWjdFLMD3.jpg
-
![](/static/nostr-icon-purple-64x64.png)
@ b133bfc5:49d5789d
2025-01-28 14:26:15
Whatever happened to this chick & wtf was it really about? https://image.nostr.build/019bac5542dec8a3096f2c5a812ee377fa4e763689f9a8607d8a301808735699.jpg
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:25:46
Block 881211
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 38dbb9b0:7048cd5c
2025-01-28 14:24:39
😂
-
![](/static/nostr-icon-purple-64x64.png)
@ 5c87db16:e1c8b8ca
2025-01-28 14:22:18
People think it’s that they are eating all meat. It’s more about what they stopped eating
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:20:46
Block 881210
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 14:20:46
Block 881210
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e2ccf7cf:26c1c8eb
2025-01-28 14:19:59
Shitivore diet
-
![](/static/nostr-icon-purple-64x64.png)
@ 4d784205:6f5b96b3
2025-01-28 14:19:30
⚡️🇺🇸 JUST IN - OpenAI launches "ChatGPT Gov" for U.S. government agencies, allowing the agencies to feed "non-public, sensitive information" into the AI models in "secure environments" — CNBC
https://m.primal.net/OEVn.jpg
https://m.primal.net/OEVo.jpg
-
![](/static/nostr-icon-purple-64x64.png)
@ e2ccf7cf:26c1c8eb
2025-01-28 14:18:32
A huge problem with many bitcoiners is because we were right on Bitcoin with psychopath conviction, we have that same conviction with other things that are very likely to be wrong
-
![](/static/nostr-icon-purple-64x64.png)
@ 04c915da:3dfbecc9
2025-01-28 14:17:50
https://cdn.satellite.earth/aa8b4de55ee2c169226f04c1e2c254246a2020f8204377a041275319f60a2fe2.jpg
https://cdn.satellite.earth/2d0a2a1ce0c5bbb2f55e0d07acd86737a144e09d7d76d9d6aefbc3a4e529ef3f.jpg
-
![](/static/nostr-icon-purple-64x64.png)
@ 42796796:a6806e37
2025-01-28 14:16:12
Allegedly, I’m still skeptical, great if true. Time will tell.
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:15:46
Block 881206
2 - high priority
2 - medium priority
2 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 4a6468c5:e9061ef3
2025-01-28 14:14:17
What could possibly go wrong with this?
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:10:46
Block 881206
3 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 14:10:46
Block 881206
3 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 42796796:a6806e37
2025-01-28 14:08:01
Gm
-
![](/static/nostr-icon-purple-64x64.png)
@ 3f770d65:7a745b24
2025-01-28 14:07:16
#Olas is leveling up. https://nostr-relay.derekross.me/e73f967d39b7f68a4b0cd2ffcea30c6e7fa63b36923841a6e83b553cd366ef33.jpg
https://nostr-relay.derekross.me/db506bf8c65cf3a92710d7abd9b058d2af995b64a3c248a1e432f1d42c3e24d2.jpg
-
![](/static/nostr-icon-purple-64x64.png)
@ 42796796:a6806e37
2025-01-28 14:07:12
Has anyone tried to get around the guard rails by phrasing the question as theoretical, or tricking it to answer honestly? I have to do this all the time with ChatGPT.
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:05:46
Block 881205
4 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 04c915da:3dfbecc9
2025-01-28 14:05:28
GOOD MORNING NOSTR.
LIVE FREE. 🫡
https://cdn.satellite.earth/0e2cfd300ee7f5e5ca2eeff451b1508adfc3acaf9a0e60019c1c36431dcf8ba8.mp4
-
![](/static/nostr-icon-purple-64x64.png)
@ cbab7074:f9f0bd61
2025-01-28 14:00:47
Block 881205
4 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 1ae011cb:1257a556
2025-01-28 14:00:47
Block 881205
4 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 14:00:47
Block 881205
4 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 14:00:47
Block 881205
4 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 13:55:46
Block 881205
3 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 50d94fc2:df281d63
2025-01-28 13:54:16
BUT HAVE YOU TRIED ASKING IT ABOUT TIANANMEN SQUARE?
https://image.nostr.build/deb322ea5f81931203f1aa9ecc028e17bb4f600165edbcdcc306ce5776bee7b1.jpg
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 13:50:46
Block 881204
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 13:50:46
Block 881204
5 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ d043c3a7:ba24b89e
2025-01-28 13:50:16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>zchan</title>
<style>
body { font-family: Arial, sans-serif; background: #ffe }
h1 {
margin-top: 0;
}
#list, .replies {
list-style: none;
padding: 0;
}
.replies {
margin-top: -0.5em;
margin-bottom: 1em;
}
li, #download-link-container {
background: #F0E0D6;
padding: .3em;
}
#download-link-container {
font-weight: bold;
}
li {
position: relative;
}
textarea {
display: block;
width: calc(100% - 3em);
height: 7em;
}
#content {
position: relative;
}
.date {
color: #555;
color: #555;
position: absolute;
right: 0;
bottom: 0;
margin: .2em;
}
.date span {
margin-left: .6em;
}
img {
max-width: 100%;
max-height: 20vh;
}
.loading,
.loading * {
cursor: wait;
}
.container { max-width: 1000px; margin: auto; border: 2px solid #ddd; border-radius: 1em; padding: 1em; }
.category, .thread, .message { margin-bottom: 1em; word-wrap: break-word; }
.thread.reply, .more {
margin-left: 3em;
margin-bottom: 10px;
}
.thread {
padding-bottom: 1.5em;
}
.pagination { display: flex; justify-content: center; margin-top: 1em; }
.pagination a { margin: 0 5px; }
.new-thread-form, .new-message-form { display: none; margin-top: 1em; }
#download-link-container {
display: none;
}
.threadTitle {
display: block;
overflow: hidden;
text-overflow: ellipsis;
background: #EEAA88;
padding: .2em;
white-space: nowrap;
}
.imgLink {
display: inline-block;
margin-right: .2em;
}
h2 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#actions {
position: absolute;
right: 0;
}
</style>
</head>
<body>
<div class="container">
<h1>zchan</h1>
<div id="content">Loading...</div>
<p id="download-link-container">
Download <a id="download-link" download="chan.html" href="#">chan.html</a> to run this app locally
</p>
</div>
<script src="https://unpkg.com/nostr-tools@2.7.2/lib/nostr.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/image-blob-reduce@4.1.0/dist/image-blob-reduce.min.js"></script>
<script>
window.nostr = window.NostrTools
const reduce = new ImageBlobReduce()
const max_file_size = 12e4
const local = !location.href.startsWith("http")
const fileServers = [
"https://blossom.primal.net",
"https://nostr.download",
"https://blossom.f7z.io"
]
const fileRelays = [
"wss://nostr.swiss-enigma.ch",
"wss://nostr.cercatrova.me"
]
if(!local){
document.getElementById("download-link").href = location.pathname
document.getElementById("download-link-container").style.display = "block"
}
const maxWait = 1000
const catFreshDays = 10
const clientTags = []
let relays = []
let sockets = []
let queryIdCounter = 0
let activeThread
function init(){
relays.push("wss://nostr.swiss-enigma.ch")
relays.push("wss://nostr.cercatrova.me")
relays.push("wss://soloco.nl")
relays.push("wss://nostr.oxtr.dev")
relays.push("wss://relay.piazza.today")
relays.push("wss://junxingwang.org")
relays.push("wss://n.ok0.org")
relays.push("wss://nostr.data.haus")
relays.push("wss://bostr.bitcointxoko.com")
relays.push("wss://relaypag.es")
relays.push("wss://relay.nostr.watch")
relays.push("wss://relay.damus.io")
const contentDiv = document.getElementById('content')
contentDiv.innerText = "Connecting"
return new Promise(resolve => {
let openSockets = []
let resolved = false
for(const relay of relays){
const sock = new WebSocket(relay)
sock.onopen = function(){
if(resolved){
sock.close()
return
}
openSockets.push(this)
contentDiv.innerText = "Connected to " + openSockets.length + " relays"
if(openSockets.length >= 5){
sockets = openSockets
resolved = true
resolve()
}
}
sockets.push(sock)
}
})
}
function send(messageObject, limit, queryId, timeout) {
if(!timeout){
timeout = 1000
}
const st = new Date().getTime()
const query = JSON.stringify(messageObject)
const events = []
const activeSockets = [...sockets]
return new Promise(resolve => {
let resolved = false
for(const socket of sockets){
const sst = new Date().getTime()
socket.addEventListener("message", function(e){
if(resolved){
return
}
const data = JSON.parse(e.data)
if(data[0] == "EOSE" || data[0] == "OK"){
const sockIndex = activeSockets.indexOf(this)
if(sockIndex != -1){
activeSockets.splice(sockIndex, 1)
}
console.log("query @ " + this.url + " took ", new Date().getTime() - sst, "ms")
}
else if(data[0] == "EVENT" && data[1] == queryId && data.length > 2){
if(events.find(e => e.id == data[2].id)){
return
}
events.push(data[2])
}
if(activeSockets.length == 0){
console.log("send ok (2), query took ", new Date().getTime() - st, "ms", Array.from(events))
resolved = true
resolve(events)
return
}
})
socket.addEventListener("close", function(){
console.log("closed", this.url)
})
socket.send(query)
}
setTimeout(() => {
if(resolved){
return
}
console.log("send ok (3), query took ", new Date().getTime() - st, "ms")
console.log("timeouted relays", activeSockets.map(s => s.url))
resolved = true
resolve(Array.from(events))
}, timeout)
})
}
function text(str, multiline){
if(!str){
return str
}
str = str.replaceAll("<", "<").replaceAll("'", "‘").replaceAll("\"", """)
if(multiline){
str = str.replaceAll("\n", "<br/>")
}
return str
}
function showNewCategoryForm() {
const form = document.getElementById('new-category-form');
form.style.display = 'block';
}
function goToHashtag(){
const hashTags = document.querySelector("#hashtaginput").value.split(" ")
if(document.querySelector("#hashtaginput").value.length == 0){
return
}
const uri = hashTags.map(value => value.match(/^(#|)(.*)$/)[2]).join(",")
location.hash = "#tag/" + uri
}
function renderCategories() {
const usedNames = []
const contentDiv = document.getElementById('content')
const feedLink = localStorage.getItem("feed") && '<a href="#feed">view feed</a>' || ''
contentDiv.innerHTML = `
<h2>Topics</h2>
<form action="#" onsubmit="goToHashtag(); return false" autocomplete="on">
<p><input id="hashtaginput" type="text" placeholder="zchan aiart loli" required/></p>
<p><button>View threads</button></p>
</form>
${feedLink}
`
}
function saveFeed(){
localStorage.setItem("feed", location.hash.match("#tag/(.*)")[1])
location.hash = "#feed"
}
function createMessageElement(message, threadMessages, reply, reactions){
let threadTitleSafe = text(message.content)
const threadElement = document.createElement('li');
const date = text(new Date(message.created_at * 1000).toLocaleString("en-uk"))
threadElement.className = 'thread'
let repliesHtml = ""
if(reply){
threadElement.classList.add("reply")
}else{
repliesHtml = `
${reactions.length > 0 && `<span style="font-weight: bold">${reactions.length}</span> reactions` || ''}
<span style="${threadMessages.length > 1 ? "font-weight: bold": ""}">${threadMessages.length-1}</span> replies
`
}
threadElement.innerHTML = `
<a class="threadTitle" href="#thread/${text(threadMessages[0].id)}">${threadTitleSafe}</a> <span class="date">${date}${repliesHtml}</span>
<div class="messageContent">${links(text(message.content, false))}</div>
`
return threadElement
}
function escapeHref(uri){
return uri.replaceAll("'", "")
}
function makeUntilHash(categories, until){
if(location.hash.startsWith("#feed")){
return `#feed//${until}`
}
const tagsUri = categories.map(c => c.match(/^[^, ]+$/i)[0]).join(",")
return `#tag/${escapeHref(tagsUri)}/${until}`
}
function renderThreads(categories, threads, threadsMessages, threadsReactions) {
const categoriesHtml = makeCategoriesHtml(categories)
const categoriesValue = categories.map(c => c.match(/^[^, ]+$/i)[0]).join(" ")
const contentDiv = document.getElementById('content')
const feed = location.hash.startsWith("#feed") && localStorage.getItem("feed")
let feedLink = !feed && '<a href="#feed">view feed</a> ' || `<a href='#tag/${escapeHref(feed)}'>feed link</a>`
const feedButtonHtml = ! location.hash.startsWith("#feed") && '<button onclick="saveFeed()">Save feed</button>' || ''
let firstPageLinkHtml = ""
if(getUntil()){
firstPageLinkHtml = `<a href="${makeUntilHash(categories, "")}">first page</a>`
}
const lastTimestamp = Math.min(threads[threads.length-1].created_at, threadsMessages.length > 0 && threadsMessages[threadsMessages.length-1].created_at || Infinity) - 1
contentDiv.innerHTML = `
<h2><a href="#">Home</a> > ${categoriesHtml}</h2>
<div id="actions">${feedLink}${feedButtonHtml}</div>
<button onclick="showNewThreadForm('${categories[0]}', '${categories[0]}')">New Thread</button>
<div id="new-thread-form" class="new-thread-form">
<p><input type="text" id="new-thread-categories" placeholder="categories" value="${categoriesValue}"/>
<p><textarea id="new-thread-message" placeholder="First Message"></textarea></p>
<p><input id="new-thread-file" type="file"/> ${makeFileServerSelectionHtml()}</p>
<p><button onclick='createNewThread()'>Create Thread</button></p>
</div>
<ul id="list"></ul>
${firstPageLinkHtml}
<a onclick="window.scrollTo(0,0)" href="${location.origin + location.pathname + makeUntilHash(categories, lastTimestamp)}">next page >></a>
`
const list = document.getElementById("list")
threads.forEach(thread => {
let messages = [thread, ...threadsMessages.filter(m => m.tags.find(t => t[0] == "e" && t[1] == thread.id))]
let reactions = threadsReactions.filter(m => m.tags.find(t => t[0] == "e" && t[1] == thread.id))
const messageElement = createMessageElement(thread, messages, false, reactions)
list.appendChild(messageElement)
if(messages.length > 1){
const replyList = document.createElement("ul")
replyList.classList.add("replies")
const countDiff = messages.length - 4
if(countDiff > 0){
const el = document.createElement("li")
const link = document.createElement("a")
el.classList.add("more")
link.href = `#thread/${thread.id}`
link.innerText = `${countDiff} more messages`
el.append(link)
replyList.appendChild(el)
}
for(let message of messages.slice(1).slice(-3)){
//let reactions = threadsReactions.filter(m => m.tags.find(t => t[0] == "e" && t[1] == message.id))
const messageElement = createMessageElement(message, messages, true)
replyList.appendChild(messageElement)
}
list.append(replyList)
}
})
}
function showNewThreadForm(categoryId, categoryTitle) {
const form = document.getElementById('new-thread-form');
form.style.display = 'block';
}
async function sha256(blob) {
const uint8Array = new Uint8Array(await blob.arrayBuffer())
const hashBuffer = await crypto.subtle.digest('SHA-256', uint8Array)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map((h) => h.toString(16).padStart(2, '0')).join('')
}
async function load_image(image_blob){
return new Promise((resolve) => {
let reader = new FileReader()
let data_url = reader.readAsDataURL(image_blob)
reader.onload = function(e){
const img = new Image()
img.src = e.target.result
img.onload = async () => resolve(img)
}
})
}
async function resize(max_size, quality, blob){
let resized_img = await reduce.toBlob(blob, { max: max_size })
return await reduce.pica.toBlob(await reduce.toCanvas(resized_img), 'image/webp', quality)
}
async function resizeImg(blob){
let quality = 0.90
let max_size = 3000
let min_size = 900
let min_quality = 0.75
let resized_img = null
while(resized_img == null || resized_img.size > max_file_size){
resized_img = await resize(max_size, quality, blob)
if(quality > min_quality){
quality = Math.max(min_quality, quality - 0.5)
}
else if(max_size > min_size){
max_size = Math.max(min_size, max_size - 500)
}
else {
break
}
}
let img = null
if(resized_img.size <= max_file_size){
img = await load_image(resized_img)
console.log("resized image file size: " + resized_img.size + " bytes, w: " + img.width + ", h: " + img.height + ", quality: " + quality)
}else{
throw "error: could not resize image within size limitations"
return
}
return resized_img
}
function blobToDataURL(blob, callback) {
return new Promise(resolve => {
var a = new FileReader()
a.onload = function(e) {
resolve(e.target.result)
}
a.readAsDataURL(blob)
})
}
async function uploadFile(fileBlob, privateKey){
const imgBlob = await resizeImg(fileBlob)
const fileDataUri = await blobToDataURL(imgBlob)
let fileEvent = {
"kind": 1063,
"content": "image",
"created_at": Math.floor(Date.now() / 1000),
"tags": [
["url", fileDataUri],
["m", imgBlob.type],
]
}
fileEvent = window.nostr.finalizeEvent(fileEvent, privateKey)
const fileId = fileEvent.id
await send(["EVENT", fileEvent], 1, "sendq", 5000)
const nevent_obj = Object.create(fileEvent)
nevent_obj.relays = fileRelays
const neventStr = window.nostr.nip19.neventEncode(nevent_obj)
return neventStr
}
async function uploadBlob(fileBlob, fileHash, privateKey, serverUrl){
let auth = {
"kind": 24242,
"content": "",
"created_at": Math.floor(new Date().getTime() / 1000), //Math.floor(new Date().getTime() / 1000 - Math.random() * 84000),
"tags": [
["t", "upload"],
["x", fileHash],
["expiration", (Math.floor(new Date().getTime() / 1000) + 10000).toString()]
]
}
auth = window.nostr.finalizeEvent(auth, privateKey)
const formData = new FormData()
formData.append("files", fileBlob)
const body = new Blob([fileBlob], { type: 'application/octet-stream' })
return await (await fetch(`${serverUrl}/upload`, {
method: 'PUT',
headers: {
'Content-Type': 'application/octet-stream',
'Authorization': 'Nostr ' + btoa(JSON.stringify(auth)),
},
body
})).json()
}
async function uploadAnyFile(fileBlob, fileHash, fileServers, privateKey){
const fileServerType = document.getElementById('form-file-server').value
if(fileBlob && fileBlob.type.startsWith("image/") && fileServerType == "event"){
const neventStr = await uploadFile(fileBlob, privateKey)
return ["nostr:" + neventStr]
}
const fileUrls = []
for(let serverUrl of fileServers){
const match = fileBlob.name.match(/\.[a-z0-9]{1,10}/)
fileExtension = match && match[0] || ""
const uploadResult = await uploadBlob(fileBlob, fileHash, privateKey, serverUrl)
fileUrls.push(`${serverUrl}/${uploadResult.sha256}${fileExtension}`)
}
return fileUrls
}
async function createNewThread() {
const categories = document.querySelector("#new-thread-categories").value.split(" ")
const messageInput = document.getElementById('new-thread-message')
const fileBlob = document.getElementById('new-thread-file').files[0]
let firstMessage = messageInput.value
if (!firstMessage && !fileBlob){
alert("message or file required")
return
}
const privateKey = window.nostr.generateSecretKey()
const uploadResult = fileBlob && await handleFileUpload(fileBlob, privateKey)
if(uploadResult && uploadResult.urls && uploadResult.urls.length > 0){
firstMessage = uploadResult.urls[0] + "\n" + firstMessage
}
const fileTags = uploadResult && uploadResult.tags || []
let messageEvent = {
kind: 1,
tags: [
...categories.map(category => ["t", category]),
...fileTags
],
content: firstMessage,
created_at: Math.floor(Date.now() / 1000),
pubkey: window.nostr.getPublicKey(privateKey),
}
messageEvent.id = window.nostr.getEventHash(messageEvent)
messageEvent = window.nostr.finalizeEvent(messageEvent, privateKey)
await send(["EVENT", messageEvent])
loadThreads(categories)
}
function links(str){
const img = '<a class="imgLink" href="$1" target="_blank"><img src="$1"/></a>'
str = str.replace(/(http(s|):\/\/\S+\.(jpg|jpeg|webp|png|gif)|blob:http(s|):\/\/\S+)/gi, img)
const embedHtml = '<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/$1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>'
str = str.replace(/https:\/\/www.youtube.com\/watch\?v=([a-zA-Z0-9]{11})/g, embedHtml)
str = (" "+str).replace(/(<br\/>| )(http(s|):\/\/[a-z0-9-_.+?%/&]+)/gi, '$1<a href="$2">$2</a>')
str = str.substr(1)
str = str.replaceAll("\n", "<br/>")
return str
}
function makeCategoriesHtml(categories){
return categories.map(category => {
const prefix = "tag"
const match = category.match(/^(#|)(.*)$/)
return `<a href="#${prefix}/${text(match[2])}">${text(match[2])}</a>`
}).join(" / ")
}
function makeFileServerSelectionHtml(idServer, idCount){
return `preferred server
<select id="form-file-server">
<option value="-1">default</option>
<option value="blob">http server</option>
<option value="event">event (base64)</option>
</select>
no of servers
<select id="form-file-server-count">
<option value="2">2 (default)</option>
<option value="1">1</option>
</select>
`
}
function renderMessages(messages, categories) {
console.log("renderMessages")
const categoriesHtml = makeCategoriesHtml(categories)
const contentDiv = document.getElementById('content');
contentDiv.innerHTML = `
<h2><a href="#">Home</a> > ${categoriesHtml} > ${text(messages[0].content)}</h2>
<ul id="list"></ul>
`
const list = document.getElementById("list")
messages.forEach(message => {
console.log("message", message)
const messageElement = document.createElement('li');
messageElement.className = 'message'
messageElement.innerHTML = `<div>${links(text(message.content, false))}</div>`
list.appendChild(messageElement);
})
activeThread = messages[0]
contentDiv.innerHTML += `
<button onclick="showNewMessageForm()">New Message</button>
<div id="new-message-form" class="new-message-form">
<p><textarea id="new-message-content" placeholder="New Message"></textarea></p>
<p><input id="new-message-file" type="file"/> ${makeFileServerSelectionHtml()}</p>
<p><button onclick='createNewMessage()'>Post Message</button></p>
</div>
`
}
function displayHtmlError(htmlError){
const contentDiv = document.getElementById('content')
contentDiv.innerHTML = `
<h2><a href="#">Home</a> > error</h2>
<p>${htmlError}</p>
`
}
function showNewMessageForm() {
const form = document.getElementById('new-message-form');
form.style.display = 'block';
}
function pickFileServers(){
const fileServerCount = document.getElementById('form-file-server-count').value
const selectedFileServers = []
const myFileServers = [...fileServers]
for(let i = 0; i < fileServerCount; i++){
const fileServerIndex = Math.round(Math.random()*(myFileServers.length-1))
selectedFileServers.push(...myFileServers.splice(fileServerIndex, 1))
}
return selectedFileServers
}
async function handleFileUpload(fileBlob, privateKey){
const tags = []
let urls
if(fileBlob){
const fileHash = await sha256(fileBlob)
const fileServers = pickFileServers()
urls = await uploadAnyFile(fileBlob, fileHash, fileServers, privateKey)
tags.push(["x", fileHash])
for(let fileServer of fileServers){
tags.push(["server", fileServer])
}
}
return {
tags,
urls
}
}
async function createNewMessage() {
const thread = activeThread
const messageInput = document.getElementById('new-message-content');
let messageContent = messageInput.value;
const fileBlob = document.getElementById('new-message-file').files[0]
if (!messageContent && !fileBlob){
alert("message or file required")
return
}
const privateKey = window.nostr.generateSecretKey()
const uploadResult = await handleFileUpload(fileBlob, privateKey)
if(uploadResult && uploadResult.urls && uploadResult.urls.length > 0){
messageContent = uploadResult.urls[0] + "\n" + messageContent
}
if(fileBlob && uploadResult && (!uploadResult.urls || uploadResult.urls.length == 0)){
//console.log("error", uploadResult, fileBlob)
alert("error")
return
}
const fileTags = uploadResult && uploadResult.tags || []
let event = {
kind: 1,
tags: [
['p', thread.pubkey],
['e', thread.id, relays[0], "root"],
...fileTags,
...clientTags
],
content: messageContent,
created_at: Math.floor(Date.now() / 1000),
pubkey: window.nostr.getPublicKey(privateKey),
};
event.id = window.nostr.getEventHash(event);
event = window.nostr.finalizeEvent(event, privateKey);
await send(["EVENT", event])
loadMessages(thread)
}
function fetchLatestMessages(kinds, limit) {
const queryId = (++queryIdCounter).toString()
return send(["REQ", queryId, { kinds: kinds, limit: limit }], limit, queryId)
}
function threadIdsFromMessages(messages){
const idsUnique = new Set(messages.map(msg => {
const root = msg.tags.find((t, i) => {
return t[0] == "e"
})
return root && root[1]
}).filter(id => id !== undefined && id.match(/^[a-z0-9]{64}$/)))
return Array.from(idsUnique)
}
function findThreadUpdateTime(thread, threadsMessages){
let latestTimestamp = 0
for(let message of [thread, ...threadsMessages]){
if(message != thread && !message.tags.find(t => t[0] == "e" && t[1] == thread.id)){
continue
}
if(message.created_at >= latestTimestamp){
latestTimestamp = message.created_at
}
}
return latestTimestamp
}
async function loadThreads(categories, redirect, until) {
const filters = { limit: 50 }
filters.kinds = [1]
filters["#t"] = categories
if(until){
filters["until"] = until
}
const latestMessages = await fetchLatestMessages([1], 500);
let queryId = (++queryIdCounter).toString()
let threads = await send(["REQ", queryId, filters], filters.limit, queryId)
threads = threads.filter(t => !t.tags.find(tag => tag[0] == "e"))
filters["ids"] = threadIdsFromMessages(latestMessages)
queryId = (++queryIdCounter).toString()
threads.push(...await send(["REQ", queryId, filters], filters.limit, queryId))
const threadsIds = []
let threadsUnique = []
for(let thread of threads){
if(!threadsIds.includes(thread.id)){
threadsIds.push(thread.id)
threadsUnique.push(thread)
}
}
queryId = (++queryIdCounter).toString()
const threadsEvents = await send(["REQ", queryId, { kinds: [1, 7], '#e': threadsIds, limit: 1000 }], 1000, queryId)
const threadsMessages = threadsEvents.filter(e => e.kind == 1)
const threadsReactions = threadsEvents.filter(e => e.kind == 7)
threadsMessages.sort((a, b) => a.created_at - b.created_at)
threadsUnique.sort((a, b) => {
aTimestamp = findThreadUpdateTime(a, threadsMessages)
bTimestamp = findThreadUpdateTime(b, threadsMessages)
return bTimestamp - aTimestamp;
})
await resolveEmbeddedMedia([...threadsUnique, ...threadsMessages])
if(threadsUnique.length == 0){
const feedLinkHtml = localStorage.getItem("feed") && ' or <a href="#feed">feed</a>' || ''
displayHtmlError(`no threads. try <a href="#">going to home</a>${feedLinkHtml}`)
return
}
renderThreads(categories, threadsUnique, threadsMessages, threadsReactions)
if(redirect != false){
const prefix = "tag"
const untilStr = until && `/${until}` || ""
window.location.hash = `#${prefix}/${categories.join(",")}${untilStr}`
}
}
async function resolveEmbeddedMedia(messages){
const matches = messages.map(m => m.content).join().matchAll(/nostr:(nevent[a-z0-9]+)/g)
const embeddedEventIds = []
const eventMap = {}
for(let m of [...matches]){
const neventStr = m[1]
let id
try {
id = nostr.nip19.decode(neventStr).data.id
}catch(e){
console.log("invalid nevent", neventStr)
}
eventMap[id] = neventStr
embeddedEventIds.push(id)
}
let queryId = (++queryIdCounter).toString()
const embeddedEvents = await send(["REQ", queryId, { ids: embeddedEventIds }], 200, queryId, 3000)
for(let event of embeddedEvents){
const urlTag = event.tags.find(t => t[0] == "url")
if(!urlTag){
continue
}
const dataUri = urlTag[1]
const neventStr = eventMap[event.id]
for(let message of messages){
const blob = await (await fetch(dataUri)).blob()
let blobUrl = URL.createObjectURL(blob)
message.content = message.content.replaceAll("nostr:" + neventStr, blobUrl)
}
}
}
async function loadMessages(thread) {
let queryId = (++queryIdCounter).toString()
let messages = await send(["REQ", queryId, { kinds: [1], '#e': [thread.id], limit: 200 }], 200, queryId)
messages.sort((a, b) => a.created_at - b.created_at)
const categories = []
for(let tag of thread.tags){
if(tag[0] == "t"){
categories.push(tag[1])
}
}
messages = [thread, ...messages]
await resolveEmbeddedMedia(messages)
renderMessages(messages, categories);
window.location.hash = `#thread/${thread.id}`
}
async function getThread(threadId) {
let queryId = (++queryIdCounter).toString()
const threads = await send(["REQ", queryId, { ids: [threadId], limit: 1 }], 1, queryId)
return threads.length > 0 ? threads[0] : null;
}
function getUntil(){
const hash = decodeURIComponent(window.location.hash)
const hashParts = hash.split('/')
if(hashParts.length > 2 && hashParts[2].match(/^\d+$/)){
return parseInt(hashParts[2], 10)
}
}
async function initializeFromHash() {
const hash = decodeURIComponent(window.location.hash)
const hashParts = hash.split('/')
let until = getUntil()
if (hash.startsWith('#thread/')) {
const threadId = hashParts[1]
const thread = await getThread(threadId)
if(!thread){
displayHtmlError('could not load thread. try <a href="javascript:location.reload()">reloading</a>')
return
}
await loadMessages(thread)
}
else if (hash.startsWith('#tag/')) {
const hashTagsStr = hashParts[1]
const categories = hashTagsStr.split(",")
await loadThreads(categories, false, until)
}
else if (hash.startsWith('#feed')){
const hashTagsStr = localStorage.getItem("feed")
const categories = hashTagsStr.split(",")
await loadThreads(categories, false, until)
}
else {
await renderCategories();
}
}
onhashchange = async function(){
document.body.classList.add("loading")
await initializeFromHash()
document.body.classList.remove("loading")
}
document.addEventListener('DOMContentLoaded', async () => {
await init()
await initializeFromHash();
});
</script>
</body>
</html>
-
![](/static/nostr-icon-purple-64x64.png)
@ c7eda660:efd97c86
2025-01-28 13:47:30
The difference is that it took only $6M to train. That’s the breakthrough.
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 13:45:46
Block 881204
4 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 1947616f:0657caaf
2025-01-28 13:42:43
Public life is overrated. It’s escapism disguised as fun. One of the best ways to escape public life is to change all your social media profiles to a nickname.
With a nickname no one has power over you anymore. You live with an alter ego and you can finally be mentally free.
#BITCOIN #NOSTR #FREEDOM #LIFEFREE
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 13:40:46
Block 881204
4 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 13:40:46
Block 881204
4 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 13:35:46
Block 881204
3 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ c7eda660:efd97c86
2025-01-28 13:34:45
Powerful. Thanks for the reminder about Dust. I used to watch regularly.
-
![](/static/nostr-icon-purple-64x64.png)
@ e2ccf7cf:26c1c8eb
2025-01-28 13:31:29
It's very weird we all say it's 2025 even though we all know it's more like year 3 billion or something for earth and like 100,000 for humans
How did the church get everyone to go along with this psyop
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 13:30:47
Block 881203
4 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 13:30:47
Block 881203
4 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ cbab7074:f9f0bd61
2025-01-28 13:30:46
Block 881203
4 - high priority
4 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 13:25:46
Block 881203
4 - high priority
3 - medium priority
3 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 42796796:a6806e37
2025-01-28 13:22:49
I didn’t vax my kids immediately after birth and have been on a very slow schedule as they got older of only select vetted ones. The doctors came in to try and convince me. I simply explained that I wasn’t comfortable with the risks after asking what they were and asked if they could 100% Guarantee the safety and efficacy. Then punted just to get out of the hospital saying I wanted to discuss it with my pediatrician and would do so right away.
Luckily I had an understanding, kind pediatrician.
I had prepared a document that said the medical professionals recommending and administering the treatments attested to the safety and efficacy and assumed the liability for any adverse effects.
My plan was to say that as a father it is my duty and responsibility to care for my child and if they signed the letter to put my mind at ease I’d be happy to follow their recommendation. But if they were recommending a treatment but wouldn’t sign the document they didn’t really believe the treatment was safe and beneficial. Plan was to do the same with any CPS people.
“I’m happy to follow your recommendations and defer to your authority, if you promise me, on paper that the treatment is safe and beneficial because I’m trying to be a good parent”
That way I put the decision back on them and can claim the ethical high ground.
Never ended up having to do it and every place and situation is unique you may want to consult an attorney.
-
![](/static/nostr-icon-purple-64x64.png)
@ e7bf8dad:839ef3db
2025-01-28 13:20:46
Block 881202
6 - high priority
5 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 13:20:46
Block 881202
6 - high priority
5 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ f03df3d4:a4d4f676
2025-01-28 13:15:46
Block 881202
6 - high priority
5 - medium priority
4 - low priority
2 - no priority
1 - purging
#bitcoinfees #mempool
-
![](/static/nostr-icon-purple-64x64.png)
@ 3f770d65:7a745b24
2025-01-28 13:14:21
Good morning and pura vida, Nostr! It's time to create notes and send zaps! 🫂💜🤙🏻
We will build the future of communication. One note, one zap, one brick at a time. Nostr will be the foundational layer in which the future Internet is built.
-
![](/static/nostr-icon-purple-64x64.png)
@ b133bfc5:49d5789d
2025-01-28 13:13:59
#bookstr
https://archive.org/details/ancientmysticori00clym