From 5fa05bc2a9bae76d7747f1ecb448859f26ebd69a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Paraniak?= Date: Mon, 11 Nov 2024 19:22:21 +0800 Subject: [PATCH 1/3] activity: implement contact activity --- desk/app/activity.hoon | 32 ++- desk/app/chat.hoon | 4 +- desk/app/groups.hoon | 10 +- desk/app/profile.hoon | 6 +- desk/app/profile/widgets.hoon | 12 +- desk/lib/activity-json.hoon | 12 +- desk/lib/activity.hoon | 17 +- desk/lib/contacts.hoon | 488 ++++++++++++++++++++++++++++++++++ desk/lib/contacts/json-1.hoon | 151 +++++++++++ desk/lib/notify.hoon | 18 +- desk/sur/activity.hoon | 144 ++++++---- desk/sur/contacts-0.hoon | 75 ++++++ desk/sur/contacts.hoon | 191 +++++++------ 13 files changed, 1002 insertions(+), 158 deletions(-) create mode 100644 desk/lib/contacts.hoon create mode 100644 desk/lib/contacts/json-1.hoon create mode 100644 desk/sur/contacts-0.hoon diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index b336755ce0..ca29a5d9e4 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -24,6 +24,7 @@ :: - thread :: - dm :: - dm-thread +:: - contact :: :: with this structure that means that data flows upwards from the :: leaves to the root, and that we can easily keep the read state @@ -49,7 +50,7 @@ +$ card card:agent:gall :: +$ current-state - $: %7 + $: %8 allowed=notifications-allowed:a =indices:a =activity:a @@ -140,12 +141,21 @@ =? cor ?=(%6 -.old) (emit %pass /adjust-old-default %agent [our.bowl dap.bowl] %poke noun+!>(%adjust-old-default)) =? old ?=(%6 -.old) [%7 +.old] - ?> ?=(%7 -.old) + =? old ?=(%7 -.old) [%8 +.old] + ?> ?=(%8 -.old) =. state old + :: :: insert missing volume defaults to %base + :: :: + :: =/ base-volume + :: (~(gut by volume-settings.state) [%base ~] *volume-map:a) + :: =. volume-settings.state + :: %+ ~(put by volume-settings.state) [%base ~] + :: (~(uni by base-volume) *volume-map:a) =. allowed %all (emit %pass /fix-init-unreads %agent [our.bowl dap.bowl] %poke noun+!>(%fix-init-unreads)) +$ versioned-state - $% state-7 + $% state-8 + state-7 state-6 state-5 state-4 @@ -153,9 +163,17 @@ state-2 state-1 == - +$ state-7 current-state + +$ state-8 current-state + +$ state-7 + $: %7 + allowed=notifications-allowed:v7:old:a + =indices:v7:old:a + =activity:v7:old:a + =volume-settings:v7:old:a + == +$ state-6 _%*(. *state-7 - %6) +$ state-5 _%*(. *state-7 - %5) + :: ++ state-5-to-6 |= old=state-5 ^- state-6 @@ -572,6 +590,7 @@ (lth latest.src-info start) ?= $? %post %reply %dm-post %dm-reply %flag-post %flag-reply %group-ask + %contact == -<.event == @@ -609,6 +628,7 @@ :- (~(put by sources.acc) source src-info(added &)) ?~ top +.acc [(sub limit.acc 1) (snoc happenings.acc [source time.i.top top]) collapsed] + :: +$ out $: sources=(map source:a [latest=time-id:a added=?]) limit=@ud @@ -628,6 +648,7 @@ ?: child.event [~ | acc] ?. ?= $? %post %reply %dm-post %dm-reply %flag-post %flag-reply %group-ask + %contact == -<.event [~ | acc] @@ -771,6 +792,7 @@ =. volume-settings (~(del by volume-settings) source) :: TODO: send notification removals? (give-update [%del source] [%hose ~]) +:: ++ del-event |= [=source:a event=incoming-event:a] ^+ cor @@ -793,6 +815,7 @@ (~(put by out) source (~(got by activity) source)) %- (log |.("sending activity: {}")) (give-update [%activity new-activity] [%hose ~]) +:: ++ add-to-index |= [=source:a =time-id:a =event:a] ^+ cor @@ -800,6 +823,7 @@ =/ new=_stream.index (put:on-event:a stream.index time-id event) (refresh-index source index(stream new)) +:: ++ refresh-index |= [=source:a new=index:a] %- (log |.("refeshing index: {}")) diff --git a/desk/app/chat.hoon b/desk/app/chat.hoon index f98355fa4d..2bfeb35e4f 100644 --- a/desk/app/chat.hoon +++ b/desk/app/chat.hoon @@ -1,7 +1,7 @@ /- c=chat, d=channels, g=groups, u=ui, e=epic, old=chat-2, activity /- meta /- ha=hark -/- contacts +/- contacts-0 /+ default-agent, verb-lib=verb, dbug, neg=negotiate /+ pac=dm /+ utils=channel-utils @@ -1751,7 +1751,7 @@ |= =diff:dm:c =? net.dm &(?=(%inviting net.dm) !from-self) %done =/ =wire /contacts/(scot %p ship) - =/ =cage [act:mar:contacts !>(`action:contacts`[%heed ~[ship]])] + =/ =cage contact-action+!>(`action-0:contacts-0`[%heed ~[ship]]) =. cor (emit %pass wire %agent [our.bowl %contacts] %poke cage) =/ old-unread di-unread =/ had=(unit [=time =writ:c]) diff --git a/desk/app/groups.hoon b/desk/app/groups.hoon index 6d91854ceb..67a87bcff3 100644 --- a/desk/app/groups.hoon +++ b/desk/app/groups.hoon @@ -3,8 +3,8 @@ :: note: all subscriptions are handled by the subscriber library so :: we can have resubscribe loop protection. :: -/- g=groups, zero=groups-0, ha=hark, h=heap, d=channels, c=chat, tac=contacts, - activity +/- g=groups, zero=groups-0, ha=hark, h=heap, d=channels, c=chat, + tac=contacts-0, activity /- meta /- e=epic /+ default-agent, verb, dbug @@ -758,10 +758,10 @@ cor :: %fact - =+ !<(=update:tac q.cage.sign) - ?~ con.update cor + =+ !<(=update-0:tac q.cage.sign) + ?~ con.update-0 cor %- emil - %+ turn ~(tap in groups.con.update) + %+ turn ~(tap in groups.con.update-0) |= =flag:g [%pass /gangs/(scot %p p.flag)/[q.flag]/preview %agent [p.flag dap.bowl] %watch /groups/(scot %p p.flag)/[q.flag]/preview] == diff --git a/desk/app/profile.hoon b/desk/app/profile.hoon index b555c9b039..49ab4f398f 100644 --- a/desk/app/profile.hoon +++ b/desk/app/profile.hoon @@ -5,7 +5,7 @@ :: other apps can poke this agent with widgets of their own, and the user :: can choose which widgets to display on their public page. :: -/- contacts +/- contacts-0 /+ dbug, verb, sigil, hutils=http-utils /= stock-widgets /app/profile/widgets :: @@ -175,8 +175,8 @@ :: ++ render-page ^- manx - =/ ours=(unit contact:contacts) - (get-contact:contacts bowl our.bowl) + =/ ours=(unit contact-0:contacts-0) + (get-contact:contacts-0 bowl our.bowl) |^ ;html ;+ head ;+ body diff --git a/desk/app/profile/widgets.hoon b/desk/app/profile/widgets.hoon index 49f670f10c..3ef0e9d035 100644 --- a/desk/app/profile/widgets.hoon +++ b/desk/app/profile/widgets.hoon @@ -1,20 +1,20 @@ :: profile: construct stock widgets :: -/- contacts +/- contacts-0 /+ sigil :: |= =bowl:gall -=/ ours=(unit contact:contacts) - =, contacts +=/ ours=(unit contact-0:contacts-0) + =, contacts-0 ::NOTE we scry for the full rolodex, because we are not guaranteed to :: have an entry for ourselves, and contacts doesn't expose a "safe" :: (as in crashless) endpoint for checking =+ .^ =rolodex /gx/(scot %p our.bowl)/contacts/(scot %da now.bowl)/all/contact-rolodex == - =/ =foreign (~(gut by rolodex) our.bowl *foreign) - ?: ?=([[@ ^] *] foreign) - `con.for.foreign + =/ =foreign-0 (~(gut by rolodex) our.bowl *foreign-0) + ?: ?=([[@ ^] *] foreign-0) + `con.for.foreign-0 ~ |^ %- ~(gas by *(map term [%0 @t %marl marl])) :~ [%profile %0 'Profile Header' %marl profile-widget] diff --git a/desk/lib/activity-json.hoon b/desk/lib/activity-json.hoon index 7a67ddfb71..bd2ef3a3eb 100644 --- a/desk/lib/activity-json.hoon +++ b/desk/lib/activity-json.hoon @@ -1,5 +1,5 @@ /- a=activity, g=groups, c=chat -/+ gj=groups-json, cj=channel-json +/+ gj=groups-json, cj=channel-json, dj=contacts-json-1 =* z ..zuse |% ++ enjs @@ -70,6 +70,9 @@ '/' (msg-id id.key.s) == + :: + %contact + (cat 3 'contact/' (scot %p who.s)) == :: ++ source @@ -79,6 +82,7 @@ %base ~[base/~] %group ~[group/s/(flag:enjs:gj flag.s)] %dm ~[dm+(whom whom.s)] + %contact ~[contact+(ship who.s)] :: %channel :~ :- %channel @@ -242,6 +246,12 @@ ship+(ship ship.e) roles+a+(turn ~(tap in roles.e) |=(role=sect:g s+role)) == + :: + %contact + %- pairs + :~ who+(ship who.e) + update+(contact:enjs:dj [update.e ~ ~]) + == == :: ++ time-event diff --git a/desk/lib/activity.hoon b/desk/lib/activity.hoon index c1d17645d8..15f782925a 100644 --- a/desk/lib/activity.hoon +++ b/desk/lib/activity.hoon @@ -27,6 +27,7 @@ =/ parent ?- -.source %dm [%base ~] + %contact [%base ~] %group [%base ~] %channel [%group group.source] %dm-thread [%dm whom.source] @@ -37,7 +38,7 @@ ++ get-children :: direct children only |= [=indices:a =source:a] ^- (list source:a) - ?: ?=(?(%thread %dm-thread) -.source) ~ + ?: ?=(?(%thread %dm-thread %contact) -.source) ~ %+ skim ~(tap in ~(key by indices)) |= src=source:a @@ -54,7 +55,8 @@ %~ got by ^~ %- my - :~ [%thread 6] + :~ [%contact 7] + [%thread 6] [%dm-thread 5] [%channel 4] [%group 3] @@ -73,6 +75,7 @@ %dm-thread (get-volumes vs %dm whom.source) %channel (get-volumes vs %group group.source) %thread (get-volumes vs %channel channel.source group.source) + %contact (get-volumes vs %base ~) == :: ++ sort-sources @@ -140,9 +143,9 @@ %chan-init [%channel channel.event group.event] %post [%channel channel.event group.event] %reply [%thread parent.event channel.event group.event] - %dm-invite [%dm whom.event] - %dm-post [%dm whom.event] - %dm-reply [%dm-thread parent.event whom.event] + %dm-invite [%dm whom.event] + %dm-post [%dm whom.event] + %dm-reply [%dm-thread parent.event whom.event] %group-invite [%group group.event] %group-kick [%group group.event] %group-join [%group group.event] @@ -150,6 +153,7 @@ %group-ask [%group group.event] %flag-post [%group group.event] %flag-reply [%group group.event] + %contact [%contact who.event] == :: ++ event-type @@ -170,6 +174,7 @@ ?+ type | %post & %reply & + %contact & %dm-post & %dm-reply & %dm-invite & @@ -367,4 +372,4 @@ == -- -- --- \ No newline at end of file +-- diff --git a/desk/lib/contacts.hoon b/desk/lib/contacts.hoon new file mode 100644 index 0000000000..428f7d3829 --- /dev/null +++ b/desk/lib/contacts.hoon @@ -0,0 +1,488 @@ +/- *contacts, c0=contacts-0 +|% +:: ++| %contact +:: +is-value-empty: is value considered empty +:: +++ is-value-empty + |= val=value + ^- ? + ?+ -.val | + %text =(0 (met 3 p.val)) + %look =(0 (met 3 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 + :: + ++ 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:g + %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 + |= 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) + == + | + ?. (~(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) + == + | + & +:: +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 don) 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:g + 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:g) + %- ~(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/desk/lib/contacts/json-1.hoon b/desk/lib/contacts/json-1.hoon new file mode 100644 index 0000000000..a65fc33c78 --- /dev/null +++ b/desk/lib/contacts/json-1.hoon @@ -0,0 +1,151 @@ +/- c=contacts, g=groups +/+ gj=groups-json +|% +++ 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 + %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+s/(flag:enjs:gj 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:gj) + %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/desk/lib/notify.hoon b/desk/lib/notify.hoon index 9c5805abcc..cdab146c9d 100644 --- a/desk/lib/notify.hoon +++ b/desk/lib/notify.hoon @@ -50,6 +50,9 @@ %group-role =* g group.event [`g ~ %groups /(scot %p p.g)/[q.g]/add-roles] + :: + %contact + [~ ~ %groups /profile/(scot %p who.event)/[p.update.event]] == :* `@`time-id rope @@ -174,6 +177,17 @@ =/ cabal (~(got by cabals.group) sect) title.meta.cabal == + :: + %contact + =, -.event + :~ ship+who + =- (crip "updated their {(trip -)}") + ?+ `@tas`p.update p.update + %avatar 'profile picture' + %cover 'cover picture' + %groups 'favourite groups' + == + == == :: ?- -<.event @@ -194,6 +208,8 @@ %group-join (weld (group-path group.event) /edit/members) %group-kick (weld (group-path group.event) /edit/members) %group-role (weld (group-path group.event) /edit/members) + :: + %contact /contact/(scot %p who.event) == :: ~ @@ -232,4 +248,4 @@ %club (scot %uv p.whom) == -- --- \ No newline at end of file +-- diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon index cac66923b0..30603448fe 100644 --- a/desk/sur/activity.hoon +++ b/desk/sur/activity.hoon @@ -1,4 +1,4 @@ -/- c=channels, ch=chat, g=groups +/- c=channels, t=contacts, ch=chat, g=groups /+ mp=mop-extensions |% +| %collections @@ -34,12 +34,12 @@ :: :: actions are only ever performed for and by our selves :: -:: $add: add an event to the stream -:: $bump: mark a source as having new activity from myself -:: $del: remove a source and all its activity -:: $read: mark an event as read -:: $adjust: adjust the volume of an source -:: $allow-notifications: change which notifications are allowed +:: %add: add an event to the stream +:: %bump: mark a source as having new activity from myself +:: %del: remove a source and all its activity +:: %read: mark an event as read +:: %adjust: adjust the volume of an source +:: %allow-notifications: change which notifications are allowed :: +$ action $% [%add =incoming-event] @@ -53,9 +53,9 @@ :: :: $read-action: mark activity read :: -:: $item: (DEPRECATED) mark an individual activity as read, indexed by id -:: $event: (DEPRECATED) mark an individual activity as read, indexed by the event itself -:: $all: mark _everything_ as read for this source, and possibly children +:: %item: (DEPRECATED) mark an individual activity as read, indexed by id +:: %event: (DEPRECATED) mark an individual activity as read, indexed by the event itself +:: %all: mark _everything_ as read for this source, and possibly children :: +$ read-action $% [%item id=time-id] @@ -67,12 +67,12 @@ :: :: $update: what we hear after an action :: -:: $add: an event was added to the stream -:: $del: a source and its activity were removed -:: $read: a source's activity state was updated -:: $activity: the activity state was updated -:: $adjust: the volume of a source was adjusted -:: $allow-notifications: the allowed notifications were changed +:: %add: an event was added to the stream +:: %del: a source and its activity were removed +:: %read: a source's activity state was updated +:: %activity: the activity state was updated +:: %adjust: the volume of a source was adjusted +:: %allow-notifications: the allowed notifications were changed :: +$ update $% [%add =source time-event] @@ -87,8 +87,8 @@ :: $event: a single point of activity, from one of our sources :: :: $incoming-event: the event that was sent to us -:: $notified: if this event has been notified -:: $child: if this event is from a child source +:: .notified: if this event has been notified +:: .child: if this event is from a child source :: +$ event $: incoming-event @@ -109,6 +109,7 @@ [%group-role group=flag:g =ship roles=(set sect:g)] [%flag-post key=message-key channel=nest:c group=flag:g] [%flag-reply key=message-key parent=message-key channel=nest:c group=flag:g] + [%contact contact-event] == :: +$ post-event @@ -142,6 +143,10 @@ content=story:c mention=? == ++$ contact-event + $: who=ship + update=(pair @tas value:t) + == :: :: $source: where the activity is happening +$ source @@ -151,6 +156,7 @@ [%thread key=message-key channel=nest:c group=flag:g] [%dm =whom] [%dm-thread key=message-key =whom] + [%contact who=ship] == :: :: $index: the stream of activity and read state for a source @@ -223,15 +229,83 @@ %group-role %flag-post %flag-reply + %contact == +| %helpers +$ time-event [=time =event] ++ on-event ((on time event) lte) ++ ex-event ((mp time event) lte) ++ on-read-items ((on time ,~) lte) ++| %constants +++ default-volumes + ^~ + ^- (map event-type volume) + %- my + :~ [%post & &] + [%reply & |] + [%dm-reply & &] + [%post-mention & &] + [%reply-mention & &] + [%dm-invite & &] + [%dm-post & &] + [%dm-post-mention & &] + [%dm-reply-mention & &] + [%group-invite & &] + [%group-ask & &] + [%flag-post & &] + [%flag-reply & &] + [%group-kick & |] + [%group-join & |] + [%group-role & |] + [%contact | &] + == +++ old-volumes + ^~ + %- my + :~ [%soft (~(put by default-volumes) %post [& |])] + [%loud (~(run by default-volumes) |=([u=? *] [u &]))] + [%hush (~(run by default-volumes) |=([u=? *] [u |]))] + == +++ mute + ^~ + (~(run by default-volumes) |=(* [| |])) +:: +| %old-types ++ old |% + ++ v7 + |% + +$ stream ((mop time event) lte) + +$ event + $: incoming-event + notified=? + child=? + == + +$ incoming-event + $% [%post post-event] + [%reply reply-event] + [%dm-invite =whom] + [%dm-post dm-post-event] + [%dm-reply dm-reply-event] + [%group-ask group=flag:g =ship] + [%group-kick group=flag:g =ship] + [%group-join group=flag:g =ship] + [%group-invite group=flag:g =ship] + [%chan-init channel=nest:c group=flag:g] + [%group-role group=flag:g =ship roles=(set sect:g)] + [%flag-post key=message-key channel=nest:c group=flag:g] + [%flag-reply key=message-key parent=message-key channel=nest:c group=flag:g] + == + +$ source + $% [%base ~] + [%group =flag:g] + [%channel =nest:c group=flag:g] + [%thread key=message-key channel=nest:c group=flag:g] + [%dm =whom] + [%dm-thread key=message-key =whom] + == + +$ index [=stream =reads bump=time] + -- ++ v4 |% +$ feed (list activity-bundle) @@ -289,36 +363,4 @@ == -- -- -+| %constants -++ default-volumes - ^~ - ^- (map event-type volume) - %- my - :~ [%post & &] - [%reply & |] - [%dm-reply & &] - [%post-mention & &] - [%reply-mention & &] - [%dm-invite & &] - [%dm-post & &] - [%dm-post-mention & &] - [%dm-reply-mention & &] - [%group-invite & &] - [%group-ask & &] - [%flag-post & &] - [%flag-reply & &] - [%group-kick & |] - [%group-join & |] - [%group-role & |] - == -++ old-volumes - ^~ - %- my - :~ [%soft (~(put by default-volumes) %post [& |])] - [%loud (~(run by default-volumes) |=([u=? *] [u &]))] - [%hush (~(run by default-volumes) |=([u=? *] [u |]))] - == -++ mute - ^~ - (~(run by default-volumes) |=(* [| |])) --- \ No newline at end of file +-- diff --git a/desk/sur/contacts-0.hoon b/desk/sur/contacts-0.hoon new file mode 100644 index 0000000000..91ba0d3342 --- /dev/null +++ b/desk/sur/contacts-0.hoon @@ -0,0 +1,75 @@ +/- e=epic, g=groups +|% ++$ contact-0 + $: nickname=@t + bio=@t + status=@t + color=@ux + avatar=(unit @t) + cover=(unit @t) + groups=(set flag:g) + == +:: ++$ 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:e +:: ++$ 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:g] + [%del-group =flag:g] + == +:: ++$ 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/desk/sur/contacts.hoon b/desk/sur/contacts.hoon index bfdc750174..414ad3c517 100644 --- a/desk/sur/contacts.hoon +++ b/desk/sur/contacts.hoon @@ -1,104 +1,137 @@ /- e=epic, g=groups |% -:: [compat] protocol-versioning scheme :: -:: adopted from :groups, slightly modified. ++| %compat :: -:: for our action/update marks, we -:: - *must* support our version (+okay) -:: - *should* support previous versions (especially actions) -:: - but *can't* support future versions +++ okay `epic`1 :: -:: in the case of updates at unsupported protocol versions, -:: we backoff and subscribe for version changes (/epic). -:: (this alone is unlikely to help with future versions, -:: but perhaps our peer will downgrade. in the meantime, -:: we wait to be upgraded.) ++| %types +:: $value-type: contact field value type :: -+| %compat -++ okay `epic`0 -++ mar - |% - ++ base - |% - +$ act %contact-action - +$ upd %contact-update - -- - :: - ++ act `mark`^~((rap 3 *act:base '-' (scot %ud okay) ~)) - ++ upd `mark`^~((rap 3 *upd:base '-' (scot %ud okay) ~)) - -- ++$ value-type + $? %text + %numb + %date + %tint + %ship + %look + %flag + %set + == +:: $value: contact field value :: -+| %types -+$ contact - $: nickname=@t - bio=@t - status=@t - color=@ux - avatar=(unit @t) - cover=(unit @t) - groups=(set flag:g) ++$ 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:g] + :: + :: 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)] -+$ profile [wen=@da con=$@(~ contact)] -+$ rolodex (map ship foreign) ++$ 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 epic:e +:: +$ saga - $@ $? %want :: subscribing - %fail :: %want failed - %lost :: epic %fail - ~ :: none intended - == - saga:e -:: -+$ field - $% [%nickname nickname=@t] - [%bio bio=@t] - [%status status=@t] - [%color color=@ux] - [%avatar avatar=(unit @t)] - [%cover cover=(unit @t)] - [%add-group =flag:g] - [%del-group =flag:g] + $? %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: 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)] + [%self p=contact] + [%page p=kip q=contact] + [%edit p=kip q=contact] + [%wipe p=(list kip)] [%meet p=(list ship)] - [%heed p=(list ship)] [%drop p=(list ship)] [%snub p=(list ship)] == +:: network update +:: +:: %full: our profile :: -+$ update :: network ++$ update $% [%full profile] == +:: $response: local update :: -+$ news :: local - [who=ship con=$@(~ contact)] -:: -++ get-contact - |= [=bowl:gall who=@p] - => :_ ..get-contact - [who=who our=our.bowl now=now.bowl] - ~+ ^- (unit contact) - =/ 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 +:: %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] + == -- From 96b38d5a7ef372ee47ead8fe3db6046185a490e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Paraniak?= Date: Wed, 20 Nov 2024 12:13:51 +0800 Subject: [PATCH 2/3] activity: insert missing volume defaults in +load --- desk/app/activity.hoon | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index ca29a5d9e4..b9aa83977b 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -144,13 +144,13 @@ =? old ?=(%7 -.old) [%8 +.old] ?> ?=(%8 -.old) =. state old - :: :: insert missing volume defaults to %base - :: :: - :: =/ base-volume - :: (~(gut by volume-settings.state) [%base ~] *volume-map:a) - :: =. volume-settings.state - :: %+ ~(put by volume-settings.state) [%base ~] - :: (~(uni by base-volume) *volume-map:a) + :: insert missing volume defaults to %base + :: + =/ base-volume + (~(gut by volume-settings.state) [%base ~] *volume-map:a) + =. volume-settings.state + %+ ~(put by volume-settings.state) [%base ~] + (~(uni by base-volume) *volume-map:a) =. allowed %all (emit %pass /fix-init-unreads %agent [our.bowl dap.bowl] %poke noun+!>(%fix-init-unreads)) +$ versioned-state From adb2070b9479e1c1029a189f6bf9eb2bfbe8e26d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Paraniak?= Date: Thu, 21 Nov 2024 15:55:21 +0800 Subject: [PATCH 3/3] lib-notify: fix missing space in contact activity description --- desk/lib/notify.hoon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/lib/notify.hoon b/desk/lib/notify.hoon index cdab146c9d..96ab2c1633 100644 --- a/desk/lib/notify.hoon +++ b/desk/lib/notify.hoon @@ -181,7 +181,7 @@ %contact =, -.event :~ ship+who - =- (crip "updated their {(trip -)}") + =- (crip " updated their {(trip -)}") ?+ `@tas`p.update p.update %avatar 'profile picture' %cover 'cover picture'