diff options
| author | polwex <polwex@sortug.com> | 2025-11-12 07:11:07 +0700 |
|---|---|---|
| committer | polwex <polwex@sortug.com> | 2025-11-12 07:11:07 +0700 |
| commit | 284ce9ce7d9f81e54e91f917329d48926487fbf4 (patch) | |
| tree | 7a156986323fd799e1457c8b7663806e32b2af7d | |
| parent | 7305d67ff7f9e78b73326ef0e1f68a9613d34205 (diff) | |
fixes to engagement handling
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | app/app/nostrill.hoon | 16 | ||||
| -rw-r--r-- | app/lib/bip/b173.hoon | 145 | ||||
| -rw-r--r-- | app/lib/bitcoin-utils.hoon | 177 | ||||
| -rw-r--r-- | app/lib/json/nostrill.hoon | 14 | ||||
| -rw-r--r-- | app/lib/mutations/trill.hoon | 51 | ||||
| -rw-r--r-- | app/lib/nostr/client.hoon | 6 | ||||
| -rw-r--r-- | app/lib/nostrill.hoon | 3 | ||||
| -rw-r--r-- | app/lib/nostrill/comms.hoon | 62 | ||||
| -rw-r--r-- | app/lib/nostrill/follows.hoon | 3 | ||||
| -rw-r--r-- | app/lib/trill/feed.hoon | 31 | ||||
| -rw-r--r-- | app/mar/txt.hoon | 274 | ||||
| -rw-r--r-- | app/sur/bitcoin.hoon | 84 | ||||
| -rw-r--r-- | app/sur/nostrill.hoon | 11 | ||||
| -rw-r--r-- | app/sur/nostrill/comms.hoon | 11 | ||||
| -rw-r--r-- | arvo/iris.hoon | 3 | ||||
| -rw-r--r-- | gui/src/components/feed/PostList.tsx | 2 | ||||
| -rw-r--r-- | gui/src/components/post/Footer.tsx | 3 | ||||
| -rw-r--r-- | gui/src/components/post/Quote.tsx | 3 | ||||
| -rw-r--r-- | gui/src/logic/trill/helpers.ts | 24 | ||||
| -rw-r--r-- | gui/src/pages/Feed.tsx | 17 | ||||
| -rw-r--r-- | gui/src/pages/Thread.tsx | 52 | ||||
| -rw-r--r-- | gui/src/state/state.ts | 6 |
23 files changed, 960 insertions, 40 deletions
@@ -5,5 +5,5 @@ AGENTS.md CLAUDE.md NOTES.md run.sh -pier +piers urbit diff --git a/app/app/nostrill.hoon b/app/app/nostrill.hoon index 15a20a3..52cb8bb 100644 --- a/app/app/nostrill.hoon +++ b/app/app/nostrill.hoon @@ -1,6 +1,7 @@ /- sur=nostrill, nsur=nostr, tf=trill-feed, comms=nostrill-comms /+ lib=nostrill, nostr-keys, sr=sortug, scri, ws=websockets, + bip-b173, nreq=nostr-req, nostr-client, dbug, @@ -140,6 +141,7 @@ ?- -.pok %req (handle-req:coms +.pok) %res (handle-res:coms +.pok) + %eng (handle-eng:coms +.pok) == [cs this] :: DEPRECATED @@ -281,6 +283,10 @@ =/ key i.ks ~& pub=(scow:sr %ux -.key) ~& priv=(scow:sr %ux +.key) + =/ npub (encode-pubkey:bip-b173 %main [33 -.key]) + ~& npub=npub + :: =/ nsec + :: ~& nsec=nsec $(ks t.ks) %feed :: =/ lol debug-own-feed:mutat @@ -357,6 +363,16 @@ :_ this =/ subs ~(tap by sup.bowl) %+ turn subs |= [* p=@p pat=path] [%give %kick ~[pat] ~] + %leave + :_ this =/ subs ~(tap by wex.bowl) + %+ turn subs |= [[wire sip=@p term] q=*] + (urbit-leave:fols sip) + %comms + :_ this + :~ (urbit-watch:fols ~zod) + [%pass /foldbug %agent [~zod dap.bowl] %poke %bitch !>(~)] + == + == diff --git a/app/lib/bip/b173.hoon b/app/lib/bip/b173.hoon new file mode 100644 index 0000000..e554597 --- /dev/null +++ b/app/lib/bip/b173.hoon @@ -0,0 +1,145 @@ +:: BIP173: Bech32 Addresses +:: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki +:: +:: Heavily copies: +:: https://github.com/bitcoinjs/bech32/blob/master/index.js +:: +:: TODO not really working at generating npub/nsec keys from hex +/- sur=bitcoin +/+ bcu=bitcoin-utils +=, sur +=, bcu +|% +++ prefixes + ^- (map network tape) + (my [[%main "bc"] [%testnet "tb"] [%regtest "bcrt"] ~]) +++ charset "qpzry9x8gf2tvdw0s3jn54khce6mua7l" ++$ raw-decoded [hrp=tape data=(list @) checksum=(list @)] +:: below is a port of: https://github.com/bitcoinjs/bech32/blob/master/index.js +:: +++ polymod + |= values=(list @) + |^ ^- @ + =/ gen=(list @ux) + ~[0x3b6a.57b2 0x2650.8e6d 0x1ea1.19fa 0x3d42.33dd 0x2a14.62b3] + =/ chk=@ 1 + |- ?~ values chk + =/ top (rsh [0 25] chk) + =. chk + (mix i.values (lsh [0 5] (dis chk 0x1ff.ffff))) + $(values t.values, chk (update-chk chk top gen)) +:: + ++ update-chk + |= [chk=@ top=@ gen=(list @ux)] + =/ is (gulf 0 4) + |- ?~ is chk + ?: =(1 (dis 1 (rsh [0 i.is] top))) + $(is t.is, chk (mix chk (snag i.is gen))) + $(is t.is) + -- +:: +++ expand-hrp + |= hrp=tape + ^- (list @) + =/ front (turn hrp |=(p=@tD (rsh [0 5] p))) + =/ back (turn hrp |=(p=@tD (dis 31 p))) + (zing ~[front ~[0] back]) +:: +++ verify-checksum + |= [hrp=tape data-and-checksum=(list @)] + ^- ? + %- |=(a=@ =(1 a)) + %- polymod + (weld (expand-hrp hrp) data-and-checksum) +:: +++ checksum + |= [hrp=tape data=(list @)] + ^- (list @) + :: xor 1 with the polymod + :: + =/ pmod=@ + %+ mix 1 + %- polymod + (zing ~[(expand-hrp hrp) data (reap 6 0)]) + %+ turn (gulf 0 5) + |=(i=@ (dis 31 (rsh [0 (mul 5 (sub 5 i))] pmod))) +:: +++ charset-to-value + |= c=@tD + ^- (unit @) + (find ~[c] charset) +++ value-to-charset + |= value=@ + ^- (unit @tD) + ?: (gth value 31) ~ + `(snag value charset) +:: +++ is-valid + |= [bech=tape last-1-pos=@] ^- ? + ?& ?|(=((cass bech) bech) =((cuss bech) bech)) :: to upper or to lower is same as bech + (gte last-1-pos 1) + (lte (add last-1-pos 7) (lent bech)) + (lte (lent bech) 90) + (levy bech |=(c=@tD (gte c 33))) + (levy bech |=(c=@tD (lte c 126))) + == +:: data should be 5bit words +:: +++ encode-raw + |= [hrp=tape data=(list @)] + ^- cord + =/ combined=(list @) + (weld data (checksum hrp data)) + %- crip + (zing ~[hrp "1" (tape (murn combined value-to-charset))]) +++ decode-raw + |= body=cord + ^- (unit raw-decoded) + =/ bech (cass (trip body)) :: to lowercase + =/ pos (flop (fand "1" bech)) + ?~ pos ~ + =/ last-1=@ i.pos + ?. (is-valid bech last-1) :: check bech32 validity (not segwit validity or checksum) + ~ + =/ hrp (scag last-1 bech) + =/ encoded-data-and-checksum=(list @) + (slag +(last-1) bech) + =/ data-and-checksum=(list @) + %+ murn encoded-data-and-checksum + charset-to-value + ?. =((lent encoded-data-and-checksum) (lent data-and-checksum)) :: ensure all were in CHARSET + ~ + ?. (verify-checksum hrp data-and-checksum) + ~ + =/ checksum-pos (sub (lent data-and-checksum) 6) + `[hrp (scag checksum-pos data-and-checksum) (slag checksum-pos data-and-checksum)] +:: +from-address: BIP173 bech32 address encoding to hex +:: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki +:: expects to drop a leading 5-bit 0 (the witness version) +:: +++ from-address + |= body=cord + ^- hexb + ~| "Invalid bech32 address" + =/ d=(unit raw-decoded) (decode-raw body) + ?> ?=(^ d) + =/ bs=bits (from-atoms:bit 5 data.u.d) + =/ byt-len=@ (div (sub wid.bs 5) 8) + ?> =(5^0b0 (take:bit 5 bs)) + ?> ?| =(20 byt-len) + =(32 byt-len) + == + [byt-len `@ux`dat:(take:bit (mul 8 byt-len) (drop:bit 5 bs))] +:: pubkey is the 33 byte ECC compressed public key +:: +++ encode-pubkey + |= [=network pubkey=byts] + ^- (unit cord) + ?. =(33 wid.pubkey) + ~|('pubkey must be a 33 byte ECC compressed public key' !!) + =/ prefix (~(get by prefixes) network) + ?~ prefix ~ + :- ~ + %+ encode-raw u.prefix + [0v0 (to-atoms:bit 5 [160 `@ub`dat:(hash-160 pubkey)])] +-- diff --git a/app/lib/bitcoin-utils.hoon b/app/lib/bitcoin-utils.hoon new file mode 100644 index 0000000..a23354c --- /dev/null +++ b/app/lib/bitcoin-utils.hoon @@ -0,0 +1,177 @@ +:: lib/bitcoin-utils.hoon +:: Utilities for working with BTC data types and transactions +:: +/- *bitcoin +~% %bitcoin-utils-lib ..part ~ +|% +:: +:: TODO: move this bit/byt stuff to zuse +:: bit/byte utilities +:: +:: +:: +blop: munge bit and byt sequences (cat, flip, take, drop) +:: +++ blop + ~/ %blop + |_ =bloq + +$ biyts [wid=@ud dat=@] + ++ cat + |= bs=(list biyts) + ^- biyts + :- (roll (turn bs |=(b=biyts -.b)) add) + (can bloq (flop bs)) + :: +flip: flip endianness while preserving lead/trail zeroes + :: + ++ flip + |= b=biyts + ^- biyts + [wid.b (rev bloq b)] + :: +take: take n bloqs from front + :: pads front with extra zeroes if n is longer than input + :: + ++ take + |= [n=@ b=biyts] + ^- biyts + ?: (gth n wid.b) + [n dat.b] + [n (rsh [bloq (sub wid.b n)] dat.b)] + :: +drop: drop n bloqs from front + :: returns 0^0 if n >= width + :: + ++ drop + |= [n=@ b=biyts] + ^- biyts + ?: (gte n wid.b) + 0^0x0 + =+ n-take=(sub wid.b n) + [n-take (end [bloq n-take] dat.b)] + -- +++ byt ~(. blop 3) +:: +++ bit + ~/ %bit + =/ bl ~(. blop 0) + |% + ++ cat cat:bl:bit + ++ flip flip:bl:bit + ++ take take:bl:bit + ++ drop drop:bl:bit + ++ from-atoms + |= [bitwidth=@ digits=(list @)] + ^- bits + %- cat:bit + %+ turn digits + |= a=@ + ?> (lte (met 0 a) bitwidth) + [bitwidth `@ub`a] + :: +to-atoms: convert bits to atoms of bitwidth + :: + ++ to-atoms + |= [bitwidth=@ bs=bits] + ^- (list @) + =| res=(list @) + ?> =(0 (mod wid.bs bitwidth)) + |- + ?: =(0 wid.bs) res + %= $ + res (snoc res dat:(take:bit bitwidth bs)) + bs (drop:bit bitwidth bs) + == + -- +:: big endian sha256: input and output are both MSB first (big endian) +:: +++ sha256 + ~/ %sha256 + |= =byts + ^- hexb + %- flip:byt + [32 (shay (flip:byt byts))] +:: +++ dsha256 + ~/ %dsha256 + |= =byts + (sha256 (sha256 byts)) +:: +++ hash-160 + ~/ %hash-160 + |= val=byts + ^- hexb + =, ripemd:crypto + :- 20 + %- ripemd-160 + (sha256 val) + +:: +:: hxb: hex parsing utilities +:: +++ hxb + ~% %hxb ..blop ~ + |% + ++ from-cord + ~/ %from-cord + |= h=@t + ^- hexb + ?: =('' h) 1^0x0 + :: Add leading 00 + :: + =+ (lsh [3 2] h) + :: Group by 4-size block + :: + =+ (rsh [3 2] -) + :: Parse hex to atom + :: + =/ a (need (de:base16:mimes:html -)) + [-.a `@ux`+.a] + :: + ++ to-cord + ~/ %to-cord + |= =hexb + ^- cord + (en:base16:mimes:html hexb) + -- +:: +:: +csiz: CompactSize integers (a Bitcoin-specific datatype) +:: https://btcinformation.org/en/developer-reference#compactsize-unsigned-integers +:: - encode: big endian to little endian +:: - decode: little endian to big endian +:: +++ csiz + ~% %csiz ..blop ~ + |% + ++ en + ~/ %en + |= a=@ + ^- hexb + =/ l=@ (met 3 a) + ?: =(l 0) 1^a + ?: =(l 1) 1^a + ?: =(l 2) (cat:byt ~[1^0xfd (flip:byt 2^a)]) + ?: (lte l 4) (cat:byt ~[1^0xfe (flip:byt 4^a)]) + ?: (lte l 8) (cat:byt ~[1^0xff (flip:byt 8^a)]) + ~|("Cannot encode CompactSize longer than 8 bytes" !!) + :: + ++ de + ~/ %de + |= h=hexb + ^- [n=hexb rest=hexb] + =/ s=@ux dat:(take:byt 1 h) + ?: (lth s 0xfd) [1^s (drop:byt 1 h)] + ~| "Invalid compact-size at start of {<h>}" + =/ len=bloq + ?+ s !! + %0xfd 1 + %0xfe 2 + %0xff 3 + == + :_ (drop:byt (add 1 (bex len)) h) + %- flip:byt + (take:byt (bex len) (drop:byt 1 h)) + :: +dea: atom instead of hexb for parsed CompactSize + :: + ++ dea + |= h=hexb + ^- [a=@ rest=hexb] + => (de h) + [dat.n rest] + -- +-- diff --git a/app/lib/json/nostrill.hoon b/app/lib/json/nostrill.hoon index b07de61..266a6c8 100644 --- a/app/lib/json/nostrill.hoon +++ b/app/lib/json/nostrill.hoon @@ -15,6 +15,7 @@ feed+(feed-with-cursor:en:trill feed ~ ~) nostr+(en-nostr-feed nostr-feed) following+(enfollowing following) + following2+(feed-with-cursor:en:trill following2 ~ ~) ['followGraph' (engraph follow-graph)] ~ == @@ -190,9 +191,10 @@ add+postadd reply+reply quote+quote - rp+rp + rp+pid :: rt+de-rt - :: del+hex:de:common + reaction+reaction + del+pid == ++ postadd %- ot :~ @@ -211,11 +213,17 @@ host+(se:de:common %p) id+de-atom-id == -++ rp +++ pid %- ot :~ host+(se:de:common %p) id+de-atom-id == +++ reaction + %- ot :~ + host+(se:de:common %p) + id+de-atom-id + reaction+so + == ++ rt %- ot :~ id+hex:de:common diff --git a/app/lib/mutations/trill.hoon b/app/lib/mutations/trill.hoon index 40cce1e..cfedecf 100644 --- a/app/lib/mutations/trill.hoon +++ b/app/lib/mutations/trill.hoon @@ -3,6 +3,7 @@ /+ appjs=json-nostrill, lib=nostrill, + trill-feed, trill=trill-post, njs=json-nostr, postlib=trill-post, @@ -22,11 +23,26 @@ ++ add-to-feed |= p=post:post =. feed.state (put:orm:feed feed.state id.p p) state +++ headsup-poke + |= [poke=post-poke:ui:sur p=post:post] ^- (unit engagement:comms) + ?- -.poke + %add ~ + ::: TODO del-reply + %del ~ + %quote `[%quote id.poke p] + %reply `[%reply id.poke p] + %rp `[%rp id.poke id.p] + %reaction `[%reaction id.poke reaction.poke] + == ++ handle-post |= poke=post-poke:ui:sur ^- (quip card _state) =/ profile (~(get by profiles.state) [%urbit our.bowl]) =/ pubkey pub.i.keys.state + ?: ?=(%del -.poke) + =. feed.state =< + (del:orm:feed feed.state id.poke) + :: TODO + `state =/ p=post:post ?- -.poke %add @@ -45,23 +61,36 @@ =/ sp (build-sp:trill host.poke our.bowl '' ~ ~) =. contents.sp ~[quote] (build-post:trill now.bowl pubkey sp) + %reaction + =/ p (got:orm:feed feed.state id.poke) + =. reacts.engagement.p %+ ~(put by reacts.engagement.p) + our.bowl [reaction.poke *signature:post] + p == + =. state (add-to-feed p) =/ pw [p (some pubkey) ~ ~ profile] =/ jfact=fact:ui:sur [%post %add pw] =/ ui-card (update-ui:cards:lib jfact) - :: only update followers when we are updating our own feed - ?. .=(our.bowl host.p) [~[ui-card] state] - =. state (add-to-feed p) - =/ =fact:comms [%post %add p] - =/ fact-card (update-followers:cards:lib fact) + =/ crds ~(. cards:lib bowl) + =/ engagement-poke (headsup-poke poke p) + =/ base-cards + ?~ engagement-poke :~(ui-card) + =/ poke [%eng u.engagement-poke] + =/ eng-card (poke-host:crds host.p poke) + :~(ui-card eng-card) + :: if our own post we update followers, if someone elses post we send an engagement poke :_ state - :~ ui-card - fact-card - == - + ?: .=(our.bowl host.p) + :: + =/ =fact:comms [%post %add p] + =/ fact-card (update-followers:cards:lib fact) + :- fact-card base-cards + :: + base-cards ++ handle-post-fact |= pf=post-fact:comms ^- (quip card _state) + ~& handle-post-fact=pf =/ =user:sur [%urbit src.bowl] =/ fed (~(get by following.state) user) ?~ fed ~& "emmm not following ya" `state @@ -72,6 +101,10 @@ :: =/ =user:nsur [%urbit host.p.pdf] (put:orm:feed u.fed id.p.pf p.pf) =. following.state (~(put by following.state) user nf) + =. following2.state + ?: ?=(%del -.pf) + =< + (del:orm:feed following2.state id.pf) + (insert-to-global:trill-feed nf p.pf) :: TODO update the ui with the changes :_ state ~ -- diff --git a/app/lib/nostr/client.hoon b/app/lib/nostr/client.hoon index 2438a47..c258258 100644 --- a/app/lib/nostr/client.hoon +++ b/app/lib/nostr/client.hoon @@ -48,9 +48,9 @@ ++ get-posts ^- (quip card _state) =/ kinds (silt ~[1]) - =/ since (sub now.bowl ~m1) + =/ last-week (sub now.bowl ~d1) :: =/ since (to-unix-secs:jikan:sr last-week) - =/ =filter:nsur [~ ~ `kinds ~ `since ~ ~] + =/ =filter:nsur [~ ~ `kinds ~ `last-week ~ ~] (send-req ~[filter]) ++ get-user-feed @@ -92,7 +92,7 @@ ++ test-connection |= relay-url=@t =/ kinds (silt ~[1]) - =/ since (sub now.bowl ~m1) + =/ since (sub now.bowl ~m10) =/ =filter:nsur [~ ~ `kinds ~ `since ~ ~] =/ sub-id (gen-sub-id:nostr-keys eny.bowl) =/ req=client-msg:nsur [%req sub-id ~[filter]] diff --git a/app/lib/nostrill.hoon b/app/lib/nostrill.hoon index d46d090..1f5db40 100644 --- a/app/lib/nostrill.hoon +++ b/app/lib/nostrill.hoon @@ -55,5 +55,8 @@ :: ++ update-followers |= =fact:comms ^- card:agent:gall ++ update-followers |= =fact:comms ^- card:agent:gall [%give %fact ~[/follow] %noun !>(fact)] + :: + ++ poke-host |= [sip=@p =poke:comms] ^- card:agent:gall + [%pass /heads-up %agent [sip dap.bowl] %poke %noun !>(poke)] -- -- diff --git a/app/lib/nostrill/comms.hoon b/app/lib/nostrill/comms.hoon index 5f9b0a9..a386c0e 100644 --- a/app/lib/nostrill/comms.hoon +++ b/app/lib/nostrill/comms.hoon @@ -1,5 +1,5 @@ -/- sur=nostrill, nsur=nostr, comms=nostrill-comms, feed=trill-feed -/+ js=json-nostr, sr=sortug,constants, gatelib=trill-gate, feedlib=trill-feed, jsonlib=json-nostrill +/- sur=nostrill, nsur=nostr, comms=nostrill-comms, feed=trill-feed, post=trill-post +/+ js=json-nostr, sr=sortug,constants, gatelib=trill-gate, feedlib=trill-feed, jsonlib=json-nostrill, lib=nostrill |_ [=state:sur =bowl:gall] ++ cast-poke |= raw=* ^- poke:comms @@ -87,6 +87,64 @@ =/ c1 [%give %fact paths cage] :~(c1) +:: engagement pokes, heads up when replying etc. to a post on your feed +++ handle-eng + |= e=engagement:comms + ?- -.e + %reply + =/ poast (get:orm:feed feed.state parent.e) + ?~ poast `state + =. children.u.poast (~(put in children.u.poast) id.child.e) + =. feed.state (put:orm:feed feed.state parent.e u.poast) + =. feed.state (put:orm:feed feed.state id.child.e child.e) + =/ f=fact:comms [%post %add child.e] + =/ f2=fact:comms [%post %changes u.poast] + :_ state + :~ (update-followers:cards:lib f) + (update-followers:cards:lib f2) + == + %del-reply + =. feed.state =< + (del:orm:feed feed.state child.e) + =/ poast (get:orm:feed feed.state parent.e) + ?~ poast `state + =. children.u.poast (~(del in children.u.poast) child.e) + =. feed.state (put:orm:feed feed.state parent.e u.poast) + :_ state + :~ (update-followers:cards:lib [%post %changes u.poast]) + (update-followers:cards:lib [%post %del child.e]) + == + :: TODO ideally we want the full quote to display it within the post engagement. So do we change quoted.engagement.post? What if the quoter edits the quote down the line, etc. + %quote + =/ poast (get:orm:feed feed.state src.e) + ?~ poast `state + =/ spid [*signature:post src.bowl id.post.e] + =. quoted.engagement.u.poast (~(put in quoted.engagement.u.poast) spid) + =. feed.state (put:orm:feed feed.state src.e u.poast) + =/ f=fact:comms [%post %changes u.poast] + :_ state + :~ (update-followers:cards:lib f) + == + %rp + =/ poast (get:orm:feed feed.state src.e) + ?~ poast `state + =/ spid [*signature:post src.bowl rt.e] + =. shared.engagement.u.poast (~(put in shared.engagement.u.poast) spid) + =. feed.state (put:orm:feed feed.state src.e u.poast) + =/ f=fact:comms [%post %changes u.poast] + :_ state + :~ (update-followers:cards:lib f) + == + %reaction + =/ poast (get:orm:feed feed.state post.e) + ?~ poast `state + :: TODO signatures et al. + =. reacts.engagement.u.poast (~(put by reacts.engagement.u.poast) src.bowl [reaction.e *signature:post]) + =. feed.state (put:orm:feed feed.state post.e u.poast) + =/ f=fact:comms [%post %changes u.poast] + :_ state + :~ (update-followers:cards:lib f) + == + == -- diff --git a/app/lib/nostrill/follows.hoon b/app/lib/nostrill/follows.hoon index 34c1d19..942a876 100644 --- a/app/lib/nostrill/follows.hoon +++ b/app/lib/nostrill/follows.hoon @@ -48,7 +48,8 @@ ++ handle-follow-ok |= [=user:sur =fc:feed profile=(unit user-meta:nsur)] ^- (quip card:agent:gall _state) - =. following.state (~(put by following.state) user feed.fc) + =. following.state (~(put by following.state) user feed.fc) + =. following2.state (add-new-feed:feedlib following2.state feed.fc) =/ graph (~(get by follow-graph.state) [%urbit our.bowl]) =/ follows ?~ graph (silt ~[user]) (~(put in u.graph) user) =. follow-graph.state (~(put by follow-graph.state) [%urbit our.bowl] follows) diff --git a/app/lib/trill/feed.hoon b/app/lib/trill/feed.hoon index 721a596..31fcd64 100644 --- a/app/lib/trill/feed.hoon +++ b/app/lib/trill/feed.hoon @@ -67,4 +67,35 @@ (put:form:post acc id full-node) ?~ children [%empty ~] :- %full g +:: +++ add-new-feed +|= [global=feed:feed new=feed:feed] ^- feed:feed + =/ poasts (tap:orm:feed new) + |- ?~ poasts global + =/ poast +.i.poasts + =. global (insert-to-global global poast) + $(poasts t.poasts) + +++ consolidate-feeds +|= feeds=(list [* feed:feed]) ^- feed:feed + =| nf=feed:feed + |- ?~ feeds nf + =/ poasts (tap:orm:feed +.i.feeds) + =. nf |- ?~ poasts nf + =/ poast +.i.poasts + =. nf (insert-to-global nf poast) + $(poasts t.poasts) + $(feeds t.feeds) + +++ find-available-id +=| tries=@ud +|= [f=feed:feed id=@da] ^- @da + ?: (gte tries 20) ~|('find-available-id stack overflow' !!) + ?. (has:orm:feed f id) id + $(id +(id), tries +(tries)) + +++ insert-to-global +|= [f=feed:feed p=post:post] ^- feed:feed + =/ nid (find-available-id f id.p) + (put:orm:feed f nid p) -- diff --git a/app/mar/txt.hoon b/app/mar/txt.hoon new file mode 100644 index 0000000..982dce9 --- /dev/null +++ b/app/mar/txt.hoon @@ -0,0 +1,274 @@ +:: +:::: /hoon/txt/mar + :: +/? 310 +:: +=, clay +=, differ +=, format +=, mimes:html +|_ txt=wain +:: +++ grab :: convert from + |% + ++ mime |=((pair mite octs) (to-wain q.q)) + ++ noun wain :: clam from %noun + -- +++ grow + => v=. + |% + ++ mime => v [/text/plain (as-octs (of-wain txt))] + -- +++ grad + |% + ++ form %txt-diff + ++ diff + |= tyt=wain + ^- (urge cord) + (lusk txt tyt (loss txt tyt)) + :: + ++ pact + |= dif=(urge cord) + ~| [%pacting dif] + ^- wain + (lurk txt dif) + :: + ++ join + |= [ali=(urge cord) bob=(urge cord)] + ^- (unit (urge cord)) + |^ + =. ali (clean ali) + =. bob (clean bob) + |- ^- (unit (urge cord)) + ?~ ali `bob + ?~ bob `ali + ?- -.i.ali + %& + ?- -.i.bob + %& + ?: =(p.i.ali p.i.bob) + %+ bind $(ali t.ali, bob t.bob) + |=(cud=(urge cord) [i.ali cud]) + ?: (gth p.i.ali p.i.bob) + %+ bind $(p.i.ali (sub p.i.ali p.i.bob), bob t.bob) + |=(cud=(urge cord) [i.bob cud]) + %+ bind $(ali t.ali, p.i.bob (sub p.i.bob p.i.ali)) + |=(cud=(urge cord) [i.ali cud]) + :: + %| + ?: =(p.i.ali (lent p.i.bob)) + %+ bind $(ali t.ali, bob t.bob) + |=(cud=(urge cord) [i.bob cud]) + ?: (gth p.i.ali (lent p.i.bob)) + %+ bind $(p.i.ali (sub p.i.ali (lent p.i.bob)), bob t.bob) + |=(cud=(urge cord) [i.bob cud]) + ~ + == + :: + %| + ?- -.i.bob + %| + ?. =(i.ali i.bob) + ~ + %+ bind $(ali t.ali, bob t.bob) + |=(cud=(urge cord) [i.ali cud]) + :: + %& + ?: =(p.i.bob (lent p.i.ali)) + %+ bind $(ali t.ali, bob t.bob) + |=(cud=(urge cord) [i.ali cud]) + ?: (gth p.i.bob (lent p.i.ali)) + %+ bind $(ali t.ali, p.i.bob (sub p.i.bob (lent p.i.ali))) + |=(cud=(urge cord) [i.ali cud]) + ~ + == + == + ++ clean :: clean + |= wig=(urge cord) + ^- (urge cord) + ?~ wig ~ + ?~ t.wig wig + ?: ?=(%& -.i.wig) + ?: ?=(%& -.i.t.wig) + $(wig [[%& (add p.i.wig p.i.t.wig)] t.t.wig]) + [i.wig $(wig t.wig)] + ?: ?=(%| -.i.t.wig) + $(wig [[%| (welp p.i.wig p.i.t.wig) (welp q.i.wig q.i.t.wig)] t.t.wig]) + [i.wig $(wig t.wig)] + -- + :: + ++ mash + |= $: [als=ship ald=desk ali=(urge cord)] + [bos=ship bod=desk bob=(urge cord)] + == + ^- (urge cord) + |^ + =. ali (clean ali) + =. bob (clean bob) + |- ^- (urge cord) + ?~ ali bob + ?~ bob ali + ?- -.i.ali + %& + ?- -.i.bob + %& + ?: =(p.i.ali p.i.bob) + [i.ali $(ali t.ali, bob t.bob)] + ?: (gth p.i.ali p.i.bob) + [i.bob $(p.i.ali (sub p.i.ali p.i.bob), bob t.bob)] + [i.ali $(ali t.ali, p.i.bob (sub p.i.bob p.i.ali))] + :: + %| + ?: =(p.i.ali (lent p.i.bob)) + [i.bob $(ali t.ali, bob t.bob)] + ?: (gth p.i.ali (lent p.i.bob)) + [i.bob $(p.i.ali (sub p.i.ali (lent p.i.bob)), bob t.bob)] + =/ [fic=(unce cord) ali=(urge cord) bob=(urge cord)] + (resolve ali bob) + [fic $(ali ali, bob bob)] + :: ~ :: here, alice is good for a while, but not for the whole + == :: length of bob's changes + :: + %| + ?- -.i.bob + %| + =/ [fic=(unce cord) ali=(urge cord) bob=(urge cord)] + (resolve ali bob) + [fic $(ali ali, bob bob)] + :: + %& + ?: =(p.i.bob (lent p.i.ali)) + [i.ali $(ali t.ali, bob t.bob)] + ?: (gth p.i.bob (lent p.i.ali)) + [i.ali $(ali t.ali, p.i.bob (sub p.i.bob (lent p.i.ali)))] + =/ [fic=(unce cord) ali=(urge cord) bob=(urge cord)] + (resolve ali bob) + [fic $(ali ali, bob bob)] + == + == + :: + ++ annotate :: annotate conflict + |= $: ali=(list @t) + bob=(list @t) + bas=(list @t) + == + ^- (list @t) + %- zing + ^- (list (list @t)) + %- flop + ^- (list (list @t)) + :- :_ ~ + %^ cat 3 '<<<<<<<<<<<<' + %^ cat 3 ' ' + %^ cat 3 `@t`(scot %p bos) + %^ cat 3 '/' + bod + + :- bob + :- ~['------------'] + :- bas + :- ~['++++++++++++'] + :- ali + :- :_ ~ + %^ cat 3 '>>>>>>>>>>>>' + %^ cat 3 ' ' + %^ cat 3 `@t`(scot %p als) + %^ cat 3 '/' + ald + ~ + :: + ++ clean :: clean + |= wig=(urge cord) + ^- (urge cord) + ?~ wig ~ + ?~ t.wig wig + ?: ?=(%& -.i.wig) + ?: ?=(%& -.i.t.wig) + $(wig [[%& (add p.i.wig p.i.t.wig)] t.t.wig]) + [i.wig $(wig t.wig)] + ?: ?=(%| -.i.t.wig) + $(wig [[%| (welp p.i.wig p.i.t.wig) (welp q.i.wig q.i.t.wig)] t.t.wig]) + [i.wig $(wig t.wig)] + :: + ++ resolve + |= [ali=(urge cord) bob=(urge cord)] + ^- [fic=[%| p=(list cord) q=(list cord)] ali=(urge cord) bob=(urge cord)] + =- [[%| bac (annotate alc boc bac)] ali bob] + |- ^- $: $: bac=(list cord) + alc=(list cord) + boc=(list cord) + == + ali=(urge cord) + bob=(urge cord) + == + ?~ ali [[~ ~ ~] ali bob] + ?~ bob [[~ ~ ~] ali bob] + ?- -.i.ali + %& + ?- -.i.bob + %& [[~ ~ ~] ali bob] :: no conflict + %| + =+ lob=(lent p.i.bob) + ?: =(lob p.i.ali) + [[p.i.bob p.i.bob q.i.bob] t.ali t.bob] + ?: (lth lob p.i.ali) + [[p.i.bob p.i.bob q.i.bob] [[%& (sub p.i.ali lob)] t.ali] t.bob] + =+ wat=(scag (sub lob p.i.ali) p.i.bob) + =+ ^= res + %= $ + ali t.ali + bob [[%| (scag (sub lob p.i.ali) p.i.bob) ~] t.bob] + == + :* :* (welp bac.res wat) + (welp alc.res wat) + (welp boc.res q.i.bob) + == + ali.res + bob.res + == + == + :: + %| + ?- -.i.bob + %& + =+ loa=(lent p.i.ali) + ?: =(loa p.i.bob) + [[p.i.ali q.i.ali p.i.ali] t.ali t.bob] + ?: (lth loa p.i.bob) + [[p.i.ali q.i.ali p.i.ali] t.ali [[%& (sub p.i.bob loa)] t.bob]] + =+ wat=(slag (sub loa p.i.bob) p.i.ali) + =+ ^= res + %= $ + ali [[%| (scag (sub loa p.i.bob) p.i.ali) ~] t.ali] + bob t.bob + == + :* :* (welp bac.res wat) + (welp alc.res q.i.ali) + (welp boc.res wat) + == + ali.res + bob.res + == + :: + %| + =+ loa=(lent p.i.ali) + =+ lob=(lent p.i.bob) + ?: =(loa lob) + [[p.i.ali q.i.ali q.i.bob] t.ali t.bob] + =+ ^= res + ?: (gth loa lob) + $(ali [[%| (scag (sub loa lob) p.i.ali) ~] t.ali], bob t.bob) + ~& [%scagging loa=loa pibob=p.i.bob slag=(scag loa p.i.bob)] + $(ali t.ali, bob [[%| (scag (sub lob loa) p.i.bob) ~] t.bob]) + :* :* (welp bac.res ?:((gth loa lob) p.i.bob p.i.ali)) + (welp alc.res q.i.ali) + (welp boc.res q.i.bob) + == + ali.res + bob.res + == + == + == + -- + -- +-- diff --git a/app/sur/bitcoin.hoon b/app/sur/bitcoin.hoon new file mode 100644 index 0000000..4b83b5b --- /dev/null +++ b/app/sur/bitcoin.hoon @@ -0,0 +1,84 @@ +:: sur/btc.hoon +:: Utilities for working with BTC data types and transactions +:: +:: chyg: whether account is (non-)change. 0 or 1 +:: bytc: "btc-byts" with dat cast to @ux +|% ++$ network ?(%main %testnet %regtest) ++$ hexb [wid=@ dat=@ux] :: hex byts ++$ bits [wid=@ dat=@ub] ++$ xpub @ta ++$ address + $% [%base58 @uc] + [%bech32 cord] + == ++$ fprint hexb ++$ bipt $?(%44 %49 %84) ++$ chyg $?(%0 %1) ++$ idx @ud ++$ hdkey [=fprint pubkey=hexb =network =bipt =chyg =idx] ++$ sats @ud ++$ vbytes @ud ++$ txid hexb ++$ utxo [pos=@ =txid height=@ value=sats recvd=(unit @da)] +++ address-info + $: =address + confirmed-value=sats + unconfirmed-value=sats + utxos=(set utxo) + == +++ tx + |% + +$ data + $: is=(list input) + os=(list output) + locktime=@ud + nversion=@ud + segwit=(unit @ud) + == + +$ val + $: =txid + pos=@ud + =address + value=sats + == + :: included: whether tx is in the mempool or blockchain + :: + +$ info + $: included=? + =txid + confs=@ud + recvd=(unit @da) + inputs=(list val) + outputs=(list val) + == + +$ input + $: =txid + pos=@ud + sequence=hexb + script-sig=(unit hexb) + pubkey=(unit hexb) + value=sats + == + +$ output + $: script-pubkey=hexb + value=sats + == + -- +++ psbt + |% + +$ base64 cord + +$ in [=utxo rawtx=hexb =hdkey] + +$ out [=address hk=(unit hdkey)] + +$ target $?(%input %output) + +$ keyval [key=hexb val=hexb] + +$ map (list keyval) + -- +++ ops + |% + ++ op-dup 118 + ++ op-equalverify 136 + ++ op-hash160 169 + ++ op-checksig 172 + -- +-- diff --git a/app/sur/nostrill.hoon b/app/sur/nostrill.hoon index c5c5ae8..92a0876 100644 --- a/app/sur/nostrill.hoon +++ b/app/sur/nostrill.hoon @@ -15,6 +15,7 @@ :: profiles profiles=(map user user-meta:nostr) following=(map user =feed:trill) + following2=feed:trill follow-graph=(map user (set user)) :: TODO global feed somehow? @@ -39,7 +40,6 @@ $: pub=(unit @ux) $% [%fols fols-poke] [%begs begs-poke] [%post post-poke] - :: [%reac reac-poke] [%prof prof-poke] [%keys ~] :: cycle-keys [%rela relay-poke] @@ -50,11 +50,12 @@ $: pub=(unit @ux) == +$ post-poke $% [%add content=@t] - [%reply content=@t host=@p id=@ thread=@] - [%quote content=@t host=@p id=@] - [%rp host=@p id=@] :: NIP-18 + [%reply content=@t host=@p id=@da thread=@da] + [%quote content=@t host=@p id=@da] + [%rp host=@p id=@da] :: NIP-18 + [%reaction host=@p id=@da reaction=@t] :: [%rt id=@ux pubkey=@ux relay=@t] :: NIP-18 - :: [%del pubkey=@ux] + [%del host=@p id=@da] == +$ fols-poke $% [%add =user] diff --git a/app/sur/nostrill/comms.hoon b/app/sur/nostrill/comms.hoon index 42ea1ba..fa60989 100644 --- a/app/sur/nostrill/comms.hoon +++ b/app/sur/nostrill/comms.hoon @@ -3,12 +3,15 @@ +$ poke $% [%req req] [%res res] + [%eng engagement] [%dbug *] == -+$ emgagement - $% [%reply host=@p id=@da] - [%del-reply host=@p id=@da] - [%reaction host=@p id=@da reaction=@t] ++$ engagement + $% [%reply parent=@da child=post:post] + [%del-reply parent=@da child=@da] + [%quote src=@da =post:post] + [%rp src=@da rt=@da] + [%reaction post=@da reaction=@t] == +$ req $% [%feed ~] diff --git a/arvo/iris.hoon b/arvo/iris.hoon index c55c0ae..ead7a5b 100644 --- a/arvo/iris.hoon +++ b/arvo/iris.hoon @@ -557,7 +557,8 @@ |- ?~ sockets ~ =/ socket=websocket-connection:iris q.i.sockets ?. .=(app.socket caller) $(sockets t.sockets) - ?: .=(url.socket i.t.s.bem) `socket + ?: .=(url.socket i.t.s.bem) `sock + et $(sockets t.sockets) == =* ren car diff --git a/gui/src/components/feed/PostList.tsx b/gui/src/components/feed/PostList.tsx index b09a0e9..0d01bd2 100644 --- a/gui/src/components/feed/PostList.tsx +++ b/gui/src/components/feed/PostList.tsx @@ -20,6 +20,8 @@ function TrillFeed({ data, refetch }: { data: FC; refetch: Function }) { return ( <> {Object.keys(data.feed) + // omit replies + .filter((i) => !data.feed[i].parent) .sort() .reverse() .slice(0, 50) diff --git a/gui/src/components/post/Footer.tsx b/gui/src/components/post/Footer.tsx index 5b79da0..a87c1f8 100644 --- a/gui/src/components/post/Footer.tsx +++ b/gui/src/components/post/Footer.tsx @@ -41,6 +41,7 @@ function Footer({ poast, refetch }: PostProps) { // Scroll to top where composer is located window.scrollTo({ top: 0, behavior: "smooth" }); } + console.log({ poast }); const childrenCount = poast.children ? poast.children.length ? poast.children.length @@ -52,7 +53,7 @@ function Footer({ poast, refetch }: PostProps) { e.preventDefault(); const r = await api!.deletePost(our); if (r) toast.success("Repost deleted"); - refetch(); + // refetch(); if (location.includes(poast.id)) navigate("/"); } async function sendRP(e: React.MouseEvent) { diff --git a/gui/src/components/post/Quote.tsx b/gui/src/components/post/Quote.tsx index 28149f0..98720ea 100644 --- a/gui/src/components/post/Quote.tsx +++ b/gui/src/components/post/Quote.tsx @@ -49,12 +49,11 @@ function Quote({ return ( <div onMouseUp={gotoQuote} className="quote-in-post"> <header className="btw"> - ( <div className="quote-author flex"> <Sigil patp={data.author} size={20} /> {data.author} </div> - )<span>{date_diff(data.time, "short")}</span> + <span>{date_diff(data.time, "short")}</span> </header> <Body poast={data} nest={nest} refetch={refetch!} /> </div> diff --git a/gui/src/logic/trill/helpers.ts b/gui/src/logic/trill/helpers.ts index 6b5a138..8bd1b0c 100644 --- a/gui/src/logic/trill/helpers.ts +++ b/gui/src/logic/trill/helpers.ts @@ -8,3 +8,27 @@ export function toFlat(n: FullNode): Poast { : Object.keys(n.children).map((c) => n.children[c].id), }; } + +type res = { threadChildren: FullNode[]; replies: FullNode[] }; +const bunt: res = { threadChildren: [], replies: [] }; +export function extractThread(node: FullNode): res { + if (!node.children) return bunt; + const r = Object.keys(node.children) + .sort() + .reduce((acc, index) => { + const n = node.children[index]; + // if (typeof n.post === "string") return acc; + const nn = n as FullNode; + return n.author !== node.author + ? { ...acc, replies: [...acc.replies, nn] } + : { + ...acc, + threadChildren: [ + ...acc.threadChildren, + nn, + ...extractThread(nn).threadChildren, + ], + }; + }, bunt); + return r; +} diff --git a/gui/src/pages/Feed.tsx b/gui/src/pages/Feed.tsx index 66acc66..ac596dd 100644 --- a/gui/src/pages/Feed.tsx +++ b/gui/src/pages/Feed.tsx @@ -57,7 +57,7 @@ function FeedPage({ t }: { t: FeedType }) { {active === "global" ? ( <Global /> ) : active === "following" ? ( - <Global /> + <Following /> ) : active === "nostr" ? ( <Nostr /> ) : null} @@ -87,6 +87,21 @@ function Global() { // else return <Inner data={data} refetch={refetch} />; return <p>Error</p>; } +function Following() { + const following = useLocalState((s) => s.following2); + console.log({ following }); + + // console.log(data, "scry feed data"); + // if (isPending) return <img className="x-center" src={spinner} />; + // else if ("bucun" in data) return <p>Error</p>; + // else return <Inner data={data} refetch={refetch} />; + + return ( + <div> + <PostList data={following} refetch={() => {}} /> + </div> + ); +} function Nostr() { const { nostrFeed, api } = useLocalState((s) => ({ nostrFeed: s.nostrFeed, diff --git a/gui/src/pages/Thread.tsx b/gui/src/pages/Thread.tsx index 8296f07..dec8946 100644 --- a/gui/src/pages/Thread.tsx +++ b/gui/src/pages/Thread.tsx @@ -1,15 +1,15 @@ import { useParams } from "wouter"; import { useQuery } from "@tanstack/react-query"; import useLocalState from "@/state/state"; -import PostList from "@/components/feed/PostList"; -import Composer from "@/components/composer/Composer"; import Icon from "@/components/Icon"; import spinner from "@/assets/triangles.svg"; import { ErrorPage } from "@/Router"; import "@/styles/trill.css"; import "@/styles/feed.css"; import Post from "@/components/post/Post"; -import { toFlat } from "@/logic/trill/helpers"; +import { extractThread, toFlat } from "@/logic/trill/helpers"; +import type { FullNode } from "@/types/trill"; +import Composer from "@/components/composer/Composer"; export default function Thread() { const params = useParams<{ host: string; id: string }>(); @@ -19,7 +19,7 @@ export default function Thread() { async function fetchThread() { return await api!.scryThread(host, id); } - const { isPending, data, error, refetch } = useQuery({ + const { isPending, data, error } = useQuery({ queryKey: ["thread", params.host, params.id], queryFn: fetchThread, enabled: !!api && !!params.host && !!params.id, @@ -66,7 +66,8 @@ export default function Thread() { </main> ); } - + console.log({ data }); + // TODO make Composer a modal when in Thread mode return ( <main> <div className="thread-header"> @@ -90,13 +91,52 @@ export default function Thread() { <div id="feed-proper"> <Composer /> - <div className="thread-content"> + <div id="thread-head"> <Post poast={toFlat(data.ok)} /> </div> + <div id="thread-children"> + <ChildTree node={data.ok} /> + </div> </div> </main> ); } + +function ChildTree({ node }: { node: FullNode }) { + const { threadChildren, replies } = extractThread(node); + return ( + <> + <div id="tail"> + {threadChildren.map((n) => { + return <Post key={n.id} poast={toFlat(n)} />; + })} + </div> + <div id="replies"> + {replies.map((n) => ( + <ReplyThread key={n.id} node={n} /> + ))} + </div> + </> + ); +} + +function ReplyThread({ node }: { node: FullNode }) { + // const { threadChildren, replies } = extractThread(node); + const { replies } = extractThread(node); + return ( + <div className="trill-reply-thread"> + <div className="head"> + <Post poast={toFlat(node)} /> + </div> + <div className="tail"> + {replies.map((r) => ( + <Post key={r.id} poast={toFlat(r)} /> + ))} + </div> + </div> + ); +} + // function OwnData(props: Props) { // const { api } = useLocalState((s) => ({ // api: s.api, diff --git a/gui/src/state/state.ts b/gui/src/state/state.ts index f329145..9bd5e0e 100644 --- a/gui/src/state/state.ts +++ b/gui/src/state/state.ts @@ -26,6 +26,7 @@ export type LocalState = { profiles: Map<string, UserProfile>; // pubkey key addProfile: (key: string, u: UserProfile) => void; following: Map<string, FC>; + following2: FC; followers: string[]; // Notifications notifications: Notification[]; @@ -50,7 +51,8 @@ export const useStore = creator((set, get) => ({ await api.subscribeStore((data) => { console.log("store sub", data); if ("state" in data) { - const { feed, nostr, following, relays, profiles, pubkey } = data.state; + const { feed, nostr, following, following2, relays, profiles, pubkey } = + data.state; const flwing = new Map(Object.entries(following as Record<string, FC>)); flwing.set(api!.airlock.our!, feed); set({ @@ -58,6 +60,7 @@ export const useStore = creator((set, get) => ({ nostrFeed: nostr, profiles: new Map(Object.entries(profiles)), following: flwing, + following2, pubkey, }); } else if ("fact" in data) { @@ -103,6 +106,7 @@ export const useStore = creator((set, get) => ({ nostrFeed: [], following: new Map(), followers: [], + following2: { feed: {}, start: "", end: "" }, UISettings: {}, modal: null, setModal: (modal) => set({ modal }), |
