diff --git a/app/app/n-contacts.hoon b/app/app/n-contacts.hoon new file mode 100644 index 0000000..506b90a --- /dev/null +++ b/app/app/n-contacts.hoon @@ -0,0 +1,856 @@ +/+ default-agent, dbug +/+ *contacts-contacts, kol +:: +:: performance, keep warm +:: /+ j0=contacts-json-0, j1=contacts-json-1, mark-warmer +/+ j0=contacts-json-0, j1=contacts-json-1 +:: +|% +:: conventions +:: +:: .con: a contact +:: .rof: our profile +:: .rol: our full rolodex (legacy) +:: .far: foreign peer +:: .for: foreign profile +:: .sag: foreign subscription state +:: ++| %types ++$ card card:agent:gall ++$ state-4 + $: %4 + rof=profile + =book + =peers + last-updated=(list [=kip =time]) + retry=(map ship @da) :: retry sub at time + == +-- +%- agent:dbug +^- agent:gall +=| state-4 +=* state - +=< |_ =bowl:gall + +* this . + def ~(. (default-agent this %|) bowl) + cor ~(. raw bowl) + :: + ++ on-init + ^- (quip card _this) + =^ cards state abet:init:cor + [cards this] + :: + ++ on-save !>([state ~]) + :: + ++ on-load + |= old=vase + ^- (quip card _this) + =^ cards state abet:(load:cor old) + [cards this] + :: + ++ on-watch + |= =path + ^- (quip card _this) + =^ cards state abet:(peer:cor path) + [cards this] + :: + ++ on-poke + |= [=mark =vase] + ^- (quip card _this) + =^ cards state abet:(poke:cor mark vase) + [cards this] + :: + ++ on-peek peek:cor + ++ on-leave on-leave:def + :: + ++ on-agent + |= [=wire =sign:agent:gall] + ^- (quip card _this) + =^ cards state abet:(agent:cor wire sign) + [cards this] + :: + ++ on-arvo + |= [=wire sign=sign-arvo] + =^ cards state abet:(arvo:cor wire sign) + [cards this] + :: + ++ on-fail on-fail:def + -- + +|% +:: ++| %state +:: +:: namespaced to avoid accidental direct reference +:: +++ raw + =| out=(list card) + |_ =bowl:gall + +* ol (kol gte) + :: + +| %generic + :: + ++ abet [(flop out) state] + ++ cor . + ++ emit |=(c=card cor(out [c out])) + ++ emil |=(c=(list card) cor(out (weld (flop c) out))) + ++ give |=(=gift:agent:gall (emit %give gift)) + ++ pass |=([=wire =note:agent:gall] (emit %pass wire note)) + :: + +| %operations + :: + :: + ++ pub + => |% + :: if this proves to be too slow, the set of paths + :: should be maintained statefully: put on +p-init:pub, + :: filtered at some interval (on +load?) to avoid a space leak. + :: + :: XX number of peers is usually around 5.000. + :: this means that the number of subscribers is about the + :: same. Thus on each contact update we need to filter + :: over 5.000 elements: do some benchmarking. + :: + ++ subs + ^- (set path) + %- ~(rep by sup.bowl) + :: default .acc prevents invalid empty fact path in the case + :: of no subscribers + :: + |= [[duct ship pat=path] acc=_(sy `path`/v1/contact ~)] + ?.(?=([%v1 %contact *] pat) acc (~(put in acc) pat)) + ++ fact + |= [pat=(set path) u=update] + ^- gift:agent:gall + [%fact ~(tap in pat) %contact-update-1 !>(u)] + -- + :: + |% + :: +p-anon: delete our profile + :: + ++ p-anon ?.(?=([@ ^] rof) cor (p-commit-self ~)) + :: +p-self: edit our profile + :: + ++ p-self + |= con=(map @tas value) + =/ old=contact + ?.(?=([@ ^] rof) *contact con.rof) + =/ new=contact + (do-edit old con) + ?: =(old new) + cor + ?> (sane-contact `our.bowl new) + (p-commit-self new) + :: +p-page-spot: add ship as a contact + :: + ++ p-page-spot + |= [who=ship mod=contact] + ?: (~(has by book) who) + ~| "peer {} is already a contact" !! + =/ con=contact + ~| "peer {} not found" + =/ far=foreign + (~(got by peers) who) + ?~ for.far *contact + con.for.far + ?> (sane-contact ~ mod) + (p-commit-page who con mod) + :: +p-page: create new contact page + :: + ++ p-page + |= [=kip mod=contact] + ?@ kip + (p-page-spot kip mod) + ?: (~(has by book) kip) + ~| "contact page {} already exists" !! + ?> (sane-contact ~ mod) + (p-commit-page kip ~ mod) + :: +p-edit: edit contact page overlay + :: + ++ p-edit + |= [=kip mod=contact] + =/ =page + ~| "contact page {} does not exist" + (~(got by book) kip) + =/ old=contact + mod.page + =/ new=contact + (do-edit old mod) + ?: =(old new) + cor + ?> (sane-contact ?@(kip `kip ~) new) + (p-commit-edit kip con.page new) + :: +p-wipe: delete a contact page + :: + ++ p-wipe + |= wip=(list kip) + %+ roll wip + |= [=kip acc=_cor] + (p-commit-wipe kip) + :: +p-commit-self: publish modified profile + :: + ++ p-commit-self + |= con=contact + =/ p=profile [(mono wen.rof now.bowl) con] + =. rof p + =. cor + (p-news-0 our.bowl (contact:to-0 con)) + =. cor + (p-response [%self con]) + (give (fact subs [%full p])) + :: +p-commit-page: publish new contact page + :: + ++ p-commit-page + |= [=kip =page] + =. book (~(put by book) kip page) + (p-response [%page kip page]) + :: +p-commit-edit: publish contact page update + :: + ++ p-commit-edit + |= [=kip =page] + =. book + (~(put by book) kip page) + (p-response [%page kip page]) + :: +p-commit-wipe: publish contact page wipe + :: + ++ p-commit-wipe + |= =kip + =. book + (~(del by book) kip) + (p-response [%wipe kip]) + :: +p-init: publish our profile + :: + ++ p-init + |= wen=(unit @da) + ?~ wen (give (fact ~ full+rof)) + ?: =(u.wen wen.rof) cor + :: + :: no future subs + ?>((lth u.wen wen.rof) (give (fact ~ full+rof))) + :: +p-news-0: [legacy] publish news + :: + ++ p-news-0 + |= n=news-0:c0 + (give %fact ~[/news] %contact-news !>(n)) + :: +p-response: publish response + :: + ++ p-response + |= r=response + (give %fact ~[/v1/news] %contact-response-0 !>(r)) + -- + :: + :: +sub: subscription mgmt + :: + :: /contact/*: foreign profiles, _s-impl + :: + :: subscription state is tracked per peer in .sag + :: + :: ~: no subscription + :: %want: /contact/* requested + :: + :: for a given peer, we always have at most one subscription, + :: to /contact/* + :: + ++ sub + |^ |= who=ship + ^+ s-impl + ?< =(our.bowl who) + =/ old (~(get by peers) who) + ~(. s-impl who %live ?=(~ old) (fall old *foreign)) + :: + ++ s-many + |= [l=(list ship) f=$-(_s-impl _s-impl)] + ^+ cor + %+ roll l + |= [who=@p acc=_cor] + ?: =(our.bowl who) acc + si-abet:(f (sub:acc who)) + :: + ++ s-impl + |_ [who=ship sas=?(%dead %live) new=? foreign] + :: + ++ si-cor . + :: + ++ si-abet + ^+ cor + ?- sas + %live =. peers (~(put by peers) who [for sag]) + ?. new cor + :: NB: this assumes con.for is only set in +si-hear + :: + =. cor (p-news-0:pub who ~) + (p-response:pub [%peer who ~]) + :: + %dead ?: new cor + =. peers (~(del by peers) who) + :: + :: this is not quite right, reflecting *total* deletion + :: as *contact* deletion. but it's close, and keeps /news simpler + :: + =. cor (p-news-0:pub who ~) + (p-response:pub [%peer who ~]) + == + :: + ++ si-take + |= [=wire =sign:agent:gall] + ^+ si-cor + ?- -.sign + %poke-ack ~|(strange-poke-ack+wire !!) + :: + %watch-ack ~| strange-watch-ack+wire + ?> ?=(%want sag) + ?~ p.sign si-cor + %- (slog 'contact-fail' u.p.sign) + =/ wake=@da (add now.bowl ~m30) + =. retry (~(put by retry) who wake) + %_ si-cor cor + (pass /retry/(scot %p who) %arvo %b %wait wake) + == + :: + %kick si-meet(sag ~) + :: + %fact ?+ p.cage.sign ~|(strange-fact+wire !!) + %contact-update-1 + (si-hear !<(update q.cage.sign)) + == == + :: + ++ si-hear + |= u=update + ^+ si-cor + ?. (sane-contact `src.bowl con.u) + si-cor + ?: &(?=(^ for) (lte wen.u wen.for)) + si-cor + =. last-updated (~(put ol last-updated) who now.bowl) + %_ si-cor + for +.u + cor =. cor + (p-news-0:pub who (contact:to-0 con.u)) + =/ page=(unit page) (~(get by book) who) + :: update contact book and send notification + :: + =? cor ?=(^ page) + ?: =(con.u.page con.u) cor + =. book (~(put by book) who u.page(con con.u)) + (p-response:pub %page who con.u mod.u.page) + (p-response:pub %peer who con.u) + == + :: + :: + ++ si-meet + ^+ si-cor + :: + :: already subscribed + ?: ?=(%want sag) + si-cor + =/ pat [%v1 %contact ?~(for / /at/(scot %da wen.for))] + %_ si-cor + cor (pass /contact %agent [who dap.bowl] %watch pat) + sag %want + == + :: + ++ si-retry + ^+ si-cor + :: + ::XX this works around a gall/behn bug: + :: the timer is identified by the duct. + :: it needn't be the same when gall passes our + :: card to behn. + :: + ?. (~(has by retry) who) + si-cor + =. retry (~(del by retry) who) + si-meet(sag ~) + :: + ++ si-drop si-snub(sas %dead) + :: + ++ si-snub + %_ si-cor + sag ~ + cor ?. ?=(%want sag) cor + :: retry is scheduled, cancel the timer + :: + ?^ when=(~(get by retry) who) + =. retry (~(del by retry) who) + (pass /retry/(scot %p who)/cancel %arvo %b %rest u.when) + (pass /contact %agent [who dap.bowl] %leave ~) + == + -- + -- + :: + :: +migrate: from :contact-store + :: + :: all known ships, non-default profiles, no subscriptions + :: + ++ migrate + => |% + ++ legacy + |% + +$ rolodex (map ship contact) + +$ resource [=entity name=term] + +$ entity ship + +$ contact + $: nickname=@t + bio=@t + status=@t + color=@ux + avatar=(unit @t) + cover=(unit @t) + groups=(set resource) + last-updated=@da + == + -- + -- + :: + ^+ cor + =/ bas /(scot %p our.bowl)/contact-store/(scot %da now.bowl) + ?. .^(? gu+(weld bas /$)) cor + =/ ful .^(rolodex:legacy gx+(weld bas /all/noun)) + :: + |^ + cor(rof us, peers them) + ++ us + %+ fall + (bind (~(get by ful) our.bowl) convert) + *profile + :: + ++ them + ^- ^peers + %- ~(rep by (~(del by ful) our.bowl)) + |= [[who=ship con=contact:legacy] =^peers] + (~(put by peers) who (convert con) ~) + :: + ++ convert + |= con=contact:legacy + ^- profile + %- profile:from-0 + [last-updated.con con(|6 groups.con)] + -- + :: + +| %implementation + :: + ++ init + =. wen.rof now.bowl + (emit %pass /migrate %agent [our dap]:bowl %poke noun+!>(%migrate)) + :: + ++ load + |= old-vase=vase + ^+ cor + |^ =+ !<([old=versioned-state *] old-vase) + =^ caz-0=(list card) old + ?. ?=(%0 -.old) [~ old] + (state-0-to-1 old) + =. cor (emil caz-0) + =? old ?=(%1 -.old) (state-1-to-2 old) + =? cor ?=(%2 -.old) + :: fix incorrectly bunted timestamp for an empty profile + :: migrated from v0. + :: + ?: &(=(*@da wen.rof) ?=(~ con.rof)) + (p-commit-self:pub ~) + cor + =? old ?=(%2 -.old) (state-2-to-3 old) + =? old ?=(%3 -.old) (state-3-to-4 old) + ?> ?=(%4 -.old) + =. state old + inflate-io + :: + +$ state-0 [%0 rof=$@(~ profile-0:c0) rol=rolodex:c0] + +$ state-1 + $: %1 + rof=profile + =^book + =^peers + retry=(map ship @da) :: retry sub at time + == + +$ state-2 + $: %2 + rof=profile + =^book + =^peers + retry=(map ship @da) :: retry sub at time + == + +$ state-3 + $: %3 + rof=profile + =^book + =^peers + last-updated=(list [=kip =time]) + retry=(map ship @da) :: retry sub at time + == + +$ versioned-state + $% state-4 + state-3 + state-2 + state-1 + state-0 + == + :: + ++ state-3-to-4 + |= state-3 + ^- state-4 + =* state +< + :: sanitize our nickname + :: + =+ nick=(~(get cy con.rof) %nickname %text) + =? con.rof &(?=(^ nick) !(sane-nickname `our.bowl u.nick)) + %+ ~(put by con.rof) %nickname + text+(sani-nickname u.nick) + :: sanitize peer nicknames + :: + =. state + %+ roll ~(tap in ~(key by peers)) + |= [her=ship =_state] + =+ far=(~(got by peers) her) + :: examine peer nickname and sanitize it if needed + :: + ?~ for.far state + =+ nick=(~(get cy con.for.far) %nickname %text) + ?~ nick state + ?: (sane-nickname `her u.nick) state + =. u.nick (sani-nickname u.nick) + =. con.for.far + %+ ~(put by con.for.far) %nickname + text+u.nick + =. peers.state + (~(put by peers.state) her far) + :: update the corresponding entry in the contact book, if any. + :: + ?~ page=(~(get by book) her) state + =. con.u.page + %+ ~(put by con.u.page) %nickname + text+u.nick + =. book.state + (~(put by book.state) her u.page) + state + state(- %4) + :: + ++ state-2-to-3 + |= =state-2 + ^- state-3 + [%3 rof book peers ~ retry]:state-2 + :: + ++ state-1-to-2 + |= =state-1 + ^- state-2 + state-1(- %2) + :: + ++ state-0-to-1 + |= state-0 + ^- [(list card) state-1] + =| =state-1 + =. rof.state-1 + ?~(rof *profile (profile:from-0 rof)) + :: migrate peers. for each peer + :: 1. leave /epic, if any + :: 2. subscribe if desired + :: 3. put into peers + :: + =^ caz=(list card) peers.state-1 + %+ roll ~(tap by rol) + |= [[who=ship foreign-0:c0] caz=(list card) =_peers.state-1] + :: leave /epic if any + :: + =? caz (~(has by wex.bowl) [/epic who dap.bowl]) + :_ caz + [%pass /epic %agent [who dap.bowl] %leave ~] + =/ fir=$@(~ profile) + ?~ for ~ + (profile:from-0 for) + :: no intent to connect + :: + ?: =(~ sag) + :- caz + (~(put by peers) who fir ~) + :_ (~(put by peers) who fir %want) + ?: (~(has by wex.bowl) [/contact who dap.bowl]) + caz + =/ =path [%v1 %contact ?~(fir / /at/(scot %da wen.fir))] + :_ caz + [%pass /contact %agent [who dap.bowl] %watch path] + [caz state-1] + :: + ++ inflate-io + ^+ cor + =/ cards + %+ roll ~(tap by peers) + |= [[who=ship foreign] caz=(list card)] + :: intent to connect, resubscribe + :: + ?: ?& =(%want sag) + !(~(has by wex.bowl) [/contact who dap.bowl]) + == + =/ =path [%v1 %contact ?~(for / /at/(scot %da wen.for))] + :_ caz + [%pass /contact %agent [who dap.bowl] %watch path] + caz + (emil cards) + -- + :: + ++ poke + |= [=mark =vase] + ^+ cor + ?+ mark ~|(bad-mark+mark !!) + %noun + ?+ q.vase !! + %migrate migrate + == + $? %contact-action + %contact-action-0 + %contact-action-1 + == + ?> =(our src):bowl + =/ act=action + ?- mark + :: + %contact-action-1 + !<(action vase) + :: upconvert legacy %contact-action + :: + ?(%contact-action %contact-action-0) + =/ act-0 !<(action-0:c0 vase) + ?. ?=(%edit -.act-0) + (to-action act-0) + :: v0 %edit needs special handling to evaluate + :: groups edit + :: + =/ groups=(set $>(%flag value)) + ?~ con.rof ~ + =+ set=(~(ges cy con.rof) groups+%flag) + (fall set ~) + [%self (to-self-edit p.act-0 groups)] + == + =^ kis=(list kip) cor + ?- -.act + %anon [[our.bowl]~ p-anon:pub] + %self [[our.bowl]~ (p-self:pub p.act)] + :: if we add a page for someone who is not a peer, + :: we meet them first + :: + %page :- [p.act]~ + =? cor &(?=(ship p.act) !(~(has by peers) p.act)) + si-abet:si-meet:(sub p.act) + (p-page:pub p.act q.act) + %edit [[p.act]~ (p-edit:pub p.act q.act)] + %wipe [p.act (p-wipe:pub p.act)] + %meet [~ (s-many:sub p.act |=(s=_s-impl:sub si-meet:s))] + %drop [~ (s-many:sub p.act |=(s=_s-impl:sub si-drop:s))] + %snub [~ (s-many:sub p.act |=(s=_s-impl:sub si-snub:s))] + == + =- cor(last-updated -) + %+ roll kis + |= [=kip =_last-updated] + (~(put ol last-updated) kip now.bowl) + == + :: +peek: scry + :: + :: v0 scries + :: + :: /x/all -> $rolodex:c0 + :: /x/contact/her=@ -> $@(~ contact-0:c0) + :: + :: v1 scries + :: + :: /x/v1/self -> $contact + :: /x/v1/book -> $book + :: /x/v1/book/her=@p -> $page + :: /x/v1/book/id/cid=@uv -> $page + :: /x/v1/all -> $directory + :: /x/v1/contact/her=@p -> $contact + :: /x/v1/peer/her=@p -> $contact + :: + ++ peek + |= pat=(pole knot) + ^- (unit (unit cage)) + ?+ pat [~ ~] + :: + [%x %all ~] + =/ rol-0=rolodex:c0 + %- ~(urn by peers) + |= [who=ship far=foreign] + ^- foreign-0:c0 + =/ mod=contact + ?~ page=(~(get by book) who) + ~ + mod.u.page + (foreign:to-0 (foreign-mod far mod)) + =/ lor-0=rolodex:c0 + ?: ?=(~ con.rof) rol-0 + (~(put by rol-0) our.bowl (profile:to-0 rof) ~) + ``contact-rolodex+!>(lor-0) + :: + [%x %contact her=@ ~] + ?~ who=(slaw %p her.pat) + [~ ~] + =/ tac=?(~ contact-0:c0) + ?: =(our.bowl u.who) + ?~(con.rof ~ (contact:to-0 con.rof)) + =+ far=(~(get by peers) u.who) + ?: |(?=(~ far) ?=(~ for.u.far)) ~ + (contact:to-0 con.for.u.far) + ?~ tac [~ ~] + ``contact+!>(`contact-0:c0`tac) + :: + [%x %v1 %self ~] + ``contact-1+!>(`contact`con.rof) + :: + [%x %v1 %book ~] + ``contact-book-0+!>(book) + :: + [%u %v1 %book her=@p ~] + ?~ who=(slaw %p her.pat) + [~ ~] + ``loob+!>((~(has by book) u.who)) + :: + [%x %v1 %book her=@p ~] + ?~ who=(slaw %p her.pat) + [~ ~] + =/ page=(unit page) + (~(get by book) u.who) + ``contact-page-0+!>(`^page`(fall page *^page)) + :: + [%u %v1 %book %id =cid ~] + ?~ id=(slaw %uv cid.pat) + [~ ~] + ``loob+!>((~(has by book) id+u.id)) + :: + [%x %v1 %book %id =cid ~] + ?~ id=(slaw %uv cid.pat) + [~ ~] + =/ page=(unit page) + (~(get by book) id+u.id) + ``contact-page-0+!>(`^page`(fall page *^page)) + :: + [%x %v1 %all ~] + =| dir=directory + :: export all ship contacts + :: + =. dir + %- ~(rep by book) + |= [[=kip =page] =_dir] + ?^ kip + dir + (~(put by dir) kip (contact-uni page)) + :: export all peers + :: + =. dir + %- ~(rep by peers) + |= [[who=ship far=foreign] =_dir] + ?~ for.far dir + ?: (~(has by dir) who) dir + (~(put by dir) who con.for.far) + ``contact-directory-0+!>(dir) + :: + [%x %v1 %changes since=@ ~] + =+ since=(slav %da since.pat) + :^ ~ ~ + %contact-changed-contacts + !> ^- (map ship profile) + %- ~(rep by peers) + |= [[who=ship foreign] out=(map ship profile)] + ?~ for out + ?: (lte wen.for since) out + (~(put by out) who for) + :: + [%x %v2 %changes since=@ ~] + =+ since=(slav %da since.pat) + :^ ~ ~ + %contact-changed-pages + !> %- ~(gas by *(map kip [(unit contact) (unit contact)])) + %+ turn (~(top ol last-updated) since) + |= [=kip @da] + ^- [_kip con=(unit contact) mod=(unit contact)] + :- kip + ?: =(our.bowl kip) [`con.rof ~] + :_ (bind (~(get by book) kip) tail) + ?^ kip ~ + =+ per=(~(get by peers) kip) + ?:(?=([~ ^ *] per) `con.for.u.per ~) + :: + [%u %v1 %contact her=@p ~] + ?~ who=(slaw %p her.pat) + [~ ~] + ?: (~(has by book) u.who) + ``loob+!>(&) + =- ``loob+!>(-) + ?~ far=(~(get by peers) u.who) + | + ?~ for.u.far + | + & + :: + [%x %v1 %contact her=@p ~] + ?~ who=(slaw %p her.pat) + [~ ~] + ?^ page=(~(get by book) u.who) + ``contact-1+!>((contact-uni u.page)) + ?~ far=(~(get by peers) u.who) + [~ ~] + ?~ for.u.far + [~ ~] + ``contact-1+!>(con.for.u.far) + :: + [%u %v1 %peer her=@p ~] + ?~ who=(slaw %p her.pat) + [~ ~] + ``loob+!>((~(has by peers) u.who)) + :: + [%x %v1 %peer her=@p ~] + ?~ who=(slaw %p her.pat) + [~ ~] + ?~ far=(~(get by peers) u.who) + [~ ~] + ``contact-foreign-0+!>(`foreign`u.far) + == + :: + ++ peer + |= pat=(pole knot) + ^+ cor + ?+ pat ~|(bad-watch-path+pat !!) + :: + :: v0 + [%news ~] ~|(local-news+src.bowl ?>(=(our src):bowl cor)) + :: + :: v1 + [%v1 %contact ~] (p-init:pub ~) + [%v1 %contact %at wen=@ ~] (p-init:pub `(slav %da wen.pat)) + [%v1 %news ~] ~|(local-news+src.bowl ?>(=(our src):bowl cor)) + :: + [%epic ~] (give %fact ~ epic+!>(okay)) + == + :: + ++ agent + |= [=wire =sign:agent:gall] + ^+ cor + ?+ wire ~|(evil-agent+wire !!) + [%contact ~] + si-abet:(si-take:(sub src.bowl) wire sign) + :: + [%migrate ~] + ?> ?=(%poke-ack -.sign) + ?~ p.sign cor + %- (slog leaf/"{} failed" u.p.sign) + cor + :: + [%activity ~] + cor + :: + [%epic ~] + cor + == + :: + ++ arvo + |= [=wire sign=sign-arvo] + ^+ cor + ?+ wire ~|(evil-vane+wire !!) + :: + [%retry her=@p ~] + :: XX technically, the timer could fail. + :: it should be ok to still retry. + :: + ?> ?=([%behn %wake *] sign) + =+ who=(slav %p i.t.wire) + si-abet:si-retry:(sub who) + == + -- +-- diff --git a/app/app/nostrill.hoon b/app/app/nostrill.hoon index 81f67f1..0192e7c 100644 --- a/app/app/nostrill.hoon +++ b/app/app/nostrill.hoon @@ -60,7 +60,6 @@ |~ [=mark =vase] ^- (quip card:agent:gall agent:gall) |^ - ~& nostrill-on-poke=mark ?+ mark `this %noun handle-comms %json on-ui @@ -73,7 +72,6 @@ == ++ on-ted =/ pok !<(ted:ui vase) - :: =/ poke !<() =^ cards state (handle-ted:mutan pok) :_ this cards @@ -704,6 +702,23 @@ :~ (urbit-watch:fols ~zod) [%pass /foldbug %agent [~zod dap.bowl] %poke %bitch !>(~)] == + %uinf + :_ this + :~ (update-ui:cards [%nostr %feed nostr-feed]) + == + %tedt + =/ teds .^((list path) %gx /(scot %p our.bowl)/spider/(scot %da now.bowl)/tree/noun) + ~& teds + =/ flt=(list @t) (zing teds) + |- ?~ flt ~& 'no ted' `this + ?. .=('sync-' (cut 3 [0 5] i.flt)) $(flt t.flt) + =/ tid i.flt + + :_ this + =/ payload [%res 'lmao'] + :~ + [%pass /fuck/this %agent [our.bowl %spider] %poke %spider-input !>([tid %nostrill-ted !>(payload)])] + == :: :: TODO refactoring into mutations :: :: :: %rt0 @@ -779,21 +794,25 @@ ?+ pole `this [%websocket-client wids=@ ~] :: reconnect logic requires knowing which side dropped the connection, which is... tricky - `this - :: ~& websocket-client-connection-dropped=`@t`wids.pole - :: =/ wid (slav %ud wids.pole) + ~& websocket-client-connection-dropped=`@t`wids.pole + =/ uwid (slaw %ud wids.pole) + ?~ uwid `this + =/ wid u.uwid + =. relays (~(del by relays) wid) :: :: check if it's the global relay :: =/ is-global .= `wid global-relay-conn :: ?: is-global :: ~& reconnecting-to-global=wid :: :_ this :~((connect:ws global-relay:constants bowl)) :: :: - :: =/ relay (~(get by relays) wid) - :: ?~ relay `this :: that would be weird :: :_ this :: ~& reconnecting=url.u.relay :: :~ (connect:ws url.u.relay bowl) :: == + :_ this + :: TODO more minute disconnected note + :~ (update-ui:cards [%nostr %relays relays]) + == == :: @@ -843,7 +862,7 @@ ++ on-arvo |~ [wire=(pole knot) =sign-arvo] ^- (quip card:agent:gall agent:gall) - ~& >> on-arvo=[`path`wire -.sign-arvo +<.sign-arvo] + :: ~& >> on-arvo=[`path`wire -.sign-arvo +<.sign-arvo] ?: ?=(%iris -.sign-arvo) :: ~& > +.sign-arvo `this diff --git a/app/desk.bill b/app/desk.bill index ddea07e..b8d37c7 100644 --- a/app/desk.bill +++ b/app/desk.bill @@ -1,2 +1,3 @@ :~ %nostrill + %n-contacts == diff --git a/app/desk.docket-0 b/app/desk.docket-0 index e2e0735..692be88 100644 --- a/app/desk.docket-0 +++ b/app/desk.docket-0 @@ -2,10 +2,10 @@ title+'Trill 2' info+'Pater Nostr' color+0xff.d400 - version+[0 1 1] + version+[0 1 2] website+'https://groundwire.dev' license+'Noli me tangere' image+'https://s3.sortug.com/img/nostrill-tile.png' base+'nostrill' - glob-http+['https://s3.sortug.com/globs/glob-0v2.0n7m8.g69ms.uejn8.dkget.1jm7v.glob' 0v2.0n7m8.g69ms.uejn8.dkget.1jm7v] + glob-http+['https://s3.sortug.com/globs/glob-0vjp0je.6lheq.e8r6b.5e483.59v2p.glob' 0vjp0je.6lheq.e8r6b.5e483.59v2p] == diff --git a/app/lib/contacts/contacts.hoon b/app/lib/contacts/contacts.hoon new file mode 100644 index 0000000..82bf8f8 --- /dev/null +++ b/app/lib/contacts/contacts.hoon @@ -0,0 +1,536 @@ +/- *contacts, c0=contacts-0 +/+ unicode +|% +:: ++| %contact +:: +is-value-empty: is value considered empty +:: +++ is-value-empty + |= val=value + ^- ? + ?+ -.val | + %text =('' p.val) + %look =('' p.val) + %set ?=(~ p.val) + == +:: +cy: contact map engine +:: +++ cy + |_ c=contact + :: +typ: enforce type if value exists + :: + ++ typ + |* [key=@tas typ=value-type] + ^- ? + =/ val=(unit value) (~(get by c) key) + ?~ val & + ?~ u.val | + ?- typ + %text ?=(%text -.u.val) + %numb ?=(%numb -.u.val) + %date ?=(%date -.u.val) + %tint ?=(%tint -.u.val) + %ship ?=(%ship -.u.val) + %look ?=(%look -.u.val) + %flag ?=(%flag -.u.val) + %set ?=(%set -.u.val) + == + :: +get: typed get + :: + ++ get + |* [key=@tas typ=value-type] + ^- (unit _p:*$>(_typ value)) + =/ val=(unit value) (~(get by c) key) + ?~ val ~ + ?~ u.val !! + ~| "{} expected at {}" + ?- typ + %text ?>(?=(%text -.u.val) (some p.u.val)) + %numb ?>(?=(%numb -.u.val) (some p.u.val)) + %date ?>(?=(%date -.u.val) (some p.u.val)) + %tint ?>(?=(%tint -.u.val) (some p.u.val)) + %ship ?>(?=(%ship -.u.val) (some p.u.val)) + %look ?>(?=(%look -.u.val) (some p.u.val)) + %flag ?>(?=(%flag -.u.val) (some p.u.val)) + %set ?>(?=(%set -.u.val) (some p.u.val)) + == + :: +ges: get specialized to typed set + :: + ::TODO introduce more legible names, ges -> get-set etc. + ++ ges + |* [key=@tas typ=value-type] + ^- (unit (set $>(_typ value))) + =/ val=(unit value) (~(get by c) key) + ?~ val ~ + ?. ?=(%set -.u.val) + ~| "set expected at {}" !! + %- some + %- ~(run in p.u.val) + ?- typ + %text |=(v=value ?>(?=(%text -.v) v)) + %numb |=(v=value ?>(?=(%numb -.v) v)) + %date |=(v=value ?>(?=(%date -.v) v)) + %tint |=(v=value ?>(?=(%tint -.v) v)) + %ship |=(v=value ?>(?=(%ship -.v) v)) + %look |=(v=value ?>(?=(%look -.v) v)) + %flag |=(v=value ?>(?=(%flag -.v) v)) + %set |=(v=value ?>(?=(%set -.v) v)) + == + :: +gos: got specialized to typed set + :: + ++ gos + |* [key=@tas typ=value-type] + ^- (set $>(_typ value)) + =/ val=value (~(got by c) key) + ?. ?=(%set -.val) + ~| "set expected at {}" !! + %- ~(run in p.val) + ?- typ + %text |=(v=value ?>(?=(%text -.v) v)) + %numb |=(v=value ?>(?=(%numb -.v) v)) + %date |=(v=value ?>(?=(%date -.v) v)) + %tint |=(v=value ?>(?=(%tint -.v) v)) + %ship |=(v=value ?>(?=(%ship -.v) v)) + %look |=(v=value ?>(?=(%look -.v) v)) + %flag |=(v=value ?>(?=(%flag -.v) v)) + %set |=(v=value ?>(?=(%set -.v) v)) + == + :: +gut: typed gut with default + :: + ++ gut + |* [key=@tas def=value] + ^+ +.def + =/ val=value (~(gut by c) key ~) + ?~ val + +.def + ~| "{<-.def>} expected at {}" + ?- -.val + %text ?>(?=(%text -.def) p.val) + %numb ?>(?=(%numb -.def) p.val) + %date ?>(?=(%date -.def) p.val) + %tint ?>(?=(%tint -.def) p.val) + %ship ?>(?=(%ship -.def) p.val) + %look ?>(?=(%look -.def) p.val) + %flag ?>(?=(%flag -.def) p.val) + %set ?>(?=(%set -.def) p.val) + == + :: +gub: typed gut with bunt default + :: + ++ gub + |* [key=@tas typ=value-type] + ^+ +:*$>(_typ value) + =/ val=value (~(gut by c) key ~) + ?~ val + ?+ typ !! + %text *@t + %numb *@ud + %date *@da + %tint *@ux + %ship *@p + %look *@t + %flag *flag + %set *(set value) + == + ~| "{} expected at {}" + ?- typ + %text ?>(?=(%text -.val) p.val) + %numb ?>(?=(%numb -.val) p.val) + %date ?>(?=(%date -.val) p.val) + %tint ?>(?=(%tint -.val) p.val) + %ship ?>(?=(%ship -.val) p.val) + %look ?>(?=(%look -.val) p.val) + %flag ?>(?=(%flag -.val) p.val) + %set ?>(?=(%set -.val) p.val) + == + -- +:: +++ do-edit-0 + |= [c=contact-0:c0 f=field-0:c0] + ^+ c + ?- -.f + %nickname c(nickname nickname.f) + %bio c(bio bio.f) + %status c(status status.f) + %color c(color color.f) + :: + %avatar ~| "cannot add a data url to avatar!" + ?> ?| ?=(~ avatar.f) + !=('data:' (end 3^5 u.avatar.f)) + == + c(avatar avatar.f) + :: + %cover ~| "cannot add a data url to cover!" + ?> ?| ?=(~ cover.f) + !=('data:' (end 3^5 u.cover.f)) + == + c(cover cover.f) + :: + %add-group c(groups (~(put in groups.c) flag.f)) + :: + %del-group c(groups (~(del in groups.c) flag.f)) + == +:: +sane-contact: verify contact sanity +:: +:: - restrict size of the jammed noun to 10kB +:: - prohibit 'data:' URLs in image data +:: - nickname and bio must be a %text +:: - avatar and cover must be a %look +:: - groups must be a %set of %flags +:: +++ sane-contact + |= [src=(unit @p) con=contact] + ^- ? + ?~ ((soft contact) con) + | + :: 10kB contact ought to be enough for anybody + :: + ?: (gth (met 3 (jam con)) 10.000) + | + :: field restrictions + :: + :: 1. %nickname field: max 64 characters + :: 2. %bio field: max 2048 characters + :: 3. data URLs in %avatar and %cover + :: are forbidden + :: + ?. (~(typ cy con) %nickname %text) | + =+ nickname=(~(get cy con) %nickname %text) + ?: ?& ?=(^ nickname) + ?| (gth (met 3 u.nickname) 64) + !(sane-nickname src u.nickname) + == + == + | + ?. (~(typ cy con) %bio %text) | + =+ bio=(~(get cy con) %bio %text) + ?: ?& ?=(^ bio) + (gth (met 3 u.bio) 2.048) + == + | + ?. (~(typ cy con) %avatar %look) | + =+ avatar=(~(get cy con) %avatar %look) + ?: ?& ?=(^ avatar) + =('data:' (end 3^5 u.avatar)) + == + | + ?. (~(typ cy con) %cover %look) | + =+ cover=(~(get cy con) %cover %look) + ?: ?& ?=(^ cover) + =('data:' (end 3^5 u.cover)) + == + | + ?. (~(typ cy con) %groups %set) | + =+ groups=(~(get cy con) %groups %set) + :: verifying the type of the first set element is enough, + :: set uniformity is verified by +soft above. + :: + ?: ?& ?=(^ groups) + ?=(^ u.groups) + !?=(%flag -.n.u.groups) + == + | + & +:: +sane-nickname: validate a nickname against network id +:: +++ sane-nickname + |= [src=(unit @p) txt=@t] + ^- ? + ?~ src & + =+ nom=(norm-p:confusable:unicode txt) + :: TODO for better performance, should probably operate on + :: tapes. + |- + ?: =('' nom) & + =+ len=(teff nom) + =* next $(nom (rsh [3 len] nom)) + =+ c=(end [3 len] nom) + ?. =('~' c) next + :: could be the beginning of a pat-p, attempt to parse + :: + =/ vex (;~(pfix sig fed:ag) [[1 1] (trip nom)]) + ?~ q.vex next + =/ ship=@p p.u.q.vex + ?. =(ship u.src) | + =+ pos=q.p.vex + $(nom (rsh [3 (dec pos)] nom)) + :: q.p.vex +:: +sani-nickname: sanitize a nickname +:: +:: assume nickname is insane and delete all characters +:: that normalize to a sig. +:: +++ sani-nickname + |= txt=@t + ^- @t + =| norm=@t + |- + ?: =('' txt) norm + =+ len=(teff txt) + =+ c=(end [3 len] txt) + =* next + $(norm (cat 3 norm c), txt (rsh [3 len] txt)) + =* skip + $(txt (rsh [3 len] txt)) + ?: (~(has in con-sig:confusable:unicode) c) + skip + next +:: +do-edit: edit contact +:: +:: edit .con with .mod contact map. +:: unifies the two maps, and deletes any resulting fields +:: that are null. +:: +++ do-edit + |= [con=contact mod=(map @tas value)] + ^+ con + =/ don (~(uni by con) mod) + =/ del=(list @tas) + %- ~(rep by don) + |= [[key=@tas val=value] acc=(list @tas)] + ?. ?=(~ val) acc + [key acc] + =? don !=(~ del) + %+ roll del + |= [key=@tas acc=_don] + (~(del by acc) key) + don +:: +from-0: legacy to new type +:: +++ from-0 + |% + :: +contact: convert legacy to contact + :: + ++ contact + |= o=contact-0:c0 + ^- ^contact + =/ c=^contact + %- malt + ^- (list (pair @tas value)) + :~ nickname+text/nickname.o + bio+text/bio.o + status+text/status.o + color+tint/color.o + == + =? c ?=(^ avatar.o) + (~(put by c) %avatar look/u.avatar.o) + =? c ?=(^ cover.o) + (~(put by c) %cover look/u.cover.o) + =? c !?=(~ groups.o) + %+ ~(put by c) %groups + :- %set + %- ~(run in groups.o) + |= =flag + flag/flag + c + :: +profile: convert legacy to profile + :: + ++ profile + |= o=profile-0:c0 + ^- ^profile + [wen.o ?~(con.o ~ (contact con.o))] + :: + -- +:: +from: legacy from new type +:: +++ to-0 + |% + :: +contact: convert contact to legacy + :: + ++ contact + |= c=^contact + ^- $@(~ contact-0:c0) + ?~ c ~ + =| o=contact-0:c0 + %_ o + nickname + (~(gub cy c) %nickname %text) + bio + (~(gub cy c) %bio %text) + status + (~(gub cy c) %status %text) + color + (~(gub cy c) %color %tint) + avatar + (~(get cy c) %avatar %look) + cover + (~(get cy c) %cover %look) + groups + =/ groups + (~(get cy c) %groups %set) + ?~ groups ~ + ^- (set flag) + %- ~(run in u.groups) + |= val=value + ?> ?=(%flag -.val) + p.val + == + :: +profile: convert profile to legacy + :: + ++ profile + |= p=^profile + ^- profile-0:c0 + [wen.p (contact:to-0 con.p)] + :: +profile-0-mod: convert profile with contact overlay + :: to legacy + :: + ++ profile-mod + |= [p=^profile mod=^contact] + ^- profile-0:c0 + [wen.p (contact:to-0 (contact-uni con.p mod))] + :: +foreign: convert foreign to legacy + :: + ++ foreign + |= f=^foreign + ^- foreign-0:c0 + [?~(for.f ~ (profile:to-0 for.f)) sag.f] + :: foreign-mod: convert foreign with contact overlay + :: to legacy + :: + ++ foreign-mod + |= [f=^foreign mod=^contact] + ^- foreign-0:c0 + [?~(for.f ~ (profile-mod:to-0 for.f mod)) sag.f] + -- +:: +contact-uni: merge contacts +:: +++ contact-uni + |= [c=contact mod=contact] + ^- contact + (~(uni by c) mod) +:: +foreign-contact: get foreign contact +:: +++ foreign-contact + |= far=foreign + ^- contact + ?~(for.far ~ con.for.far) +:: +foreign-mod: modify foreign profile with user overlay +:: +++ foreign-mod + |= [far=foreign mod=contact] + ^- foreign + ?~ for.far + far + far(con.for (contact-uni con.for.far mod)) +:: +sole-field-0: sole field is a field that does +:: not modify the groups set +:: ++$ sole-field-0 + $~ nickname+'' + $<(?(%add-group %del-group) field-0:c0) +:: +to-sole-edit: convert legacy sole field to contact edit +:: +:: modify any field except for groups +:: +++ to-sole-edit + |= edit-0=(list sole-field-0) + ^- contact + %+ roll edit-0 + |= $: fed=sole-field-0 + acc=(map @tas value) + == + ^+ acc + ?- -.fed + :: + %nickname + %+ ~(put by acc) + %nickname + text/nickname.fed + :: + %bio + %+ ~(put by acc) + %bio + text/bio.fed + :: + %status + %+ ~(put by acc) + %status + text/status.fed + :: + %color + %+ ~(put by acc) + %color + tint/color.fed + :: + %avatar + ?~ avatar.fed acc + %+ ~(put by acc) + %avatar + look/u.avatar.fed + :: + %cover + ?~ cover.fed acc + %+ ~(put by acc) + %cover + look/u.cover.fed + == +:: +to-self-edit: convert legacy to self edit +:: +++ to-self-edit + |= [edit-0=(list field-0:c0) groups=(set value)] + ^- contact + :: converting v0 profile edit to v1 is non-trivial. + :: for field edits other than groups, we derive a contact + :: edition map. for group operations (%add-group, %del-group) + :: we need to operate directly on (existing?) groups field in + :: the profile. + :: + :: .sed: sole field edits, no group edits + :: .ged: only group edit actions + :: + =* group-type ?(%add-group %del-group) + =* sole-edits (list $<(group-type field-0:c0)) + =* group-edits (list $>(group-type field-0:c0)) + :: sift edits + :: + =/ [sed=sole-edits ged=group-edits] + :: + :: XX why is casting neccessary here? + =- [(flop `sole-edits`-<) (flop `group-edits`->)] + %+ roll edit-0 + |= [f=field-0:c0 sed=sole-edits ged=group-edits] + ^+ [sed ged] + ?. ?=(group-type -.f) + :- [f sed] + ged + :- sed + [f ged] + :: edit favourite groups + :: + =. groups + %+ roll ged + |= [fav=$>(group-type field-0:c0) =_groups] + ?- -.fav + %add-group + (~(put in groups) flag/flag.fav) + %del-group + (~(del in groups) flag/flag.fav) + == + %+ ~(put by (to-sole-edit sed)) + %groups + set/groups +:: +to-action: convert legacy to action +:: +:: convert any action except %edit. +:: %edit must be handled separately, since we need +:: access to existing groups to be able to process group edits. +:: +++ to-action + |= o=$<(%edit action-0:c0) + ^- action + ?- -.o + %anon [%anon ~] + :: + :: old %meet is now a no-op + %meet [%meet ~] + %heed [%meet p.o] + %drop [%drop p.o] + %snub [%snub p.o] + == +:: +mono: tick time +:: +++ mono + |= [old=@da new=@da] + ^- @da + ?: (lth old new) new + (add old ^~((rsh 3^2 ~s1))) +-- diff --git a/app/lib/contacts/json-0.hoon b/app/lib/contacts/json-0.hoon new file mode 100644 index 0000000..af69d80 --- /dev/null +++ b/app/lib/contacts/json-0.hoon @@ -0,0 +1,172 @@ +/- c=contacts +/- legacy=contacts-0 +=, legacy +|% +++ gj +|% + ++ enjs + =, enjs:format + |% + ++ print-flag + |= f=flag:c + ^- @t + (rap 3 (scot %p p.f) '/' q.f ~) + ++ flag + |= f=flag:c + s+(print-flag f) + ++ print-nest + |= n=nest:c + ^- @t + (rap 3 p.n '/' (print-flag q.n) ~) + :: + ++ nest + |= n=nest:c + s+(print-nest n) +-- +++ dejs +=, dejs:format +|% + ++ p + |% + ++ rank (perk %czar %king %duke %earl %pawn ~) + ++ ship ;~(pfix sig fed:ag) + ++ flag ;~((glue fas) ship ^sym) + ++ nest ;~((glue fas) ^sym flag) + -- + ++ sym (se %tas) + ++ ship (se %p) + ++ rank (su rank:p) + ++ flag (su flag:p) + ++ nest (su nest:p) + -- +-- +++ enjs + =, enjs:format + |% + :: XX shadowed for compat, +ship:enjs removes the ~ + :: + ++ ship + |=(her=@p n+(rap 3 '"' (scot %p her) '"' ~)) + :: + ++ action + |= a=action-0 + ^- json + %+ frond -.a + ?- -.a + %anon ~ + %edit a+(turn p.a field) + %meet a+(turn p.a ship) + %heed a+(turn p.a ship) + %drop a+(turn p.a ship) + %snub a+(turn p.a ship) + == + :: + ++ contact + |= c=contact-0 + ^- json + %- pairs + :~ nickname+s+nickname.c + bio+s+bio.c + status+s+status.c + color+s+(scot %ux color.c) + avatar+?~(avatar.c ~ s+u.avatar.c) + cover+?~(cover.c ~ s+u.cover.c) + :: + =- groups+a+- + %- ~(rep in groups.c) + |=([f=flag j=(list json)] [(flag:enjs:gj f) j]) + == + :: + ++ field + |= f=field-0 + ^- json + %+ frond -.f + ?- -.f + %nickname s+nickname.f + %bio s+bio.f + %status s+status.f + %color s+(rsh 3^2 (scot %ux color.f)) :: XX confirm + %avatar ?~(avatar.f ~ s+u.avatar.f) + %cover ?~(cover.f ~ s+u.cover.f) + %add-group (flag:enjs:gj flag.f) + %del-group (flag:enjs:gj flag.f) + == + :: + ++ rolodex + |= r=^rolodex + ^- json + %- pairs + %- ~(rep by r) + |= [[who=@p foreign-0] j=(list [@t json])] + [[(scot %p who) ?.(?=([@ ^] for) ~ (contact con.for))] j] :: XX stale flag per sub state? + :: + ++ news + |= n=news-0 + ^- json + %- pairs + :~ who+(ship who.n) + con+?~(con.n ~ (contact con.n)) + == + -- +:: +++ dejs + =, dejs:format + |% + :: for performance, @p is serialized above to json %n (no escape) + :: for mark roundtrips, ships are parsed from either %s or %n + :: XX do this elsewhere in groups? + :: + ++ ship (se-ne %p) + ++ se-ne + |= aur=@tas + |= jon=json + ?+ jon !! + [%s *] (slav aur p.jon) + :: XX this seems wrong: current JSON parser + :: would never pass a ship as a number + :: + [%n *] ~| bad-n+p.jon + =/ wyd (met 3 p.jon) + ?> ?& =('"' (end 3 p.jon)) + =('"' (cut 3 [(dec wyd) 1] p.jon)) + == + (slav aur (cut 3 [1 (sub wyd 2)] p.jon)) + == + :: + ++ action + ^- $-(json action-0) + %- of + :~ anon+ul + edit+(ar field) + meet+(ar ship) + heed+(ar ship) + drop+(ar ship) + snub+(ar ship) + == + :: + ++ contact + ^- $-(json contact-0) + %- ot + :~ nickname+so + bio+so + status+so + color+nu + avatar+(mu so) + cover+(mu so) + groups+(as flag:dejs:gj) + == + :: + ++ field + ^- $-(json field-0) + %- of + :~ nickname+so + bio+so + status+so + color+nu + avatar+(mu so) + cover+(mu so) + add-group+flag:dejs:gj + del-group+flag:dejs:gj + == + -- +-- diff --git a/app/lib/contacts/json-1.hoon b/app/lib/contacts/json-1.hoon new file mode 100644 index 0000000..514c303 --- /dev/null +++ b/app/lib/contacts/json-1.hoon @@ -0,0 +1,190 @@ +/- c=contacts +|% +++ g +|% + ++ enjs + =, enjs:format + |% + ++ print-flag + |= f=flag:c + ^- @t + (rap 3 (scot %p p.f) '/' q.f ~) + ++ flag + |= f=flag:c + s+(print-flag f) + ++ print-nest + |= n=nest:c + ^- @t + (rap 3 p.n '/' (print-flag q.n) ~) + :: + ++ nest + |= n=nest:c + s+(print-nest n) +-- +++ dejs +=, dejs:format +|% + ++ p + |% + ++ rank (perk %czar %king %duke %earl %pawn ~) + ++ ship ;~(pfix sig fed:ag) + ++ flag ;~((glue fas) ship ^sym) + ++ nest ;~((glue fas) ^sym flag) + -- + ++ sym (se %tas) + ++ ship (se %p) + ++ rank (su rank:p) + ++ flag (su flag:p) + ++ nest (su nest:p) + -- +-- +++ enjs + =, enjs:format + |% + :: + ++ ship + |=(her=@p n+(rap 3 '"' (scot %p her) '"' ~)) + :: + ++ cid + |= =cid:c + ^- json + s+(scot %uv cid) + :: + ++ kip + |= =kip:c + ^- json + ?@ kip + (ship kip) + (cid +.kip) + :: + ++ value + |= val=value:c + ^- json + ?@ val + (frond type+s/%null) + ?- -.val + %text (pairs type+s/%text value+s+p.val ~) + %numb (pairs type+s/%numb value+(numb p.val) ~) + %date (pairs type+s/%date value+s+(scot %da p.val) ~) + %tint (pairs type+s/%tint value+s+(rsh 3^2 (scot %ux p.val)) ~) + %ship (pairs type+s/%ship value+(ship p.val) ~) + %look (pairs type+s/%look value+s/p.val ~) + %flag (pairs type+s/%flag value+(flag:enjs:g p.val) ~) + %set (pairs type+s/%set value+a+(turn ~(tap in p.val) value) ~) + == + :: + ++ contact + |= con=contact:c + ^- json + o+(~(run by con) value) + :: + ++ page + |= =page:c + ^- json + a+[(contact con.page) (contact mod.page) ~] + :: + ++ book + |= =book:c + ^- json + =| kob=(map @ta json) + :- %o + %- ~(rep by book) + |= [[=kip:c =page:c] acc=_kob] + ?^ kip + (~(put by acc) (scot %uv +.kip) (^page page)) + (~(put by acc) (scot %p kip) (^page page)) + :: + ++ directory + |= =directory:c + ^- json + =| dir=(map @ta json) + :- %o + %- ~(rep by directory) + |= [[who=@p con=contact:c] acc=_dir] + (~(put by acc) (scot %p who) (contact con)) + :: + ++ response + |= n=response:c + ^- json + %+ frond -.n + ?- -.n + %self (frond contact+(contact con.n)) + %page %- pairs + :~ kip+(kip kip.n) + contact+(contact con.n) + mod+(contact mod.n) + == + %wipe (frond kip+(kip kip.n)) + %peer %- pairs + :~ who+(ship who.n) + contact+(contact con.n) + == + == + -- +:: +++ dejs + =, dejs:format + |% + :: + ++ ship (se %p) + :: + ++ cid + |= jon=json + ^- cid:c + ?> ?=(%s -.jon) + (slav %uv p.jon) + :: + ++ kip + |= jon=json + ^- kip:c + ?> ?=(%s -.jon) + ?: =('~' (end [3 1] p.jon)) + (ship jon) + id+(cid jon) + :: +ta: tag .wit parsed json with .mas + :: + ++ ta + |* [mas=@tas wit=fist] + |= jon=json + [mas (wit jon)] + :: + ++ value + ^- $-(json value:c) + |= jon=json + ?~ jon ~ + =/ [type=@tas val=json] + %. jon + (ot type+(se %tas) value+json ~) + ?+ type !! + %text %. val (ta %text so) + %numb %. val (ta %numb ni) + %date %. val (ta %date (se %da)) + %tint %. val + %+ ta %tint + %+ cu + |=(s=@t (slav %ux (cat 3 '0x' s))) + so + %ship %. val (ta %ship ship) + %look %. val (ta %look so) + %flag %. val (ta %flag flag:dejs:g) + %set %. val (ta %set (as value)) + == + :: + ++ contact + ^- $-(json contact:c) + (om value) + :: + ++ action + ^- $-(json action:c) + %- of + :~ anon+ul + self+contact + page+(ot kip+kip contact+contact ~) + edit+(ot kip+kip contact+contact ~) + wipe+(ar kip) + meet+(ar ship) + drop+(ar ship) + snub+(ar ship) + == + -- +-- diff --git a/app/lib/default-agent.hoon b/app/lib/default-agent.hoon new file mode 100644 index 0000000..319bf95 --- /dev/null +++ b/app/lib/default-agent.hoon @@ -0,0 +1,69 @@ +/+ skeleton +|* [agent=* help=*] +?: ?=(%& help) + ~| %default-agent-helpfully-crashing + skeleton +|_ =bowl:gall +++ on-init + `agent +:: +++ on-save + !>(~) +:: +++ on-load + |= old-state=vase + `agent +:: +++ on-poke + |= =cage + ~| "unexpected poke to {} with mark {}" + !! +:: +++ on-watch + |= =path + ~| "unexpected subscription to {} on path {}" + !! +:: +++ on-leave + |= path + `agent +:: +++ on-peek + |= =path + ~| "unexpected scry into {} on path {}" + !! +:: +++ on-agent + |= [=wire =sign:agent:gall] + ^- (quip card:agent:gall _agent) + ?- -.sign + %poke-ack + ?~ p.sign + `agent + %- (slog leaf+"poke failed from {} on wire {}" u.p.sign) + `agent + :: + %watch-ack + ?~ p.sign + `agent + =/ =tank leaf+"subscribe failed from {} on wire {}" + %- (slog tank u.p.sign) + `agent + :: + %kick `agent + %fact + ~| "unexpected subscription update to {} on wire {}" + ~| "with mark {}" + !! + == +:: +++ on-arvo + |= [=wire =sign-arvo] + ~| "unexpected system response {<-.sign-arvo>} to {} on wire {}" + !! +:: +++ on-fail + |= [=term =tang] + %- (slog leaf+"error in {}" >term< tang) + `agent +-- diff --git a/app/lib/json/nostr.hoon b/app/lib/json/nostr.hoon index 9c90c9d..13ea7f8 100644 --- a/app/lib/json/nostr.hoon +++ b/app/lib/json/nostr.hoon @@ -39,11 +39,11 @@ == ++ wevent |= w=wevent:sur ^- json - %: pairs - relays+a+(turn relays.w cord:en:common) - event+(event +.w) - ~ - == + =/ base (event +.w) + ?> ?=(%o -.base) + =. p.base (~(put by p.base) 'relays' [%a (turn relays.w cord:en:common)]) + base + ++ event =/ nostr=? .n |= e=event:sur ^- json diff --git a/app/lib/kol.hoon b/app/lib/kol.hoon new file mode 100644 index 0000000..448c70f --- /dev/null +++ b/app/lib/kol.hoon @@ -0,0 +1,35 @@ +:: kol: key-unique, value-ordered lists +:: +|* comp=$-([* *] ?) +=| a=(list (pair)) :: key-value list +|@ +++ put + |* b=(pair) + =+ c=| :: inserted b + =+ d=| :: dropped old + |- ^+ a + ?: &(d c) a :: done + ?~ a ?:(c ~ [b]~) :: end + ?: =(p.i.a p.b) $(a t.a, d &) :: drop old + ?: |(c (comp q.i.a q.b)) [i.a $(a t.a)] :: next + [b i.a $(a t.a, c &)] :: insert new +:: +++ del + |* b=* + |- ^+ a + ?~ a ~ + ?: =(p.i.a b) t.a + [i.a $(a t.a)] +:: +++ top + |* b=* + |- ^+ a + ?~ a ~ + ?:((comp q.i.a b) [i.a $(a t.a)] ~) +:: +++ bot + |* b=* + |- ^+ a + ?~ a ~ + ?:((comp q.i.a b) $(a t.a) a) +-- diff --git a/app/lib/mutations/nostr.hoon b/app/lib/mutations/nostr.hoon index 4cca0a7..d554dc7 100644 --- a/app/lib/mutations/nostr.hoon +++ b/app/lib/mutations/nostr.hoon @@ -101,7 +101,6 @@ =/ rclient ~(. relay.nclient [wid relay]) |^ =^ cards state - ~& > handle-ws=-.msg ?- -.msg :: This gets returned when we post a message to a relay %ok (handle-ok url.relay +.msg) @@ -142,9 +141,6 @@ |= [sub-id=@t =event:nsur] ^- (quip card _state) :: increment event count in relay state - ~& >> parsing-nostr-event=kind.event - ~& >> sub-id=sub-id - :: ~& > relay-subs=~(key by reqs.relay) =/ req (~(get by reqs.relay) sub-id) ?~ req ~& >>> "sub id not found in relay state" `state @@ -265,13 +261,12 @@ =/ ureq=(unit req-state:nsur) (~(get by reqs.relay) sub-id) ?~ ureq ~& >>> "sub id not found! on eose" `state =/ reqs=req-state:nsur u.ureq - ~& >> eose=reqs - ~& >>> "**************" :: :: if there's a queue we setup the next subscription =^ cards relay ?: (is-feed:evlib filters.reqs) ~& >> "eose on global feed request" + :: TODO don't send all at once, stream in chunks =/ c (update-ui:cardslib [%nostr %feed nostr-feed.state]) =^ mc relay get-profiles:rclient [[c mc] relay] @@ -314,7 +309,7 @@ :: if ongoing request we mark it as backlog received and keep it alive, else we cloe it =^ cards3 relay ?~ ongoing.reqs - ~& >>> closing-relay-sub=[sub-id filters.reqs] + ~& >>> closing-relay-sub=sub-id =/ d (close-sub-req:nclient sub-id wid relay) :_ +.d :~ (send-card:rclient -.d) @@ -323,10 +318,16 @@ =. reqs.relay (~(put by reqs.relay) sub-id reqs) [~ relay] - =/ eose-card (update-ui:cardslib [%nostr %eose sub-id]) - =/ carrds :- eose-card %+ weld cards %+ weld cards2 cards3 - =. relays.state (~(put by relays.state) wid relay) + =/ eose-card (update-ui:cardslib [%nostr %eose sub-id]) + =/ relays-card (update-ui:cardslib [%nostr %relays relays.state]) + =/ cs + :~ eose-card + == + =/ carrds=(list card) + :- eose-card :- relays-card + %+ weld cards %+ weld cards2 cards3 + :_ state carrds -- @@ -337,28 +338,6 @@ (~(put by profiles.state) [%urbit src.bowl] u.prof) :: TODO kinda wanna send it to the UI `state - - - ++ call-relay |= [wid=@ud relay-url=@t ro=relay-order:ui] - ^- (quip card _state) - =/ nclient ~(. nostr-client [state bowl]) - =/ urelay (~(get by relays.state) wid) - ?~ urelay - ~& >>> not-connected-to-relay=wid - (test-reconnection relay-url ro) - :: - =/ relay u.urelay - =/ rclient ~(. relay.nclient [wid relay]) - :: - =/ d=[(list card) _relay] - ?+ -.ro !! - %user (get-user-feed:rclient +.ro) - %thread (get-thread:rclient +.ro) - %sync get-posts:rclient - %prof get-profiles:rclient - == - =. relays.state (~(put by relays.state) wid +.d) - [-.d state] ++ test-reconnection |= [relay-url=@t ro=relay-order:ui] ^- (quip card _state) @@ -374,7 +353,7 @@ ++ relay-get |= [tid=@ta wids=(list @ud) rg=relay-get:ui] ^- (quip card _state) - ~& >> got-tid=tid + ~& >> relay-get=[tid wids rg] =/ nclient ~(. nostr-client [state bowl]) =^ cards state =| css=(list card) @@ -390,12 +369,12 @@ =/ d ?+ -.rg !! - %sync get-posts-ted:rclient + %sync ted:get-posts:rclient == =. reqs.relay (~(put by reqs.relay) sub-id.d rs.d) =. relays.state (~(put by relays.state) wid relay) =/ cs=(list card) - :~ (poke-ui-thread:cards:lib tid sub-id.d) + :~ (poke-ui-thread:cardslib tid sub-id.d) card.d == =/ ncss (weld cs css) @@ -424,15 +403,18 @@ =/ rclient ~(. relay.nclient [wid relay]) :: =/ d=[(list card) _relay] + ~& action=action.rh ?- -.action.rh %user (get-user-feed:rclient +.action.rh) %thread (get-thread:rclient +.action.rh) - %sync get-posts:rclient + %sync sub:get-posts:rclient %prof get-profiles:rclient == =. relays.state (~(put by relays.state) wid +.d) $(wids t.wids, css (weld css -.d)) - [cards state] + =/ relays-card (update-ui:cardslib [%nostr %relays relays.state]) + =/ cs [relays-card cards] + [cs state] ++ send-post |= [wids=(list @ud) host=@p id=@da] diff --git a/app/lib/nostr/client.hoon b/app/lib/nostr/client.hoon index e10cb08..02368bc 100644 --- a/app/lib/nostr/client.hoon +++ b/app/lib/nostr/client.hoon @@ -1,5 +1,5 @@ /- sur=nostrill, nsur=nostr -/+ js=json-nostr, sr=sortug, seq, nostr-keys, constants, server, ws=websockets, evlib=nostr-events +/+ js=json-nostr, sr=sortug, seq, nostr-keys, constants, server, ws=websockets, evlib=nostr-events, lib=nostrill /= web /web/router |_ [=state:sur =bowl:gall] +$ card card:agent:gall @@ -42,9 +42,9 @@ ++ set-req |= [relay=relay-stats:nsur name=@t fs=(list filter:nsur) ongoing=(unit ?) chunked=(list filter:nsur)] - ^- [client-msg:nsur relay-stats:nsur] + ^- [[%req relay-req:nsur] relay-stats:nsur] =/ sub-id (gen-sub-id:nostr-keys eny.bowl) - =/ msg=client-msg:nsur [%req sub-id fs] + =/ msg [%req sub-id fs] =/ req=req-state:nsur [name fs 0 ongoing chunked] =. reqs.relay (~(put by reqs.relay) sub-id req) [msg relay] @@ -106,43 +106,39 @@ =/ wmsg (req-to-msg req) (give-ws-payload-client:ws wid wmsg) :: - :: TODO temp, replace with one below - ++ get-posts-ted - ^- [sub-id=@t rs=req-state:nsur =card] - =/ kinds (silt ~[1]) - =/ last-week (sub now.bowl ~m1) - :: =/ since (to-unix-secs:jikan:sr last-week) - =/ =filter:nsur [~ ~ `kinds ~ `last-week ~ ~] - =/ req-name 'timeline' - =/ filters ~[filter] - =/ req (build-req filters) - =/ sub-id=@t +<.req - =/ card (send-card req) - =/ rs=req-state:nsur (init-req req-name filters ~ ~) - [sub-id rs card] - - ++ get-posts =/ kinds (silt ~[1]) - =/ last-week (sub now.bowl ~h1) - :: =/ since (to-unix-secs:jikan:sr last-week) - =/ =filter:nsur [~ ~ `kinds ~ `last-week ~ ~] + =/ from:: look in settings agent + ?. .^(? %gx /(scot %p our.bowl)/settings/(scot %da now.bowl)/has-entry/nostrill/relay/backlog-size/noun) + ~h1 + =/ res .^(* %gx /(scot %p our.bowl)/settings/(scot %da now.bowl)/entry/nostrill/relay/backlog-size/noun) + =/ hours ?> ?=(@ +>.res) +>.res + (mul hours ~h1) + ~& >>> from=from + :: =/ since (sub now.bowl from) + =/ since (sub now.bowl ~m10) + + + =/ =filter:nsur [~ ~ `kinds ~ `since ~ ~] + =/ filters ~[filter] =/ req-name 'timeline' - =^ req relay (set-req relay req-name ~[filter] ~ ~) - :_ relay - :~ (send-card req) - == - :: ++ get-posts - :: =/ kinds (silt ~[1]) - :: =/ last-week (sub now.bowl ~d7) - :: :: =/ since (to-unix-secs:jikan:sr last-week) - :: =/ =filter:nsur [~ ~ `kinds ~ `last-week ~ ~] - :: =/ req-name 'timeline' - :: =^ req relay (set-req relay req-name ~[filter] `.n ~) - :: :_ relay - :: :~ (send-card req) - :: == - :: + |% + ++ ted + ^- [sub-id=@t rs=req-state:nsur =card] + =/ req (build-req filters) + =/ sub-id=@t sub-id.req + ~& get-posts-ted=sub-id + =/ card (send-card req) + =/ rs=req-state:nsur (init-req req-name filters ~ ~) + [sub-id rs card] + ++ sub + =^ req relay (set-req relay req-name ~[filter] ~ ~) + ~& get-posts-sub=req + :_ relay + :~ (send-card req) + == + -- + ++ get-user-feed |= pubkey=@ux =/ kinds (silt ~[1]) @@ -208,7 +204,6 @@ :: ++ get-profile |= pubkey=@ux =/ kinds (silt ~[0]) - :: =/ since (to-unix-secs:jikan:sr last-week) =/ pubkeys (silt ~[pubkey]) =/ =filter:nsur [~ `pubkeys `kinds ~ ~ ~ ~] =/ req-name 'user profile fetch' @@ -246,6 +241,9 @@ =/ pubkeys=(set @ux) (silt i.chunks) =/ =filter:nsur [~ `pubkeys `kinds ~ ~ ~ ~] (set-req relay req-name ~[filter] ~ queue) + + =/ sub-id=@t sub-id.req + ~& >>> profiles-sub-id=sub-id :_ relay :~ (send-card req) == diff --git a/app/lib/nostrill.hoon b/app/lib/nostrill.hoon index 02df8e9..cc079ef 100644 --- a/app/lib/nostrill.hoon +++ b/app/lib/nostrill.hoon @@ -82,6 +82,7 @@ [%pass /ted-res/[ta-now] %agent [our.bowl %spider] %poke %spider-input !>([tid %noun !>(body)])] ++ poke-ui-thread |= [tid=@ta sub-id=@t] ^- card:agent:gall + ~& >>> poke-ui-ted=tid =/ ta-now (scot %ud `@`now.bowl) =/ payload=ted:ui [%res sub-id] [%pass /ted-res/[ta-now] %agent [our.bowl %spider] %poke %spider-input !>([tid %nostrill-ted !>(payload)])] diff --git a/app/lib/skeleton.hoon b/app/lib/skeleton.hoon new file mode 100644 index 0000000..982c371 --- /dev/null +++ b/app/lib/skeleton.hoon @@ -0,0 +1,51 @@ +:: Similar to default-agent except crashes everywhere +^- agent:gall +|_ bowl:gall +++ on-init + ^- (quip card:agent:gall agent:gall) + !! +:: +++ on-save + ^- vase + !! +:: +++ on-load + |~ old-state=vase + ^- (quip card:agent:gall agent:gall) + !! +:: +++ on-poke + |~ in-poke-data=cage + ^- (quip card:agent:gall agent:gall) + !! +:: +++ on-watch + |~ path + ^- (quip card:agent:gall agent:gall) + !! +:: +++ on-leave + |~ path + ^- (quip card:agent:gall agent:gall) + !! +:: +++ on-peek + |~ path + ^- (unit (unit cage)) + !! +:: +++ on-agent + |~ [wire sign:agent:gall] + ^- (quip card:agent:gall agent:gall) + !! +:: +++ on-arvo + |~ [wire =sign-arvo] + ^- (quip card:agent:gall agent:gall) + !! +:: +++ on-fail + |~ [term tang] + ^- (quip card:agent:gall agent:gall) + !! +-- diff --git a/app/lib/unicode.hoon b/app/lib/unicode.hoon new file mode 100644 index 0000000..d7aafda --- /dev/null +++ b/app/lib/unicode.hoon @@ -0,0 +1,956 @@ +:: unicode utilities +:: +|% +:: |confusables: unicode confusables characters +:: +:: unicode is a standard that encompasses all world scripts. some +:: of these scripts share characters, which thus gives rise to the +:: problem of semantically distinct characters appearing the same. +:: this is important if the program relies on these semantic +:: differences for operation. +:: +:: this library currently supports the following classes of +:: confusables, following which can be queried through +:: - latin alphabet confusables +:: - ascii confusables +:: - latin full-width confusables +:: +:: the lists are based on +:: https://www.unicode.org/Public/security/revision-03/confusablesSummary.txt, +:: which can be queried at +:: https://util.unicode.org/UnicodeJsps/confusables.jsp. +:: +:: utility arms are provided that aid in normalizing an utf-8 string, +:: using latin letters and ascii characters as representatives. +:: +++ confusable + |% + :: +con-sig: confusable with '~' + ++ con-sig ^~ + ^- (set @t) + %- silt + :~ '῀' + '⁓' + '˜' + '∼' + '~' + == + :: +con-hep: confusable '-' + ++ con-hep + ^~ + ^- (set @t) + %- silt + :~ '‐' + '‑' + '‒' + '−' + '–' + '⁃' + '۔' + '➖' + '˗' + '﹘' + 'Ⲻ' + '-' + == + + :: +con-a: confusable with 'a' + ++ con-a + ^~ + ^- (set @t) + %- silt + :~ '𝝰' + 'a' + 'a' + '𝑎' + '𝗮' + '𝕒' + '𝖆' + '𝓪' + '𝚊' + '𝞪' + 'а' + 'ɑ' + 'α' + '𝔞' + '𝒂' + '𝘢' + '𝛂' + '⍺' + '𝒶' + '𝙖' + '𝜶' + '𝛼' + '𝐚' + '𝖺' + == + :: +con-b: confusable with 'b' + ++ con-b + ^~ + ^- (set @t) + %- silt + :~ '𝑏' + '𝗯' + 'b' + 'Ƅ' + '𝕓' + '𝖇' + 'Ь' + '𝓫' + '𝚋' + 'Ꮟ' + 'ᖯ' + '𝔟' + 'ᑲ' + '𝒃' + '𝘣' + '𝒷' + '𝙗' + '𝐛' + '𝖻' + == + :: +con-c: confusable with 'c' + ++ con-c + ^~ + ^- (set @t) + %- silt + :~ '𝑐' + '𝗰' + 'с' + 'c' + 'c' + '𝕔' + 'ᴄ' + 'ⲥ' + '𐐽' + '𝖈' + '𝓬' + '𝚌' + 'ꮯ' + '𝔠' + 'ϲ' + '𝒄' + '𝘤' + '𝒸' + '𝙘' + '𝐜' + '𝖼' + 'ⅽ' + == + :: +con-d: confusable with 'd' + ++ con-d + ^~ + ^- (set @t) + %- silt + :~ 'ԁ' + '𝓭' + '𝚍' + 'd' + 'ⅆ' + '𝑑' + '𝗱' + 'Ꮷ' + '𝒅' + '𝘥' + '𝖉' + 'ᑯ' + 'ꓒ' + '𝐝' + '𝖽' + '𝔡' + '𝕕' + 'ⅾ' + '𝒹' + '𝙙' + == + :: +con-e: confusable with 'e' + ++ con-e + ^~ + ^- (set @t) + %- silt + :~ '𝓮' + '𝚎' + 'e' + 'e' + '𝑒' + '𝗲' + 'ⅇ' + '𝒆' + '𝘦' + '℮' + '𝖊' + 'ℯ' + '𝐞' + '𝖾' + 'ꬲ' + 'е' + '𝔢' + '𝕖' + 'ҽ' + '𝙚' + == + :: +con-f: confusable with 'f' + ++ con-f + ^~ + ^- (set @t) + %- silt + :~ '𝓯' + '𝚏' + 'ք' + '𝑓' + '𝗳' + 'f' + '𝒇' + '𝘧' + '𝖋' + '𝐟' + '𝖿' + '𝔣' + 'ꬵ' + '𝕗' + 'ꞙ' + '𝒻' + '𝙛' + 'ẝ' + 'ſ' + == + :: +con-g: confusable with 'g' + ++ con-g + ^~ + ^- (set @t) + %- silt + :~ '𝓰' + '𝚐' + 'ɡ' + 'ց' + 'ᶃ' + '𝑔' + '𝗴' + 'g' + 'g' + '𝒈' + '𝘨' + 'ℊ' + '𝖌' + 'ƍ' + '𝐠' + '𝗀' + '𝔤' + '𝕘' + '𝙜' + == + :: +con-h: confusable with 'h' + ++ con-h + ^~ + ^- (set @t) + %- silt + :~ 'Ꮒ' + '𝖍' + '𝓱' + '𝚑' + 'h' + 'h' + '𝔥' + 'ℎ' + '𝒉' + '𝘩' + 'հ' + '𝒽' + '𝙝' + '𝐡' + '𝗁' + '𝗵' + 'һ' + '𝕙' + == + :: +con-i: confusable with 'i' + ++ con-i + ^~ + ^- (set @t) + %- silt + :~ '𝓲' + '𝞲' + 'ꙇ' + 'ⅈ' + 'i' + '𝔦' + '𝘪' + 'ӏ' + '𝙞' + '𝚤' + '𝐢' + 'і' + '𝑖' + '˛' + '𝕚' + '𝖎' + 'Ꭵ' + '𝚒' + '𑣃' + 'i' + 'ɩ' + 'ɪ' + '𝒊' + '𝛊' + 'ⅰ' + 'ı' + '𝒾' + '𝜾' + '⍳' + '𝜄' + 'ꭵ' + '𝗂' + '𝝸' + 'ℹ' + 'ι' + '𝗶' + 'ͺ' + == + :: +con-j: confusable with 'j' + ++ con-j + ^~ + ^- (set @t) + %- silt + :~ '𝖏' + '𝓳' + '𝚓' + 'ⅉ' + '𝔧' + 'j' + 'j' + '𝒋' + '𝘫' + '𝒿' + '𝙟' + 'ϳ' + '𝐣' + '𝗃' + 'ј' + '𝑗' + '𝗷' + '𝕛' + == + :: +con-k: confusable with 'k' + ++ con-k + ^~ + ^- (set @t) + %- silt + :~ '𝖐' + '𝓴' + '𝚔' + '𝔨' + 'k' + '𝒌' + '𝘬' + '𝓀' + '𝙠' + '𝐤' + '𝗄' + '𝑘' + '𝗸' + '𝕜' + == + :: +con-l: confusable with 'l' + ++ con-l + ^~ + ^- (set @t) + %- silt + :~ '𝚰' + '𝘭' + 'І' + '𝖨' + '𝐥' + 'ﺍ' + 'ﺎ' + '𝔩' + 'ℐ' + 'ℑ' + '𐊊' + 'Ⲓ' + '𐌉' + 'ℓ' + '𝜤' + 'Ɩ' + '𝞘' + 'Ι' + '𝚕' + '𝟏' + '∣' + 'ا' + 'I' + '𝗅' + '𝕀' + '1' + '𝙄' + '𝓁' + '𐌠' + '𝐼' + '𞸀' + '𞺀' + '׀' + '𝑰' + 'ǀ' + 'Ӏ' + 'ᛁ' + '𝟭' + '𝕴' + 'I' + 'ߊ' + 'l' + '𝛪' + 'ⵏ' + '𝝞' + '𝕝' + '𝟣' + 'ו' + '𞣇' + '𝙡' + '𝓘' + '𝗜' + '𝟙' + '𝑙' + 'ן' + 'Ⅰ' + '𝘐' + '١' + '𝒍' + '𝖑' + '│' + '🯱' + '𝐈' + 'l' + '۱' + 'ꓲ' + '𖼨' + '𝙸' + '𝟷' + '𝓵' + '|' + 'ⅼ' + '⏽' + '𝗹' + == + :: +con-m: confusable with 'm' + ++ con-m + ^~ + ^- (set @t) + %- silt + :~ '𑜀' + '𝒎' + '𝘮' + '𑣣' + '𝖒' + '𝐦' + '𝗆' + 'm' + '𝔪' + '𝕞' + '𝓂' + '𝙢' + '𝓶' + '𝚖' + 'rn' + '𝑚' + '𝗺' + 'ⅿ' + == + :: +con-n: confusable with 'n' + ++ con-n + ^~ + ^- (set @t) + %- silt + :~ '𝒏' + '𝘯' + '𝖓' + '𝐧' + '𝗇' + '𝔫' + 'n' + '𝕟' + '𝓃' + '𝙣' + 'ո' + '𝓷' + '𝚗' + 'ռ' + '𝑛' + '𝗻' + == + :: +con-o: confusable with 'o' + ++ con-o + ^~ + ^- (set @t) + %- silt + :~ '𝘰' + 'ం' + 'ಂ' + 'ം' + 'ං' + 'օ' + '𝐨' + '𑣗' + '𝔬' + 'ᴏ' + 'ᴑ' + '𞹤' + '𐓪' + '𝚘' + '𑣈' + 'ဝ' + 'ⲟ' + '𝛐' + 'ഠ' + '𝛔' + 'ﮦ' + 'ﮧ' + '𝗈' + '𝝈' + 'ﮨ' + 'ﮩ' + 'ﮪ' + 'ﮫ' + 'ﮬ' + 'ﮭ' + '𞺄' + 'ℴ' + '𝝄' + '𝞸' + '𝞼' + 'ꬽ' + 'о' + 'ھ' + 'ο' + '၀' + 'ہ' + 'σ' + 'ه' + 'o' + '๐' + '໐' + '𝕠' + '𐐬' + '𞸤' + '𝙤' + 'ە' + '𝑜' + '𝒐' + 'ס' + '𝜎' + '𝖔' + '٥' + '०' + '੦' + '૦' + '௦' + '౦' + '೦' + '൦' + 'ﻩ' + 'ﻪ' + 'ﻫ' + 'ﻬ' + '𝜊' + 'o' + '𝝾' + '۵' + '𝞂' + '𝓸' + '𝗼' + 'ჿ' + == + :: +con-p: confusable with 'p' + ++ con-p + ^~ + ^- (set @t) + %- silt + :~ 'р' + 'ρ' + '𝔭' + '𝘱' + '𝙥' + '𝐩' + 'p' + '𝛠' + '𝑝' + '𝕡' + '𝖕' + '𝜚' + '𝚙' + '𝞎' + 'ⲣ' + '𝝔' + '𝛒' + '𝒑' + '𝟈' + '𝝆' + '𝓅' + '𝜌' + '𝗉' + 'p' + '𝞀' + 'ϱ' + '𝗽' + '⍴' + '𝞺' + '𝓹' + == + :: +con-q: confusable with 'q' + ++ con-q + ^~ + ^- (set @t) + %- silt + :~ '𝔮' + 'գ' + '𝒒' + '𝘲' + 'զ' + '𝓆' + '𝙦' + '𝐪' + '𝗊' + 'q' + '𝑞' + '𝗾' + '𝕢' + '𝖖' + 'ԛ' + '𝓺' + '𝚚' + == + :: +con-r: confusable with 'r' + ++ con-r + ^~ + ^- (set @t) + %- silt + :~ '𝔯' + 'ꮁ' + '𝒓' + '𝘳' + 'ⲅ' + 'ᴦ' + 'ꭇ' + 'ꭈ' + '𝓇' + '𝙧' + '𝐫' + '𝗋' + '𝑟' + '𝗿' + 'r' + 'г' + '𝕣' + '𝖗' + '𝓻' + '𝚛' + == + :: +con-s: confusable with 's' + ++ con-s + ^~ + ^- (set @t) + %- silt + :~ '𝔰' + '𑣁' + '𝒔' + '𝘴' + '𝓈' + '𝙨' + 'ꮪ' + '𝐬' + '𝗌' + '𝑠' + '𝘀' + 'ꜱ' + 's' + 's' + '𝕤' + 'ѕ' + '𝖘' + '𝓼' + '𝚜' + '𐑈' + 'ƽ' + == + :: +con-t: confusable with 't' + ++ con-t + ^~ + ^- (set @t) + %- silt + :~ '𝐭' + '𝗍' + '𝔱' + '𝕥' + '𝓉' + '𝙩' + '𝓽' + '𝚝' + 't' + '𝑡' + '𝘁' + '𝒕' + '𝘵' + '𝖙' + == + :: +con-u: confusable with 'u' + ++ con-u + ^~ + ^- (set @t) + %- silt + :~ '𝐮' + 'υ' + '𝔲' + '𑣘' + '𝕦' + 'ʋ' + '𝙪' + 'ꭎ' + '𐓶' + '𝚞' + 'ꭒ' + '𝑢' + '𝒖' + '𝛖' + 'ᴜ' + '𝖚' + 'ꞟ' + '𝜐' + '𝗎' + '𝓊' + '𝝊' + '𝓾' + '𝞾' + '𝞄' + 'u' + '𝘂' + '𝘶' + 'ս' + == + :: +con-v: confusable with 'v' + ++ con-v + ^~ + ^- (set @t) + %- silt + :~ '⋁' + '𝐯' + '𝔳' + '𝕧' + '𝙫' + '𝚟' + '𝑣' + 'v' + 'ט' + '𝒗' + '𝖛' + 'ᴠ' + '𝗏' + '𑣀' + '𝛎' + '∨' + '𝜈' + 'ꮩ' + '𝓋' + '𝓿' + 'ⅴ' + '𝘃' + 'ѵ' + 'v' + '𝝂' + '𝘷' + '𝞶' + '𑜆' + '𝝼' + 'ν' + == + :: +con-w: confusable with 'w' + ++ con-w + ^~ + ^- (set @t) + %- silt + :~ '𝐰' + '𝗐' + 'ᴡ' + 'ѡ' + 'ա' + 'ꮃ' + '𝔴' + '𝕨' + '𝓌' + '𝙬' + 'ɯ' + '𝔀' + '𝚠' + '𑜏' + '𑜎' + '𝑤' + '𝘄' + 'w' + '𝒘' + '𝘸' + '𝖜' + 'ԝ' + '𑜊' + == + :: +con-x: confusable with 'x' + ++ con-x + ^~ + ^- (set @t) + %- silt + :~ 'ᕁ' + '𝓍' + '𝙭' + 'х' + '𝐱' + '𝗑' + '⤫' + '𝑥' + '𝘅' + '⤬' + '᙮' + '⨯' + '𝕩' + '𝖝' + '×' + '𝔁' + '𝚡' + 'x' + 'x' + 'ⅹ' + '𝔵' + 'ᕽ' + '𝒙' + '𝘹' + == + :: +con-y: confusable with 'y' + ++ con-y + ^~ + ^- (set @t) + %- silt + :~ '𝙮' + 'у' + '𝐲' + '𝝲' + '𝑦' + 'ᶌ' + '𝞬' + '𑣜' + '𝕪' + 'ʏ' + '𝖞' + '𝚢' + 'y' + 'ꭚ' + '𝒚' + '𝓎' + 'ɣ' + '𝗒' + 'ყ' + '𝘆' + 'ү' + '𝛾' + 'γ' + '𝛄' + '𝔂' + '𝜸' + 'y' + '𝔶' + 'ℽ' + '𝘺' + 'ỿ' + == + :: +con-z: confusable with 'z' + ++ con-z + ^~ + ^- (set @t) + %- silt + :~ '𝓏' + '𝙯' + 'ᴢ' + '𝐳' + '𝗓' + '𑣄' + '𝑧' + '𝘇' + '𝕫' + '𝖟' + 'ꮓ' + '𝔃' + '𝚣' + '𝔷' + 'z' + '𝒛' + '𝘻' + == + :: con-latin: latin confusables + ++ con-latin + ^~ + ^- (list (pair @t (set @t))) + :~ ['a' con-a] + ['b' con-b] + ['c' con-c] + ['d' con-d] + ['e' con-e] + ['f' con-f] + ['g' con-g] + ['h' con-h] + ['i' con-i] + ['j' con-j] + ['k' con-k] + ['l' con-l] + ['m' con-m] + ['n' con-n] + ['o' con-o] + ['p' con-p] + ['q' con-q] + ['r' con-r] + ['s' con-s] + ['t' con-t] + ['u' con-u] + ['v' con-v] + ['w' con-w] + ['x' con-x] + ['y' con-y] + ['z' con-z] + == + :: +norm-latin-wide: normalize full-width latin character + :: + ++ norm-latin-wide + |= =@t + ^- @t + =+ c=(taft t) + :: ascii wide characters are respresented in the + :: range U+FF01 - U+FF5E. + ?. &((gte c 0xff01) (lte c 0xff5e)) t + (add (sub c 0xff00) 0x20) + :: +norm-latin: normalize latin character + :: + ++ norm-latin + |= c=@t + ^- @t + :: leave ascii as is + ?: (lte c 0x7f) c + =/ conf=(list (pair @t (set @t))) + con-latin + |- + ?~ conf c + ?: (~(has in q.i.conf) c) + p.i.conf + $(conf t.conf) + :: +norm-p: normalize a possible @p string + :: + ++ norm-p + |= txt=@t + ^- @t + =| norm=@t + |- + ?: =('' txt) norm + =+ len=(teff txt) + =+ c=(end [3 len] txt) + :: leave ascii as is + ?: (lte c 0x7f) + $(norm (cat 3 norm c), txt (rsh [3 len] txt)) + =/ n=@t + ?: (~(has in con-sig) c) '~' + ?: (~(has in con-hep) c) '-' + (norm-latin-wide (norm-latin c)) + $(norm (cat 3 norm n), txt (rsh [3 len] txt)) + -- +-- diff --git a/app/mar/contact/action-0.hoon b/app/mar/contact/action-0.hoon new file mode 100644 index 0000000..8ea2b57 --- /dev/null +++ b/app/mar/contact/action-0.hoon @@ -0,0 +1,15 @@ +/- c=contacts, legacy=contacts-0 +/+ j=contacts-json-0 +|_ action=action-0:legacy +++ grad %noun +++ grow + |% + ++ noun action + ++ json (action:enjs:j action) + -- +++ grab + |% + ++ noun action-0:legacy + ++ json action:dejs:j + -- +-- diff --git a/app/mar/contact/action-1.hoon b/app/mar/contact/action-1.hoon new file mode 100644 index 0000000..4525792 --- /dev/null +++ b/app/mar/contact/action-1.hoon @@ -0,0 +1,14 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ action=action:c +++ grad %noun +++ grow + |% + ++ noun action + -- +++ grab + |% + ++ noun action:c + ++ json action:dejs:j + -- +-- diff --git a/app/mar/contact/action.hoon b/app/mar/contact/action.hoon new file mode 100644 index 0000000..f602042 --- /dev/null +++ b/app/mar/contact/action.hoon @@ -0,0 +1,2 @@ +/= mark /mar/contact/action-0 +mark diff --git a/app/mar/contact/book-0.hoon b/app/mar/contact/book-0.hoon new file mode 100644 index 0000000..2de84aa --- /dev/null +++ b/app/mar/contact/book-0.hoon @@ -0,0 +1,14 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ book=book:c +++ grad %noun +++ grow + |% + ++ noun book + ++ json (book:enjs:j book) + -- +++ grab + |% + ++ noun book:c + -- +-- diff --git a/app/mar/contact/book.hoon b/app/mar/contact/book.hoon new file mode 100644 index 0000000..2de84aa --- /dev/null +++ b/app/mar/contact/book.hoon @@ -0,0 +1,14 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ book=book:c +++ grad %noun +++ grow + |% + ++ noun book + ++ json (book:enjs:j book) + -- +++ grab + |% + ++ noun book:c + -- +-- diff --git a/app/mar/contact/changed-contacts.hoon b/app/mar/contact/changed-contacts.hoon new file mode 100644 index 0000000..ddc5257 --- /dev/null +++ b/app/mar/contact/changed-contacts.hoon @@ -0,0 +1,21 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ dir=(map ship profile:c) +++ grad %noun +++ grow + |% + ++ noun dir + ++ json + %- pairs:enjs:format + %+ turn ~(tap by dir) + |= [who=ship pro=profile:c] + :- (scot %p who) + =+ (contact:enjs:j con.pro) + ?> ?=([%o *] -) + o+(~(put by p) 'since' s+(scot %da wen.pro)) + -- +++ grab + |% + ++ noun (map ship profile:c) + -- +-- diff --git a/app/mar/contact/changed-pages.hoon b/app/mar/contact/changed-pages.hoon new file mode 100644 index 0000000..619b92e --- /dev/null +++ b/app/mar/contact/changed-pages.hoon @@ -0,0 +1,23 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ dir=(map kip:c page=[(unit contact:c) (unit contact:c)]) +++ grad %noun +++ grow + |% + ++ noun dir + ++ json + %- pairs:enjs:format + %+ turn ~(tap by dir) + |= [=kip:c con=(unit contact:c) mod=(unit contact:c)] + ::NOTE shenanigans to extract just the string + :- ?@(kip (scot %p kip) =+((cid:enjs:j +.kip) ?>(?=([%s *] -) p))) + :- %a + :~ ?~(con ~ (contact:enjs:j u.con)) + ?~(mod ~ (contact:enjs:j u.mod)) + == + -- +++ grab + |% + ++ noun (map kip:c (unit page:c)) + -- +-- diff --git a/app/mar/contact/contact-0.hoon b/app/mar/contact/contact-0.hoon new file mode 100644 index 0000000..4e355e8 --- /dev/null +++ b/app/mar/contact/contact-0.hoon @@ -0,0 +1,14 @@ +/- c=contacts, x=contacts-0 +/+ j=contacts-json-0 +|_ contact=contact-0:x +++ grad %noun +++ grow + |% + ++ noun contact + ++ json (contact:enjs:j contact) + -- +++ grab + |% + ++ noun contact-0:x + -- +-- diff --git a/app/mar/contact/contact-1.hoon b/app/mar/contact/contact-1.hoon new file mode 100644 index 0000000..e75a43d --- /dev/null +++ b/app/mar/contact/contact-1.hoon @@ -0,0 +1,16 @@ +/+ c=contacts +/+ j=contacts-json-1 +|_ contact=contact:c +++ grad %noun +++ grow + |% + ++ noun contact + ++ json (contact:enjs:j contact) + -- +++ grab + |% + ++ noun contact:c + ++ json contact:dejs:j + ++ contact contact:from-0:c + -- +-- diff --git a/app/mar/contact/contact.hoon b/app/mar/contact/contact.hoon new file mode 100644 index 0000000..aa4bd1c --- /dev/null +++ b/app/mar/contact/contact.hoon @@ -0,0 +1,3 @@ +/= contact-0 /mar/contact-0 +contact-0 + diff --git a/app/mar/contact/directory-0.hoon b/app/mar/contact/directory-0.hoon new file mode 100644 index 0000000..b7c399c --- /dev/null +++ b/app/mar/contact/directory-0.hoon @@ -0,0 +1,14 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ dir=directory:c +++ grad %noun +++ grow + |% + ++ noun dir + ++ json (directory:enjs:j dir) + -- +++ grab + |% + ++ noun directory:c + -- +-- diff --git a/app/mar/contact/directory.hoon b/app/mar/contact/directory.hoon new file mode 100644 index 0000000..b7c399c --- /dev/null +++ b/app/mar/contact/directory.hoon @@ -0,0 +1,14 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ dir=directory:c +++ grad %noun +++ grow + |% + ++ noun dir + ++ json (directory:enjs:j dir) + -- +++ grab + |% + ++ noun directory:c + -- +-- diff --git a/app/mar/contact/news.hoon b/app/mar/contact/news.hoon new file mode 100644 index 0000000..19f3bb3 --- /dev/null +++ b/app/mar/contact/news.hoon @@ -0,0 +1,14 @@ +/- c=contacts, x=contacts-0 +/+ j=contacts-json-0 +|_ news=news-0:x +++ grad %noun +++ grow + |% + ++ noun news + ++ json (news:enjs:j news) + -- +++ grab + |% + ++ noun news-0:x + -- +-- diff --git a/app/mar/contact/page-0.hoon b/app/mar/contact/page-0.hoon new file mode 100644 index 0000000..ca62844 --- /dev/null +++ b/app/mar/contact/page-0.hoon @@ -0,0 +1,14 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ =page:c +++ grad %noun +++ grow + |% + ++ noun page + ++ json (page:enjs:j page) + -- +++ grab + |% + ++ noun page:c + -- +-- diff --git a/app/mar/contact/page-1.hoon b/app/mar/contact/page-1.hoon new file mode 100644 index 0000000..ca62844 --- /dev/null +++ b/app/mar/contact/page-1.hoon @@ -0,0 +1,14 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ =page:c +++ grad %noun +++ grow + |% + ++ noun page + ++ json (page:enjs:j page) + -- +++ grab + |% + ++ noun page:c + -- +-- diff --git a/app/mar/contact/page.hoon b/app/mar/contact/page.hoon new file mode 100644 index 0000000..ca62844 --- /dev/null +++ b/app/mar/contact/page.hoon @@ -0,0 +1,14 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ =page:c +++ grad %noun +++ grow + |% + ++ noun page + ++ json (page:enjs:j page) + -- +++ grab + |% + ++ noun page:c + -- +-- diff --git a/app/mar/contact/response-0.hoon b/app/mar/contact/response-0.hoon new file mode 100644 index 0000000..92c2968 --- /dev/null +++ b/app/mar/contact/response-0.hoon @@ -0,0 +1,14 @@ +/- c=contacts +/+ j=contacts-json-1 +|_ =response:c +++ grad %noun +++ grow + |% + ++ noun response + ++ json (response:enjs:j response) + -- +++ grab + |% + ++ noun response:c + -- +-- diff --git a/app/mar/contact/rolodex.hoon b/app/mar/contact/rolodex.hoon new file mode 100644 index 0000000..4992264 --- /dev/null +++ b/app/mar/contact/rolodex.hoon @@ -0,0 +1,14 @@ +/- c=contacts, x=contacts-0 +/+ j=contacts-json-0 +|_ rol=rolodex:x +++ grad %noun +++ grow + |% + ++ noun rol + ++ json (rolodex:enjs:j rol) + -- +++ grab + |% + ++ noun rolodex:x + -- +-- diff --git a/app/mar/contact/update-0.hoon b/app/mar/contact/update-0.hoon new file mode 100644 index 0000000..519391b --- /dev/null +++ b/app/mar/contact/update-0.hoon @@ -0,0 +1,12 @@ +/- c=contacts, x=contacts-0 +|_ update=update-0:x +++ grad %noun +++ grow + |% + ++ noun update + -- +++ grab + |% + ++ noun update-0:x + -- +-- diff --git a/app/mar/contact/update-1.hoon b/app/mar/contact/update-1.hoon new file mode 100644 index 0000000..f5d9fc5 --- /dev/null +++ b/app/mar/contact/update-1.hoon @@ -0,0 +1,12 @@ +/- c=contacts +|_ update=update:c +++ grad %noun +++ grow + |% + ++ noun update + -- +++ grab + |% + ++ noun update:c + -- +-- diff --git a/app/mar/contact/update.hoon b/app/mar/contact/update.hoon new file mode 100644 index 0000000..f143c73 --- /dev/null +++ b/app/mar/contact/update.hoon @@ -0,0 +1,2 @@ +/= mark /mar/contact/update-0 +mark diff --git a/app/sur/contacts-0.hoon b/app/sur/contacts-0.hoon new file mode 100644 index 0000000..ef34be2 --- /dev/null +++ b/app/sur/contacts-0.hoon @@ -0,0 +1,82 @@ +|% ++$ flag (pair ship term) ++$ saga + $~ [%lev ~] + $% [%dex ver=@ud] + [%lev ~] + [%chi ~] + == + ++$ contact-0 + $: nickname=@t + bio=@t + status=@t + color=@ux + avatar=(unit @t) + cover=(unit @t) + groups=(set flag) + == +:: ++$ foreign-0 [for=$@(~ profile-0) sag=$@(~ saga-0)] ++$ profile-0 [wen=@da con=$@(~ contact-0)] ++$ rolodex (map ship foreign-0) +:: ++$ saga-0 + $@ $? %want :: subscribing + %fail :: %want failed + %lost :: epic %fail + ~ :: none intended + == + saga +:: ++$ field-0 + $% [%nickname nickname=@t] + [%bio bio=@t] + [%status status=@t] + [%color color=@ux] + [%avatar avatar=(unit @t)] + [%cover cover=(unit @t)] + [%add-group =flag] + [%del-group =flag] + == +:: ++$ action-0 + :: %anon: delete our profile + :: %edit: change our profile + :: %meet: track a peer + :: %heed: follow a peer + :: %drop: discard a peer + :: %snub: unfollow a peer + :: + $% [%anon ~] + [%edit p=(list field-0)] + [%meet p=(list ship)] + [%heed p=(list ship)] + [%drop p=(list ship)] + [%snub p=(list ship)] + == +:: network +:: ++$ update-0 + $% [%full profile-0] + == +:: local +:: ++$ news-0 + [who=ship con=$@(~ contact-0)] +:: +++ get-contact + |= [=bowl:gall who=@p] + => :_ ..get-contact + [who=who our=our.bowl now=now.bowl] + ~+ ^- (unit contact-0) + =/ base=path /(scot %p our)/contacts/(scot %da now) + ?. ~+ .^(? %gu (weld base /$)) + ~ + =+ ~+ .^(rol=rolodex %gx (weld base /all/contact-rolodex)) + ?~ for=(~(get by rol) who) + ~ + ?. ?=([[@ ^] *] u.for) + ~ + `con.for.u.for +-- diff --git a/app/sur/contacts.hoon b/app/sur/contacts.hoon new file mode 100644 index 0000000..a393cfc --- /dev/null +++ b/app/sur/contacts.hoon @@ -0,0 +1,138 @@ +|% +:: ++| %compat +:: +++ okay `epic`1 ++$ flag (pair ship term) ++$ nest (pair term flag) +:: ++| %types +:: $value-type: contact field value type +:: ++$ value-type + $? %text + %numb + %date + %tint + %ship + %look + %flag + %set + == +:: $value: contact field value +:: ++$ value + $+ contact-value + $@ ~ + $% [%text p=@t] + [%numb p=@ud] + [%date p=@da] + :: + :: color + [%tint p=@ux] + [%ship p=ship] + :: + :: picture + [%look p=@ta] + :: + :: group + [%flag p=flag] + :: + :: uniform set + [%set p=$|((set value) unis)] + == +:: +unis: whether set is uniformly typed +:: +++ unis + |= set=(set value) + ^- ? + ?~ set & + =/ typ -.n.set + |- + ?& =(typ -.n.set) + ?~(l.set & $(set l.set)) + ?~(r.set & $(set r.set)) + == +:: $contact: contact data +:: ++$ contact (map @tas value) +:: $profile: contact profile +:: +:: .wen: last updated +:: .con: contact +:: ++$ profile [wen=@da con=contact] +:: $foreign: foreign profile +:: +:: .for: profile +:: .sag: connection status +:: ++$ foreign [for=$@(~ profile) sag=saga] +:: $page: contact page +:: +:: .con: peer contact +:: .mod: user overlay +:: ++$ page [con=contact mod=contact] +:: $cid: contact page id +:: ++$ cid @uvF +:: $kip: contact book key +:: ++$ kip $@(ship [%id cid]) +:: $book: contact book +:: ++$ book (map kip page) +:: $directory: merged contacts +:: ++$ directory (map ship contact) +:: $peers: network peers +:: ++$ peers (map ship foreign) +:: ++$ epic @ud +:: ++$ saga + $? %want :: subscribing + ~ :: none intended + == +:: %anon: delete our profile +:: %self: edit our profile +:: %page: create a new contact page +:: %edit: edit a contact overlay +:: %wipe: delete a contact page +:: %meet: track a peer +:: %drop: discard a peer +:: %snub: unfollow a peer +:: ++$ action + $% [%anon ~] + [%self p=contact] + [%page p=kip q=contact] + [%edit p=kip q=contact] + [%wipe p=(list kip)] + [%meet p=(list ship)] + [%drop p=(list ship)] + [%snub p=(list ship)] + == +:: network update +:: +:: %full: our profile +:: ++$ update + $% [%full profile] + == +:: $response: local update +:: +:: %self: profile update +:: %page: contact page update +:: %wipe: contact page delete +:: %peer: peer update +:: ++$ response + $% [%self con=contact] + [%page =kip con=contact mod=contact] + [%wipe =kip] + [%peer who=ship con=contact] + == +-- diff --git a/app/ted/sync.hoon b/app/ted/sync.hoon index fbacef0..4770b56 100644 --- a/app/ted/sync.hoon +++ b/app/ted/sync.hoon @@ -12,16 +12,16 @@ =/ ureq (de-relay-do:de:jsonlib u.ujon) ?~ ureq ~& bad-json=(en:json:html u.ujon) bail =/ req=relay-handling:ui u.ureq - ~& req=req + ~& >> req=req ;< =bowl:spider bind:m get-bowl:strandio + ~& >> tid=tid.bowl ?. ?=(relay-get:ui action.req) bail =/ t=ted:ui [%req tid.bowl relays.req action.req] ;< ~ bind:m (poke-our:strandio %nostrill %nostrill-ted !>(t)) ;< v=vase bind:m (take-poke:strandio %nostrill-ted) - ~& v=v =/ res !<(ted:ui v) - ~& sub-id-to-ted=res + ~& >>> sync-ted-sub-id-to-ted=res ?. ?=(%res -.res) bail =/ j=json [%s sub-id.res] (pure:m !>(j)) diff --git a/gui/devenv.lock b/gui/devenv.lock index 63e15ea..4a8f02e 100644 --- a/gui/devenv.lock +++ b/gui/devenv.lock @@ -3,10 +3,11 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1770047045, + "lastModified": 1774816935, + "narHash": "sha256-MJ1kZGA/BZ9Xy+lsppMcw19jCf9zB9lmfwmCDhiC7Dg=", "owner": "cachix", "repo": "devenv", - "rev": "5c0090a96b72ef4a1e1aec3e0abd30fc260062ac", + "rev": "a4cf85b989fff0bafe5858c879e5343493a86019", "type": "github" }, "original": { @@ -16,88 +17,49 @@ "type": "github" } }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1767039857, - "owner": "NixOS", - "repo": "flake-compat", - "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "flake-compat", - "type": "github" - } - }, - "git-hooks": { + "nixpkgs": { "inputs": { - "flake-compat": "flake-compat", - "gitignore": "gitignore", - "nixpkgs": [ - "nixpkgs" - ] + "nixpkgs-src": "nixpkgs-src" }, "locked": { - "lastModified": 1769939035, + "lastModified": 1774287239, + "narHash": "sha256-W3krsWcDwYuA3gPWsFA24YAXxOFUL6iIlT6IknAoNSE=", "owner": "cachix", - "repo": "git-hooks.nix", - "rev": "a8ca480175326551d6c4121498316261cbb5b260", + "repo": "devenv-nixpkgs", + "rev": "fa7125ea7f1ae5430010a6e071f68375a39bd24c", "type": "github" }, "original": { "owner": "cachix", - "repo": "git-hooks.nix", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "git-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1762808025, - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", + "ref": "rolling", + "repo": "devenv-nixpkgs", "type": "github" } }, - "nixpkgs": { + "nixpkgs-src": { + "flake": false, "locked": { - "lastModified": 1767052823, - "owner": "cachix", - "repo": "devenv-nixpkgs", - "rev": "538a5124359f0b3d466e1160378c87887e3b51a4", + "lastModified": 1773840656, + "narHash": "sha256-9tpvMGFteZnd3gRQZFlRCohVpqooygFuy9yjuyRL2C0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9cf7092bdd603554bd8b63c216e8943cf9b12512", "type": "github" }, "original": { - "owner": "cachix", - "ref": "rolling", - "repo": "devenv-nixpkgs", + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "devenv": "devenv", - "git-hooks": "git-hooks", - "nixpkgs": "nixpkgs", - "pre-commit-hooks": [ - "git-hooks" - ] + "nixpkgs": "nixpkgs" } } }, "root": "root", "version": 7 -} +} \ No newline at end of file diff --git a/gui/src/components/feed/Global.tsx b/gui/src/components/feed/Global.tsx index 082ae7f..5e7de37 100644 --- a/gui/src/components/feed/Global.tsx +++ b/gui/src/components/feed/Global.tsx @@ -30,7 +30,7 @@ export default function Global() { const relay = relayRef.current; const req = relay.req([{ kinds: [667] }]); for await (const msg of req) { - console.log("relay msg", msg); + // console.log("relay msg", msg); if (msg[0] === "EVENT") { const event = msg[2]; const wevent = { ...event, relays: [GLOBAL_RELAY_URL] }; diff --git a/gui/src/components/nostr/Feed.tsx b/gui/src/components/nostr/Feed.tsx index f2c93ea..b857469 100644 --- a/gui/src/components/nostr/Feed.tsx +++ b/gui/src/components/nostr/Feed.tsx @@ -5,6 +5,7 @@ import { useEffect, useState } from "react"; import Icon from "@/components/Icon"; import toast from "react-hot-toast"; import { Contact, RefreshCw } from "lucide-react"; +import type { RelayReqs, RelayStats } from "@/types/nostrill"; export default function Nostr() { const { nostrFeed, api, relays, lastEose, setEose } = useLocalState((s) => ({ @@ -14,35 +15,61 @@ export default function Nostr() { lastEose: s.lastEose, setEose: s.setEose, })); - const [isSyncing, setIsSyncing] = useState(false); - const [subId, setSubId] = useState(""); + const [fetchingPosts, setFetchingPosts] = useState(""); + const [fetchingProfiles, setFetchingProfiles] = useState(""); const refetch = () => nostrFeed; + console.log({ + eose: lastEose, + posts: fetchingPosts, + profiles: fetchingProfiles, + }); useEffect(() => { - if (!lastEose || !subId) return; - if (lastEose === subId) { - setIsSyncing(false); - setEose(""); + if (!lastEose) return; + if (lastEose === fetchingPosts) { + setFetchingPosts(""); toast.success("Nostr feed sync complete"); + setEose(""); + setFetchingProfiles("temp"); + toast.loading("Nostr profile sync initiated"); } - }, [lastEose, subId]); + if (lastEose === fetchingProfiles) { + setFetchingProfiles(""); + setEose(""); + toast.success("Nostr profiles sync complete"); + } + }, [lastEose, fetchingPosts, fetchingProfiles]); + + useEffect(() => { + const allReqs = Object.values(relays).reduce( + (acc: RelayReqs, item: RelayStats) => { + acc = Object.assign(acc, item.reqs); + return acc; + }, + {}, + ); + for (const [subId, req] of Object.entries(allReqs)) { + if (req.name === "timeline" && !req.ongoing) setFetchingPosts(subId); + if (req.name === "user profiles fetch" && !req.ongoing) + setFetchingProfiles(subId); + } + }, [relays]); const handleResync = async () => { if (!api) return; - - // setIsSyncing(true); + toast.loading("Nostr post sync initiated"); + setFetchingPosts("temp"); // TODO make this configurable const rels = Object.values(relays).map((r) => r.wid); try { - const subId = await api.syncRelays(rels); - setSubId(subId); - console.log("syncrelays", subId); - toast.success("Nostr feed sync initiated"); + const res = await api.syncRelays(rels); + // const subId = await api.syncRelaysThread(rels); + // setSubId(subId); + // console.log("syncrelays", subId); + // toast.success("Nostr feed sync initiated"); } catch (error) { toast.error("Failed to sync Nostr feed"); console.error("Sync error:", error); - // } finally { - // setIsSyncing(false); } }; @@ -50,16 +77,18 @@ export default function Nostr() { if (!api) return; const rels = Object.values(relays).map((r) => r.wid); - setIsSyncing(true); + setFetchingProfiles("temp"); try { const subId = await api.nostrProfiles(rels); - setSubId(subId); - toast.success("Nostr profile sync initiated"); + setFetchingProfiles(subId); + toast.loading("Nostr profile sync initiated"); } catch (error) { toast.error("Failed to sync Nostr feed"); console.error("Sync error:", error); } } + console.log({ nostrFeed }); + console.log({ relays }); if (Object.keys(relays).length === 0) return ( @@ -94,10 +123,10 @@ export default function Nostr() {

+ + + diff --git a/gui/src/state/state.ts b/gui/src/state/state.ts index 4685a09..3fda394 100644 --- a/gui/src/state/state.ts +++ b/gui/src/state/state.ts @@ -21,7 +21,7 @@ export type LocalState = { isNew: boolean; api: IO | null; init: () => Promise; - UISettings: Record; + uiSettings: Record; modal: JSX.Element | null; setModal: (modal: JSX.Element | null) => void; composerData: ComposerData | null; @@ -66,8 +66,14 @@ export const useStore = creator((set, get) => ({ // set({ contacts: r.ok }); // } }); - api.scrySettings().then((_r) => { - // console.log("settings", r); + api.scrySettings().then((r) => { + if ("error" in r) { + console.error("error scrying settings", r); + return; + } + const uiSettings = r.ok.desk; + console.log({ uiSettings }); + set({ uiSettings }); }); api.scryStorage().then((r) => { if ("ok" in r) { @@ -174,7 +180,10 @@ export const useStore = creator((set, get) => ({ if ("nostr" in fact) { set({ lastNostrEventTime: Date.now() }); console.log("nostr fact", fact); - // if ("feed" in fact.nostr) set({ nostrFeed: fact.nostr.feed }); + if ("feed" in fact.nostr) { + const nostrFeed = eventsToFc(fact.nostr.feed); + set({ nostrFeed }); + } if ("thread" in fact.nostr) console.log("nostr thread!!!", fact.nostr.thread); if ("relays" in fact.nostr) set({ relays: fact.nostr.relays }); @@ -219,7 +228,7 @@ export const useStore = creator((set, get) => ({ following: new Map(), followers: [], following2: { feed: {}, start: "", end: "" }, - UISettings: {}, + uiSettings: {}, modal: null, setModal: (modal) => set({ modal }), // composer data diff --git a/gui/src/styles/Settings.css b/gui/src/styles/Settings.css index 395a015..a0ced19 100644 --- a/gui/src/styles/Settings.css +++ b/gui/src/styles/Settings.css @@ -287,6 +287,41 @@ cursor: not-allowed; } +/* Backlog Size */ +.backlog-input { + width: 80px; + padding: 10px 12px; + border: 1px solid var(--color-border); + border-radius: 6px; + background: var(--color-background); + color: var(--color-text); + font-size: 14px; +} + +.backlog-input:focus { + outline: none; + border-color: var(--color-primary); +} + +.backlog-unit { + padding: 10px 32px 10px 12px; + border: 1px solid var(--color-border); + border-radius: 6px; + background-color: var(--color-background); + color: var(--color-text); + font-size: 14px; + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; +} + +.backlog-unit:focus { + outline: none; + border-color: var(--color-primary); +} + /* Responsive Design */ @media (max-width: 768px) { .settings-page { diff --git a/gui/src/types/nostrill.ts b/gui/src/types/nostrill.ts index e939d8d..ca9bbf0 100644 --- a/gui/src/types/nostrill.ts +++ b/gui/src/types/nostrill.ts @@ -1,4 +1,5 @@ import type { Wevent as NostrEvent } from "./nostr"; +import type { NostrFilter } from "@nostrify/nostrify"; import type { FC, FullNode, Poast } from "./trill"; export type UserType = { urbit: string } | { nostr: string }; @@ -36,7 +37,16 @@ export type Relays = Record; export type RelayStats = { start: number; wid: number; - reqs: Record; + reqs: RelayReqs; +}; +export type RelayReqs = Record; +export type RelayRequest = { + // subID: string; // actually the key, not to add it + eventsReceived: number; + filters: NostrFilter[]; + name: string; + ongoing: true | false | null; + chunked?: boolean; }; export type PeekRes = { feed: PeekFeedRes } | { thread: PeekThreadRes }; @@ -54,6 +64,7 @@ export type NostrFact = | { user: NostrEvent[] } | { thread: NostrEvent[] } | { event: NostrEvent } + | { sub: { subId: string; type: string } } | { eose: string } | { relays: Relays } | { "sent-post": { host: any; id: string; relays: string[] } } diff --git a/gui/src/types/urbit.ts b/gui/src/types/urbit.ts index 88ad729..dd31721 100644 --- a/gui/src/types/urbit.ts +++ b/gui/src/types/urbit.ts @@ -30,7 +30,8 @@ export type SettingsValue = | { [key: string]: SettingsValue }; export type SettingsMap = Record; -export type SettingsRes = { all: Record }; +// export type SettingsRes = { all: Record }; +export type SettingsRes = { desk: Record }; export type StorageCredentials = { accessKeyId: string; endpoint: string; diff --git a/gui/tile-b.png b/gui/tile-b.png new file mode 100644 index 0000000..ad0bbb4 Binary files /dev/null and b/gui/tile-b.png differ diff --git a/gui/tile-w.png b/gui/tile-w.png new file mode 100644 index 0000000..664f2a3 Binary files /dev/null and b/gui/tile-w.png differ