summaryrefslogtreecommitdiff
path: root/app/lib
diff options
context:
space:
mode:
Diffstat (limited to 'app/lib')
-rw-r--r--app/lib/bip/b173.hoon145
-rw-r--r--app/lib/bitcoin-utils.hoon177
-rw-r--r--app/lib/json/nostrill.hoon14
-rw-r--r--app/lib/mutations/trill.hoon51
-rw-r--r--app/lib/nostr/client.hoon6
-rw-r--r--app/lib/nostrill.hoon3
-rw-r--r--app/lib/nostrill/comms.hoon62
-rw-r--r--app/lib/nostrill/follows.hoon3
-rw-r--r--app/lib/trill/feed.hoon31
9 files changed, 474 insertions, 18 deletions
diff --git a/app/lib/bip/b173.hoon b/app/lib/bip/b173.hoon
new file mode 100644
index 0000000..e554597
--- /dev/null
+++ b/app/lib/bip/b173.hoon
@@ -0,0 +1,145 @@
+:: BIP173: Bech32 Addresses
+:: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
+::
+:: Heavily copies:
+:: https://github.com/bitcoinjs/bech32/blob/master/index.js
+::
+:: TODO not really working at generating npub/nsec keys from hex
+/- sur=bitcoin
+/+ bcu=bitcoin-utils
+=, sur
+=, bcu
+|%
+++ prefixes
+ ^- (map network tape)
+ (my [[%main "bc"] [%testnet "tb"] [%regtest "bcrt"] ~])
+++ charset "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
++$ raw-decoded [hrp=tape data=(list @) checksum=(list @)]
+:: below is a port of: https://github.com/bitcoinjs/bech32/blob/master/index.js
+::
+++ polymod
+ |= values=(list @)
+ |^ ^- @
+ =/ gen=(list @ux)
+ ~[0x3b6a.57b2 0x2650.8e6d 0x1ea1.19fa 0x3d42.33dd 0x2a14.62b3]
+ =/ chk=@ 1
+ |- ?~ values chk
+ =/ top (rsh [0 25] chk)
+ =. chk
+ (mix i.values (lsh [0 5] (dis chk 0x1ff.ffff)))
+ $(values t.values, chk (update-chk chk top gen))
+::
+ ++ update-chk
+ |= [chk=@ top=@ gen=(list @ux)]
+ =/ is (gulf 0 4)
+ |- ?~ is chk
+ ?: =(1 (dis 1 (rsh [0 i.is] top)))
+ $(is t.is, chk (mix chk (snag i.is gen)))
+ $(is t.is)
+ --
+::
+++ expand-hrp
+ |= hrp=tape
+ ^- (list @)
+ =/ front (turn hrp |=(p=@tD (rsh [0 5] p)))
+ =/ back (turn hrp |=(p=@tD (dis 31 p)))
+ (zing ~[front ~[0] back])
+::
+++ verify-checksum
+ |= [hrp=tape data-and-checksum=(list @)]
+ ^- ?
+ %- |=(a=@ =(1 a))
+ %- polymod
+ (weld (expand-hrp hrp) data-and-checksum)
+::
+++ checksum
+ |= [hrp=tape data=(list @)]
+ ^- (list @)
+ :: xor 1 with the polymod
+ ::
+ =/ pmod=@
+ %+ mix 1
+ %- polymod
+ (zing ~[(expand-hrp hrp) data (reap 6 0)])
+ %+ turn (gulf 0 5)
+ |=(i=@ (dis 31 (rsh [0 (mul 5 (sub 5 i))] pmod)))
+::
+++ charset-to-value
+ |= c=@tD
+ ^- (unit @)
+ (find ~[c] charset)
+++ value-to-charset
+ |= value=@
+ ^- (unit @tD)
+ ?: (gth value 31) ~
+ `(snag value charset)
+::
+++ is-valid
+ |= [bech=tape last-1-pos=@] ^- ?
+ ?& ?|(=((cass bech) bech) =((cuss bech) bech)) :: to upper or to lower is same as bech
+ (gte last-1-pos 1)
+ (lte (add last-1-pos 7) (lent bech))
+ (lte (lent bech) 90)
+ (levy bech |=(c=@tD (gte c 33)))
+ (levy bech |=(c=@tD (lte c 126)))
+ ==
+:: data should be 5bit words
+::
+++ encode-raw
+ |= [hrp=tape data=(list @)]
+ ^- cord
+ =/ combined=(list @)
+ (weld data (checksum hrp data))
+ %- crip
+ (zing ~[hrp "1" (tape (murn combined value-to-charset))])
+++ decode-raw
+ |= body=cord
+ ^- (unit raw-decoded)
+ =/ bech (cass (trip body)) :: to lowercase
+ =/ pos (flop (fand "1" bech))
+ ?~ pos ~
+ =/ last-1=@ i.pos
+ ?. (is-valid bech last-1) :: check bech32 validity (not segwit validity or checksum)
+ ~
+ =/ hrp (scag last-1 bech)
+ =/ encoded-data-and-checksum=(list @)
+ (slag +(last-1) bech)
+ =/ data-and-checksum=(list @)
+ %+ murn encoded-data-and-checksum
+ charset-to-value
+ ?. =((lent encoded-data-and-checksum) (lent data-and-checksum)) :: ensure all were in CHARSET
+ ~
+ ?. (verify-checksum hrp data-and-checksum)
+ ~
+ =/ checksum-pos (sub (lent data-and-checksum) 6)
+ `[hrp (scag checksum-pos data-and-checksum) (slag checksum-pos data-and-checksum)]
+:: +from-address: BIP173 bech32 address encoding to hex
+:: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
+:: expects to drop a leading 5-bit 0 (the witness version)
+::
+++ from-address
+ |= body=cord
+ ^- hexb
+ ~| "Invalid bech32 address"
+ =/ d=(unit raw-decoded) (decode-raw body)
+ ?> ?=(^ d)
+ =/ bs=bits (from-atoms:bit 5 data.u.d)
+ =/ byt-len=@ (div (sub wid.bs 5) 8)
+ ?> =(5^0b0 (take:bit 5 bs))
+ ?> ?| =(20 byt-len)
+ =(32 byt-len)
+ ==
+ [byt-len `@ux`dat:(take:bit (mul 8 byt-len) (drop:bit 5 bs))]
+:: pubkey is the 33 byte ECC compressed public key
+::
+++ encode-pubkey
+ |= [=network pubkey=byts]
+ ^- (unit cord)
+ ?. =(33 wid.pubkey)
+ ~|('pubkey must be a 33 byte ECC compressed public key' !!)
+ =/ prefix (~(get by prefixes) network)
+ ?~ prefix ~
+ :- ~
+ %+ encode-raw u.prefix
+ [0v0 (to-atoms:bit 5 [160 `@ub`dat:(hash-160 pubkey)])]
+--
diff --git a/app/lib/bitcoin-utils.hoon b/app/lib/bitcoin-utils.hoon
new file mode 100644
index 0000000..a23354c
--- /dev/null
+++ b/app/lib/bitcoin-utils.hoon
@@ -0,0 +1,177 @@
+:: lib/bitcoin-utils.hoon
+:: Utilities for working with BTC data types and transactions
+::
+/- *bitcoin
+~% %bitcoin-utils-lib ..part ~
+|%
+::
+:: TODO: move this bit/byt stuff to zuse
+:: bit/byte utilities
+::
+::
+:: +blop: munge bit and byt sequences (cat, flip, take, drop)
+::
+++ blop
+ ~/ %blop
+ |_ =bloq
+ +$ biyts [wid=@ud dat=@]
+ ++ cat
+ |= bs=(list biyts)
+ ^- biyts
+ :- (roll (turn bs |=(b=biyts -.b)) add)
+ (can bloq (flop bs))
+ :: +flip: flip endianness while preserving lead/trail zeroes
+ ::
+ ++ flip
+ |= b=biyts
+ ^- biyts
+ [wid.b (rev bloq b)]
+ :: +take: take n bloqs from front
+ :: pads front with extra zeroes if n is longer than input
+ ::
+ ++ take
+ |= [n=@ b=biyts]
+ ^- biyts
+ ?: (gth n wid.b)
+ [n dat.b]
+ [n (rsh [bloq (sub wid.b n)] dat.b)]
+ :: +drop: drop n bloqs from front
+ :: returns 0^0 if n >= width
+ ::
+ ++ drop
+ |= [n=@ b=biyts]
+ ^- biyts
+ ?: (gte n wid.b)
+ 0^0x0
+ =+ n-take=(sub wid.b n)
+ [n-take (end [bloq n-take] dat.b)]
+ --
+++ byt ~(. blop 3)
+::
+++ bit
+ ~/ %bit
+ =/ bl ~(. blop 0)
+ |%
+ ++ cat cat:bl:bit
+ ++ flip flip:bl:bit
+ ++ take take:bl:bit
+ ++ drop drop:bl:bit
+ ++ from-atoms
+ |= [bitwidth=@ digits=(list @)]
+ ^- bits
+ %- cat:bit
+ %+ turn digits
+ |= a=@
+ ?> (lte (met 0 a) bitwidth)
+ [bitwidth `@ub`a]
+ :: +to-atoms: convert bits to atoms of bitwidth
+ ::
+ ++ to-atoms
+ |= [bitwidth=@ bs=bits]
+ ^- (list @)
+ =| res=(list @)
+ ?> =(0 (mod wid.bs bitwidth))
+ |-
+ ?: =(0 wid.bs) res
+ %= $
+ res (snoc res dat:(take:bit bitwidth bs))
+ bs (drop:bit bitwidth bs)
+ ==
+ --
+:: big endian sha256: input and output are both MSB first (big endian)
+::
+++ sha256
+ ~/ %sha256
+ |= =byts
+ ^- hexb
+ %- flip:byt
+ [32 (shay (flip:byt byts))]
+::
+++ dsha256
+ ~/ %dsha256
+ |= =byts
+ (sha256 (sha256 byts))
+::
+++ hash-160
+ ~/ %hash-160
+ |= val=byts
+ ^- hexb
+ =, ripemd:crypto
+ :- 20
+ %- ripemd-160
+ (sha256 val)
+
+::
+:: hxb: hex parsing utilities
+::
+++ hxb
+ ~% %hxb ..blop ~
+ |%
+ ++ from-cord
+ ~/ %from-cord
+ |= h=@t
+ ^- hexb
+ ?: =('' h) 1^0x0
+ :: Add leading 00
+ ::
+ =+ (lsh [3 2] h)
+ :: Group by 4-size block
+ ::
+ =+ (rsh [3 2] -)
+ :: Parse hex to atom
+ ::
+ =/ a (need (de:base16:mimes:html -))
+ [-.a `@ux`+.a]
+ ::
+ ++ to-cord
+ ~/ %to-cord
+ |= =hexb
+ ^- cord
+ (en:base16:mimes:html hexb)
+ --
+::
+:: +csiz: CompactSize integers (a Bitcoin-specific datatype)
+:: https://btcinformation.org/en/developer-reference#compactsize-unsigned-integers
+:: - encode: big endian to little endian
+:: - decode: little endian to big endian
+::
+++ csiz
+ ~% %csiz ..blop ~
+ |%
+ ++ en
+ ~/ %en
+ |= a=@
+ ^- hexb
+ =/ l=@ (met 3 a)
+ ?: =(l 0) 1^a
+ ?: =(l 1) 1^a
+ ?: =(l 2) (cat:byt ~[1^0xfd (flip:byt 2^a)])
+ ?: (lte l 4) (cat:byt ~[1^0xfe (flip:byt 4^a)])
+ ?: (lte l 8) (cat:byt ~[1^0xff (flip:byt 8^a)])
+ ~|("Cannot encode CompactSize longer than 8 bytes" !!)
+ ::
+ ++ de
+ ~/ %de
+ |= h=hexb
+ ^- [n=hexb rest=hexb]
+ =/ s=@ux dat:(take:byt 1 h)
+ ?: (lth s 0xfd) [1^s (drop:byt 1 h)]
+ ~| "Invalid compact-size at start of {<h>}"
+ =/ len=bloq
+ ?+ s !!
+ %0xfd 1
+ %0xfe 2
+ %0xff 3
+ ==
+ :_ (drop:byt (add 1 (bex len)) h)
+ %- flip:byt
+ (take:byt (bex len) (drop:byt 1 h))
+ :: +dea: atom instead of hexb for parsed CompactSize
+ ::
+ ++ dea
+ |= h=hexb
+ ^- [a=@ rest=hexb]
+ => (de h)
+ [dat.n rest]
+ --
+--
diff --git a/app/lib/json/nostrill.hoon b/app/lib/json/nostrill.hoon
index b07de61..266a6c8 100644
--- a/app/lib/json/nostrill.hoon
+++ b/app/lib/json/nostrill.hoon
@@ -15,6 +15,7 @@
feed+(feed-with-cursor:en:trill feed ~ ~)
nostr+(en-nostr-feed nostr-feed)
following+(enfollowing following)
+ following2+(feed-with-cursor:en:trill following2 ~ ~)
['followGraph' (engraph follow-graph)]
~
==
@@ -190,9 +191,10 @@
add+postadd
reply+reply
quote+quote
- rp+rp
+ rp+pid
:: rt+de-rt
- :: del+hex:de:common
+ reaction+reaction
+ del+pid
==
++ postadd
%- ot :~
@@ -211,11 +213,17 @@
host+(se:de:common %p)
id+de-atom-id
==
-++ rp
+++ pid
%- ot :~
host+(se:de:common %p)
id+de-atom-id
==
+++ reaction
+ %- ot :~
+ host+(se:de:common %p)
+ id+de-atom-id
+ reaction+so
+ ==
++ rt
%- ot :~
id+hex:de:common
diff --git a/app/lib/mutations/trill.hoon b/app/lib/mutations/trill.hoon
index 40cce1e..cfedecf 100644
--- a/app/lib/mutations/trill.hoon
+++ b/app/lib/mutations/trill.hoon
@@ -3,6 +3,7 @@
/+ appjs=json-nostrill,
lib=nostrill,
+ trill-feed,
trill=trill-post,
njs=json-nostr,
postlib=trill-post,
@@ -22,11 +23,26 @@
++ add-to-feed |= p=post:post
=. feed.state (put:orm:feed feed.state id.p p)
state
+++ headsup-poke
+ |= [poke=post-poke:ui:sur p=post:post] ^- (unit engagement:comms)
+ ?- -.poke
+ %add ~
+ ::: TODO del-reply
+ %del ~
+ %quote `[%quote id.poke p]
+ %reply `[%reply id.poke p]
+ %rp `[%rp id.poke id.p]
+ %reaction `[%reaction id.poke reaction.poke]
+ ==
++ handle-post |= poke=post-poke:ui:sur
^- (quip card _state)
=/ profile (~(get by profiles.state) [%urbit our.bowl])
=/ pubkey pub.i.keys.state
+ ?: ?=(%del -.poke)
+ =. feed.state =< + (del:orm:feed feed.state id.poke)
+ :: TODO
+ `state
=/ p=post:post
?- -.poke
%add
@@ -45,23 +61,36 @@
=/ sp (build-sp:trill host.poke our.bowl '' ~ ~)
=. contents.sp ~[quote]
(build-post:trill now.bowl pubkey sp)
+ %reaction
+ =/ p (got:orm:feed feed.state id.poke)
+ =. reacts.engagement.p %+ ~(put by reacts.engagement.p)
+ our.bowl [reaction.poke *signature:post]
+ p
==
+ =. state (add-to-feed p)
=/ pw [p (some pubkey) ~ ~ profile]
=/ jfact=fact:ui:sur [%post %add pw]
=/ ui-card (update-ui:cards:lib jfact)
- :: only update followers when we are updating our own feed
- ?. .=(our.bowl host.p) [~[ui-card] state]
- =. state (add-to-feed p)
- =/ =fact:comms [%post %add p]
- =/ fact-card (update-followers:cards:lib fact)
+ =/ crds ~(. cards:lib bowl)
+ =/ engagement-poke (headsup-poke poke p)
+ =/ base-cards
+ ?~ engagement-poke :~(ui-card)
+ =/ poke [%eng u.engagement-poke]
+ =/ eng-card (poke-host:crds host.p poke)
+ :~(ui-card eng-card)
+ :: if our own post we update followers, if someone elses post we send an engagement poke
:_ state
- :~ ui-card
- fact-card
- ==
-
+ ?: .=(our.bowl host.p)
+ ::
+ =/ =fact:comms [%post %add p]
+ =/ fact-card (update-followers:cards:lib fact)
+ :- fact-card base-cards
+ ::
+ base-cards
++ handle-post-fact |= pf=post-fact:comms
^- (quip card _state)
+ ~& handle-post-fact=pf
=/ =user:sur [%urbit src.bowl]
=/ fed (~(get by following.state) user)
?~ fed ~& "emmm not following ya" `state
@@ -72,6 +101,10 @@
:: =/ =user:nsur [%urbit host.p.pdf]
(put:orm:feed u.fed id.p.pf p.pf)
=. following.state (~(put by following.state) user nf)
+ =. following2.state
+ ?: ?=(%del -.pf)
+ =< + (del:orm:feed following2.state id.pf)
+ (insert-to-global:trill-feed nf p.pf)
:: TODO update the ui with the changes
:_ state ~
--
diff --git a/app/lib/nostr/client.hoon b/app/lib/nostr/client.hoon
index 2438a47..c258258 100644
--- a/app/lib/nostr/client.hoon
+++ b/app/lib/nostr/client.hoon
@@ -48,9 +48,9 @@
++ get-posts
^- (quip card _state)
=/ kinds (silt ~[1])
- =/ since (sub now.bowl ~m1)
+ =/ last-week (sub now.bowl ~d1)
:: =/ since (to-unix-secs:jikan:sr last-week)
- =/ =filter:nsur [~ ~ `kinds ~ `since ~ ~]
+ =/ =filter:nsur [~ ~ `kinds ~ `last-week ~ ~]
(send-req ~[filter])
++ get-user-feed
@@ -92,7 +92,7 @@
++ test-connection
|= relay-url=@t
=/ kinds (silt ~[1])
- =/ since (sub now.bowl ~m1)
+ =/ since (sub now.bowl ~m10)
=/ =filter:nsur [~ ~ `kinds ~ `since ~ ~]
=/ sub-id (gen-sub-id:nostr-keys eny.bowl)
=/ req=client-msg:nsur [%req sub-id ~[filter]]
diff --git a/app/lib/nostrill.hoon b/app/lib/nostrill.hoon
index d46d090..1f5db40 100644
--- a/app/lib/nostrill.hoon
+++ b/app/lib/nostrill.hoon
@@ -55,5 +55,8 @@
:: ++ update-followers |= =fact:comms ^- card:agent:gall
++ update-followers |= =fact:comms ^- card:agent:gall
[%give %fact ~[/follow] %noun !>(fact)]
+ ::
+ ++ poke-host |= [sip=@p =poke:comms] ^- card:agent:gall
+ [%pass /heads-up %agent [sip dap.bowl] %poke %noun !>(poke)]
--
--
diff --git a/app/lib/nostrill/comms.hoon b/app/lib/nostrill/comms.hoon
index 5f9b0a9..a386c0e 100644
--- a/app/lib/nostrill/comms.hoon
+++ b/app/lib/nostrill/comms.hoon
@@ -1,5 +1,5 @@
-/- sur=nostrill, nsur=nostr, comms=nostrill-comms, feed=trill-feed
-/+ js=json-nostr, sr=sortug,constants, gatelib=trill-gate, feedlib=trill-feed, jsonlib=json-nostrill
+/- sur=nostrill, nsur=nostr, comms=nostrill-comms, feed=trill-feed, post=trill-post
+/+ js=json-nostr, sr=sortug,constants, gatelib=trill-gate, feedlib=trill-feed, jsonlib=json-nostrill, lib=nostrill
|_ [=state:sur =bowl:gall]
++ cast-poke
|= raw=* ^- poke:comms
@@ -87,6 +87,64 @@
=/ c1 [%give %fact paths cage]
:~(c1)
+:: engagement pokes, heads up when replying etc. to a post on your feed
+++ handle-eng
+ |= e=engagement:comms
+ ?- -.e
+ %reply
+ =/ poast (get:orm:feed feed.state parent.e)
+ ?~ poast `state
+ =. children.u.poast (~(put in children.u.poast) id.child.e)
+ =. feed.state (put:orm:feed feed.state parent.e u.poast)
+ =. feed.state (put:orm:feed feed.state id.child.e child.e)
+ =/ f=fact:comms [%post %add child.e]
+ =/ f2=fact:comms [%post %changes u.poast]
+ :_ state
+ :~ (update-followers:cards:lib f)
+ (update-followers:cards:lib f2)
+ ==
+ %del-reply
+ =. feed.state =< + (del:orm:feed feed.state child.e)
+ =/ poast (get:orm:feed feed.state parent.e)
+ ?~ poast `state
+ =. children.u.poast (~(del in children.u.poast) child.e)
+ =. feed.state (put:orm:feed feed.state parent.e u.poast)
+ :_ state
+ :~ (update-followers:cards:lib [%post %changes u.poast])
+ (update-followers:cards:lib [%post %del child.e])
+ ==
+ :: TODO ideally we want the full quote to display it within the post engagement. So do we change quoted.engagement.post? What if the quoter edits the quote down the line, etc.
+ %quote
+ =/ poast (get:orm:feed feed.state src.e)
+ ?~ poast `state
+ =/ spid [*signature:post src.bowl id.post.e]
+ =. quoted.engagement.u.poast (~(put in quoted.engagement.u.poast) spid)
+ =. feed.state (put:orm:feed feed.state src.e u.poast)
+ =/ f=fact:comms [%post %changes u.poast]
+ :_ state
+ :~ (update-followers:cards:lib f)
+ ==
+ %rp
+ =/ poast (get:orm:feed feed.state src.e)
+ ?~ poast `state
+ =/ spid [*signature:post src.bowl rt.e]
+ =. shared.engagement.u.poast (~(put in shared.engagement.u.poast) spid)
+ =. feed.state (put:orm:feed feed.state src.e u.poast)
+ =/ f=fact:comms [%post %changes u.poast]
+ :_ state
+ :~ (update-followers:cards:lib f)
+ ==
+ %reaction
+ =/ poast (get:orm:feed feed.state post.e)
+ ?~ poast `state
+ :: TODO signatures et al.
+ =. reacts.engagement.u.poast (~(put by reacts.engagement.u.poast) src.bowl [reaction.e *signature:post])
+ =. feed.state (put:orm:feed feed.state post.e u.poast)
+ =/ f=fact:comms [%post %changes u.poast]
+ :_ state
+ :~ (update-followers:cards:lib f)
+ ==
+ ==
--
diff --git a/app/lib/nostrill/follows.hoon b/app/lib/nostrill/follows.hoon
index 34c1d19..942a876 100644
--- a/app/lib/nostrill/follows.hoon
+++ b/app/lib/nostrill/follows.hoon
@@ -48,7 +48,8 @@
++ handle-follow-ok |= [=user:sur =fc:feed profile=(unit user-meta:nsur)]
^- (quip card:agent:gall _state)
- =. following.state (~(put by following.state) user feed.fc)
+ =. following.state (~(put by following.state) user feed.fc)
+ =. following2.state (add-new-feed:feedlib following2.state feed.fc)
=/ graph (~(get by follow-graph.state) [%urbit our.bowl])
=/ follows ?~ graph (silt ~[user]) (~(put in u.graph) user)
=. follow-graph.state (~(put by follow-graph.state) [%urbit our.bowl] follows)
diff --git a/app/lib/trill/feed.hoon b/app/lib/trill/feed.hoon
index 721a596..31fcd64 100644
--- a/app/lib/trill/feed.hoon
+++ b/app/lib/trill/feed.hoon
@@ -67,4 +67,35 @@
(put:form:post acc id full-node)
?~ children [%empty ~]
:- %full g
+::
+++ add-new-feed
+|= [global=feed:feed new=feed:feed] ^- feed:feed
+ =/ poasts (tap:orm:feed new)
+ |- ?~ poasts global
+ =/ poast +.i.poasts
+ =. global (insert-to-global global poast)
+ $(poasts t.poasts)
+
+++ consolidate-feeds
+|= feeds=(list [* feed:feed]) ^- feed:feed
+ =| nf=feed:feed
+ |- ?~ feeds nf
+ =/ poasts (tap:orm:feed +.i.feeds)
+ =. nf |- ?~ poasts nf
+ =/ poast +.i.poasts
+ =. nf (insert-to-global nf poast)
+ $(poasts t.poasts)
+ $(feeds t.feeds)
+
+++ find-available-id
+=| tries=@ud
+|= [f=feed:feed id=@da] ^- @da
+ ?: (gte tries 20) ~|('find-available-id stack overflow' !!)
+ ?. (has:orm:feed f id) id
+ $(id +(id), tries +(tries))
+
+++ insert-to-global
+|= [f=feed:feed p=post:post] ^- feed:feed
+ =/ nid (find-available-id f id.p)
+ (put:orm:feed f nid p)
--