Files
thepraandClaude Opus 5.5 b39eadffeb Hubzilla in the pasture
Hubzilla 11.4.1 with its pubcrawl addon (peers/hubzilla.sh): the hlhd image on the shared MySQL, a cron sidecar, hzuser
and hzfriend made through Hubzilla's own PHP as public channels that speak ActivityPub. scenarios/hubzilla.sh, 20
checks: follows, posts, comments, likes, edits and deletions both ways, and a third channel's comment that the thread's
owner passes on, which PrivaPub takes on its FEP-8b32 proof. Known gap G-0010 (upstream): Hubzilla 11 keeps a follower
after its Undo{Follow}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-06 09:59:49 +02:00

89 lines
7.5 KiB
Bash

# Hubzilla 11.4.1 (pubcrawl, its ActivityPub addon): follows both ways; posts both ways; comments both ways; likes both
# ways; a comment by hzfriend in hzuser's thread, which Hubzilla passes on to alice as the thread's owner; edits and
# deletions both ways; the unfollow; statistics. Hubzilla's API (api/z/1.0) posts, edits and reads; what it has no route
# for is done through its own PHP (connecting) or its web pages signed in (liking, dropping; peers/hubzilla.sh), and
# what it holds is read from its MySQL (database hubzilla, the item table by mid).
. "$here/peers/hubzilla.sh"
HZ=https://hubzilla.test:6443
# hzc <nick> <curl args...>: Hubzilla's API as <nick>
hzc() { local nick=$1; shift; site hubzilla.test -s -u "$nick@hubzilla.test:$HUBZILLA_PASSWORD" "$@"; }
hz_sql() { podman exec pasture-mysql mysql -upasture -ppasture hubzilla -Nse "$1" 2>/dev/null; }
# hz_item <mid> [nick]: the id of the item Hubzilla holds under that ActivityPub id in a channel (hzuser's)
hz_item() { hz_sql "select id from item where mid = '$1' and uid = (select channel_id from channel where channel_address = '${2:-hzuser}') and item_deleted = 0 limit 1"; }
hz_connect() { hz_eval "use Zotlabs\\Lib\\Connect; \$c = channelx_by_nick('$1'); \$r = Connect::connect(\$c, '$2'); echo \$r['success'] ? 'connected' : \$r['message'];"; }
p_home_id() { curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next((o['id'] for o in ((s.get('reblog') or s) for s in d) if '$1' in (o['content'] or '')), ''))"; }
p_status() { curl -s -H "$PH" "$P/api/v1/statuses/$1" | j "print(d.get('$2'))"; }
p_replies_have() { [ "$(curl -s -H "$PH" "$P/api/v1/statuses/$1/context" | j "print(any('$2' in s['content'] for s in d['descendants']))")" = "True" ]; }
echo "hubzilla"
[ -n "$(hzc hzuser "$HZ/api/z/1.0/verify" | j "print(d.get('channel_address') or '')")" ] && ok "Hubzilla's API as hzuser" \
|| { ko "Hubzilla's API ($(hzc hzuser "$HZ/api/z/1.0/verify" | head -c 200))"; return 1; }
PT=$(privapub_token alice_hubzilla)
PH="Authorization: Bearer $PT"
[ -n "$PT" ] && ok "PrivaPub token for alice_hubzilla" || { ko "PrivaPub token for alice_hubzilla"; return 1; }
alice_uri="$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d['url'])" | sed 's|/@|/peasants/|')"
run=$(date +%s)
echo " follows"
hz_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=hzuser@hubzilla.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
[ -n "$hz_on_p" ] && ok "PrivaPub resolves hzuser" || ko "PrivaPub cannot resolve hzuser"
# (a Hubzilla made anew keeps its addresses: alice follows again, so both follows below are new)
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$hz_on_p/unfollow"
hz_connect hzuser "$alice_uri" >/dev/null
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d[\"followers_count\"])")" -ge 1 ]' \
&& ok "hzuser follows alice" || ko "Hubzilla's follow never reached alice"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$hz_on_p/follow"
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$hz_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
&& ok "alice follows hzuser (Accept arrived)" || ko "Hubzilla's Accept never arrived"
# (hzfriend connects to hzuser now: a channel comments only on what reached it, and only once the other side has let it)
hz_connect hzfriend hzuser@hubzilla.test >/dev/null
echo " posts"
hzc hzuser -o /dev/null -X POST "$HZ/api/z/1.0/item/update" --data-urlencode "body=a Hubzilla post $run"
until_true 60 '[ -n "$(p_home_id "a Hubzilla post $run")" ]' && ok "hzuser's post reaches alice's home" || ko "hzuser's post never reached alice"
hz_post=$(p_home_id "a Hubzilla post $run")
hz_post_uri=$(p_status "$hz_post" uri)
p_post=$(curl -s -X POST -H "$PH" "$P/api/v1/statuses" -d "status=a PrivaPub post for Hubzilla $run&visibility=public")
p_post_id=$(echo "$p_post" | j "print(d['id'])"); p_post_uri=$(echo "$p_post" | j "print(d['uri'])")
until_true 60 '[ -n "$(hz_item "$p_post_uri")" ]' && ok "alice's post reaches hzuser" || ko "alice's post never reached Hubzilla"
echo " comments"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses" -d "status=@hzuser@hubzilla.test a PrivaPub comment $run&in_reply_to_id=$hz_post&visibility=public"
until_true 60 '[ "$(hz_sql "select count(*) from item where body like '"'%a PrivaPub comment $run%'"' and thr_parent = '"'$hz_post_uri'"'")" -ge 1 ]' \
&& ok "alice's comment threads under hzuser's post" || ko "alice's comment never reached Hubzilla"
hzc hzuser -o /dev/null -X POST "$HZ/api/z/1.0/item/update" --data-urlencode "body=a Hubzilla comment $run" -d "parent=$(hz_item "$p_post_uri")"
until_true 60 'p_replies_have "$p_post_id" "a Hubzilla comment $run"' && ok "hzuser's comment threads under alice's post" || ko "hzuser's comment missing on PrivaPub"
# hzfriend, who alice does not follow, comments in hzuser's thread: Hubzilla, the thread's owner, passes it on to alice
until_true 30 '[ -n "$(hz_item "$hz_post_uri" hzfriend)" ]' >/dev/null
hzc hzfriend -o /dev/null -X POST "$HZ/api/z/1.0/item/update" --data-urlencode "body=a word from hzfriend $run" -d "parent=$(hz_item "$hz_post_uri" hzfriend)"
until_true 60 'p_replies_have "$hz_post" "a word from hzfriend $run"' \
&& ok "hzfriend's comment in hzuser's thread reaches alice, passed on" || ko "hzfriend's comment never reached alice"
echo " likes"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$hz_post/favourite"
until_true 60 '[ "$(hz_sql "select count(*) from item where verb = '"'Like'"' and thr_parent = '"'$hz_post_uri'"' and item_deleted = 0")" -ge 1 ]' \
&& ok "alice's like lands on Hubzilla" || ko "alice's like never reached Hubzilla"
hz_web hzuser -o /dev/null "$HZ/like/$(hz_item "$p_post_uri")?verb=like"
until_true 60 '[ "$(p_status "$p_post_id" favourites_count)" = "1" ]' && ok "hzuser's like counts on PrivaPub" || ko "hzuser's like never counted"
echo " edits and deletions"
hzc hzuser -o /dev/null -X POST "$HZ/api/z/1.0/item/update" -d "post_id=$(hz_item "$hz_post_uri")" --data-urlencode "body=a Hubzilla post $run, edited"
until_true 60 'p_status "$hz_post" content | grep -q "edited"' && ok "hzuser's edit reaches PrivaPub" || ko "hzuser's edit never reached PrivaPub"
curl -s -o /dev/null -X PUT -H "$PH" "$P/api/v1/statuses/$p_post_id" -d "status=a PrivaPub post for Hubzilla $run, edited"
until_true 60 '[ "$(hz_sql "select count(*) from item where mid = '"'$p_post_uri'"' and body like '"'%edited%'"'")" -ge 1 ]' \
&& ok "alice's edit reaches Hubzilla" || ko "alice's edit never reached Hubzilla"
hz_web hzuser -o /dev/null "$HZ/item/drop/$(hz_item "$hz_post_uri")"
until_true 60 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$hz_post")" = "404" ]' \
&& ok "hzuser's deletion reaches PrivaPub" || ko "hzuser's deleted post still on PrivaPub"
curl -s -o /dev/null -X DELETE -H "$PH" "$P/api/v1/statuses/$p_post_id"
until_true 60 '[ -z "$(hz_item "$p_post_uri")" ]' && ok "alice's deletion reaches Hubzilla" || ko "alice's deleted post still on Hubzilla"
echo " unfollow"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$hz_on_p/unfollow"
# (Hubzilla takes the Undo, verified, but its unfollow deletes a setting Hubzilla 11 no longer keeps: G-0010)
until_true 20 '[ "$(hz_sql "select count(*) from abconfig where xchan = '"'$alice_uri'"' and cat = '"'their_perms'"' and v = '"'1'"' and chan = (select channel_id from channel where channel_address = '"'hzuser'"')")" = "0" ]' \
&& ok "alice's unfollow reaches Hubzilla" || xf "Hubzilla keeps alice following hzuser after her Undo{Follow} (G-0010)"
echo " statistics"
stats_check hubzilla.test hubzilla