Files
SocialPub/tools/pasture/scenarios/ghost.sh
T
thepraandClaude Opus 5.5 12b51a211a Pasture: Ghost 6.67 and its ActivityPub service
Ghost joins the pasture with its ActivityPub service (Fedify) on the shared MySQL, routed by Caddy as Ghost's own proxy
does. scenarios/ghost.sh: follows both ways, the publication's titled Articles with their edit and deletion, likes and
boosts both ways, Ghost's reply and note, alice's post in its Network feed, both unfollows and statistics: 22 checks,
with no change to PrivaPub.

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

75 lines
6.4 KiB
Bash

# Ghost 6.67 and its ActivityPub service 1.2.14: alice follows the publication and its post reaches her as a titled
# Article, edited and deleted; the publication follows alice, and from its Network screen likes, reposts and answers her
# post and posts a note; alice's like and boost count there; both unfollows; statistics. Driven through Ghost's admin
# API and the service's own API (peers/ghost.sh).
. "$here/peers/ghost.sh"
p_home_has() { curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next(((s.get('reblog') or s)['id'] for s in d if '$1' in ((s.get('reblog') or s)['content'] or '') or '$1' in (((s.get('reblog') or s).get('privapub') or {}).get('title') or '')), ''))"; }
p_status() { curl -s -H "$PH" "$P/api/v1/statuses/$1" | j "print(d.get('$2'))"; }
enc() { python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$1"; }
echo "ghost"
ghost_settle >/dev/null 2>&1
[ "$(ghost_ap GET v1/account/me | j "print(d['handle'])")" = "@index@ghost.test" ] && ok "Ghost's ActivityPub service knows the publication" || { ko "Ghost's ActivityPub service"; return 1; }
PT=$(privapub_token alice_ghost)
PH="Authorization: Bearer $PT"
[ -n "$PT" ] && ok "PrivaPub token for alice_ghost" || { ko "PrivaPub token for alice_ghost"; return 1; }
run=$(date +%s)
echo " following the publication"
pub_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=index@ghost.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
[ -n "$pub_on_p" ] && ok "PrivaPub resolves the publication" || ko "PrivaPub cannot resolve the publication"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$pub_on_p/follow"
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$pub_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
&& ok "alice follows the publication (Accept arrived)" || ko "the publication's Accept never arrived"
echo " articles"
post=$(ghost_api POST "posts/?source=html" "{\"posts\":[{\"title\":\"A Ghost article $run\",\"html\":\"<p>Published on Ghost, $run.</p>\",\"status\":\"published\"}]}")
post_id=$(echo "$post" | j "print(d['posts'][0]['id'])"); updated_at=$(echo "$post" | j "print(d['posts'][0]['updated_at'])")
until_true 60 '[ -n "$(p_home_has "A Ghost article $run")" ]' && ok "the publication's post reaches alice's home" || ko "the publication's post never reached alice"
art_on_p=$(p_home_has "A Ghost article $run")
[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$art_on_p" | j "print((d.get('privapub') or {}).get('title'))")" = "A Ghost article $run" ] \
&& ok "as an Article with its title" || ko "the article arrived without its title ($(curl -s -H "$PH" "$P/api/v1/statuses/$art_on_p" | j "print((d.get('privapub') or {}).get('title'))"))"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$art_on_p/favourite"
art_uri=$(curl -s -H "$PH" "$P/api/v1/statuses/$art_on_p" | j "print(d['uri'])")
until_true 45 '[ "$(ghost_ap GET "v1/post/$(enc "$art_uri")" | j "print(d.get(\"likeCount\"))")" = "1" ]' \
&& ok "alice's like counts on Ghost" || ko "alice's like never counted on Ghost ($(ghost_ap GET "v1/post/$(enc "$art_uri")" | j "print(d.get('likeCount'))"))"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$art_on_p/reblog"
until_true 45 '[ "$(ghost_ap GET "v1/post/$(enc "$art_uri")" | j "print(d.get(\"repostCount\"))")" = "1" ]' \
&& ok "alice's boost counts on Ghost" || ko "alice's boost never counted on Ghost"
ghost_api PUT "posts/$post_id/?source=html" "{\"posts\":[{\"title\":\"A Ghost article $run\",\"html\":\"<p>Published on Ghost, $run, then edited.</p>\",\"updated_at\":\"$updated_at\"}]}" >/dev/null
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$art_on_p" | j "print(\"then edited\" in d[\"content\"])")" = "True" ]' \
&& ok "its edit reaches PrivaPub" || ko "its edit never reached PrivaPub"
echo " the publication follows alice"
ghost_ap POST "v1/actions/follow/$(enc "@alice_ghost@privapub.test")" >/dev/null
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d[\"followers_count\"])")" -ge 1 ]' \
&& ok "the publication follows alice" || ko "Ghost's follow never reached alice"
p_post=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d "status=a PrivaPub post for Ghost $run&visibility=public")
p_uri=$(echo "$p_post" | j "print(d['uri'])"); p_post_id=$(echo "$p_post" | j "print(d['id'])")
until_true 45 'ghost_ap GET v1/feed/notes | grep -q "a PrivaPub post for Ghost $run"' && ok "alice's post reaches the publication's Network feed" || ko "alice's post never reached Ghost"
ghost_ap POST "v1/actions/like/$(enc "$p_uri")" >/dev/null
until_true 45 '[ "$(p_status $p_post_id favourites_count)" = "1" ]' && ok "Ghost's like counts on PrivaPub" || ko "Ghost's like never counted"
ghost_ap POST "v1/actions/repost/$(enc "$p_uri")" >/dev/null
until_true 45 '[ "$(p_status $p_post_id reblogs_count)" = "1" ]' && ok "Ghost's repost is a boost on PrivaPub" || ko "Ghost's repost never counted"
ghost_ap POST "v1/actions/reply/$(enc "$p_uri")" "{\"content\":\"a Ghost reply $run\"}" >/dev/null
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id/context" | j "print(any(\"a Ghost reply $run\" in s[\"content\"] for s in d[\"descendants\"]))")" = "True" ]' \
&& ok "Ghost's reply threads under alice's post" || ko "Ghost's reply missing on PrivaPub"
ghost_ap POST v1/actions/note "{\"content\":\"a Ghost note $run\"}" >/dev/null
until_true 45 '[ -n "$(p_home_has "a Ghost note $run")" ]' && ok "the publication's note reaches alice's home" || ko "the publication's note never reached alice"
echo " deletions"
ghost_api DELETE "posts/$post_id/" >/dev/null
until_true 60 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$art_on_p")" = "404" ]' \
&& ok "the article's deletion reaches PrivaPub" || ko "the deleted article still shows on PrivaPub"
echo " unfollows"
ghost_ap POST "v1/actions/unfollow/$(enc "@alice_ghost@privapub.test")" >/dev/null
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d[\"followers_count\"])")" = "0" ]' \
&& ok "the publication's unfollow reaches alice" || ko "alice still counts the publication"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$pub_on_p/unfollow"
until_true 45 '[ "$(ghost_ap GET v1/account/me | j "print(d[\"followerCount\"])")" = "0" ]' \
&& ok "alice's unfollow reaches the publication" || ko "Ghost still counts alice"
echo " statistics"
stats_check ghost.test ghost