Pasture: PeerTube joins, and its videos play through us
PeerTube 8.3.1 runs on the shared Postgres and Redis, transcoding off. Its scenario passes 24 checks: a persona follows a channel; a new video comes as the channel's Announce and reaches the persona's home as a boost, playable through the media proxy with byte ranges; comments both ways thread; a like and its undo count; renaming and deletion arrive; the unfollow; statistics. The town's seeder also takes reruns on the same accounts in its stride: a Lemmy community already made is found, a circle member already approved asks nothing again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
d61eac0a01
commit
40217f3261
8 files changed
+186
-1
No files matched your search
@@ -494,6 +494,10 @@ tools/pasture/run.sh down # removes e
|
|||||||
- **Hollo (0.9.19):** Fedify's microblog server on the shared Postgres, set up through its web form (which
|
- **Hollo (0.9.19):** Fedify's microblog server on the shared Postgres, set up through its web form (which
|
||||||
checks `Origin` against `Host`, so the request names `hollo.test` without the port). It needs a 44-character
|
checks `Origin` against `Host`, so the request names `hollo.test` without the port). It needs a 44-character
|
||||||
`SECRET_KEY`, a media directory and a `themeColor`; statuses and votes go as JSON. Town only, no scenario.
|
`SECRET_KEY`, a media directory and a `themeColor`; statuses and votes go as JSON. Town only, no scenario.
|
||||||
|
- **PeerTube (8.3.1):** the official image on the shared Postgres and Redis (db 4), configured through `PEERTUBE_*`
|
||||||
|
variables, trusting Caddy's CA through `NODE_EXTRA_CA_CERTS`; `peertube_settle` turns transcoding off (a test video
|
||||||
|
is served as uploaded) and keeps root's token in `.state/peertube.token`. It wants a bare `Host: peertube.test`.
|
||||||
|
`scenarios/peertube.sh`, 24 checks.
|
||||||
- **Pleroma (2.10.2):** `images/pleroma` installs the OTP release, pinned by checksum, as Akkoma's does; it also needs
|
- **Pleroma (2.10.2):** `images/pleroma` installs the OTP release, pinned by checksum, as Akkoma's does; it also needs
|
||||||
libvips, and `instance gen` asks about deduplicating uploads. Its federation runs on hackney, which trusts only
|
libvips, and `instance gen` asks about deduplicating uploads. Its federation runs on hackney, which trusts only
|
||||||
certifi's compiled-in roots, so the entrypoint points `:pleroma, :http, adapter` at the system CA bundle. It answers
|
certifi's compiled-in roots, so the entrypoint points `:pleroma, :http, adapter` at the system CA bundle. It answers
|
||||||
|
|||||||
@@ -635,6 +635,18 @@ attachment. The player is a card whose iframe loads from the remote host, which
|
|||||||
| Dislike counts; live state through `Update`; chapters and captions | P2 | own |
|
| Dislike counts; live state through `Update`; chapters and captions | P2 | own |
|
||||||
| Optionally, `View` sent from the instance actor (so it never names a persona) | P3 | — |
|
| Optionally, `View` sent from the instance actor (so it never names a persona) | P3 | — |
|
||||||
|
|
||||||
|
**Pasture evidence (2026-10-05, PeerTube 8.3.1, `tools/pasture/scenarios/peertube.sh`):** 24 checks pass:
|
||||||
|
- a persona finds a channel (a group account) and its owner's account, and follows the channel (accepted at once);
|
||||||
|
- a new video comes as the channel's `Announce`, not a `Create`, so it reaches the persona's home as the channel's boost
|
||||||
|
of the video, with a playable attachment served through the media proxy, which answers byte ranges;
|
||||||
|
- a reply to the video is a comment on PeerTube, and an answer to that comment threads under the video here;
|
||||||
|
- a like and its undo count on PeerTube; the video's renaming (`Update`) and its deletion reach us;
|
||||||
|
- the unfollow, and statistics.
|
||||||
|
|
||||||
|
PeerTube's own instance account announces each new video too, which we drop: nobody here follows it. Its users can
|
||||||
|
follow only PeerTube-like channels and accounts, never a persona. It checks the `Host` header against its own name,
|
||||||
|
without a port, before it gives out its OAuth client.
|
||||||
|
|
||||||
### Loops (1.0.0-beta.14) and Pixelfed (0.14.4)
|
### Loops (1.0.0-beta.14) and Pixelfed (0.14.4)
|
||||||
|
|
||||||
- **Loops:**
|
- **Loops:**
|
||||||
|
|||||||
@@ -57,3 +57,8 @@ pleroma.test {
|
|||||||
tls internal
|
tls internal
|
||||||
reverse_proxy pasture-pleroma:4000
|
reverse_proxy pasture-pleroma:4000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
peertube.test {
|
||||||
|
tls internal
|
||||||
|
reverse_proxy pasture-peertube:9000
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Iceshrimp.NET (2026.1): the strict one. AuthorizedFetch on (every fetch from it is signed, and it answers unsigned
|
# Iceshrimp.NET (2026.1): the strict one. AuthorizedFetch on (every fetch from it is signed, and it answers unsigned
|
||||||
# reads 401), full JSON-LD expansion that drops undefined terms, several reactions per user. .NET, so it trusts Caddy's CA
|
# reads 401), full JSON-LD expansion that drops undefined terms, several reactions per user. .NET, so it trusts Caddy's CA
|
||||||
# through SSL_CERT_FILE. On the shared Postgres (database iceshrimp); the town makes its users with its own CLI.
|
# through SSL_CERT_FILE. On the shared Postgres (database iceshrimp); the town makes its users through its own API.
|
||||||
ICESHRIMP_IMAGE=${ICESHRIMP_IMAGE:-iceshrimp.dev/iceshrimp/iceshrimp.net:v2026.1.2-beta}
|
ICESHRIMP_IMAGE=${ICESHRIMP_IMAGE:-iceshrimp.dev/iceshrimp/iceshrimp.net:v2026.1.2-beta}
|
||||||
. "$here/peers/shared.sh"
|
. "$here/peers/shared.sh"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# PeerTube 8.3: videos as ActivityPub objects (Video with its files, HLS playlists, captions and storyboards), comments
|
||||||
|
# as Notes, likes and dislikes, accounts and channels as separate actors. On the shared Postgres (database peertube) and
|
||||||
|
# Redis (db 4). Node, so it trusts Caddy's CA through NODE_EXTRA_CA_CERTS; its SSRF guard lets the pasture's public-looking
|
||||||
|
# subnet through. Transcoding is turned off once it is up, so a test video is served as it was uploaded. Its admin is
|
||||||
|
# root, with the password below; the town makes its users through the admin API.
|
||||||
|
PEERTUBE_IMAGE=${PEERTUBE_IMAGE:-docker.io/chocobozzz/peertube:v8.3.1}
|
||||||
|
PEERTUBE_ROOT_PASSWORD=Peertube-Pasture-Root-1
|
||||||
|
. "$here/peers/shared.sh"
|
||||||
|
|
||||||
|
peertube_up() {
|
||||||
|
shared_postgres_up
|
||||||
|
shared_redis_up
|
||||||
|
pg_db peertube pg_trgm unaccent
|
||||||
|
mkdir -p "$here/.state/peertube/data" "$here/.state/peertube/config"
|
||||||
|
podman run -d --replace --name pasture-peertube --network $net \
|
||||||
|
-e PEERTUBE_WEBSERVER_HOSTNAME=peertube.test -e PEERTUBE_WEBSERVER_PORT=443 -e PEERTUBE_WEBSERVER_HTTPS=true \
|
||||||
|
-e PEERTUBE_TRUST_PROXY='["loopback", "linklocal", "uniquelocal", "'"$subnet"'"]' \
|
||||||
|
-e PEERTUBE_DB_HOSTNAME=postgres -e PEERTUBE_DB_NAME=peertube -e PEERTUBE_DB_SUFFIX= \
|
||||||
|
-e PEERTUBE_DB_USERNAME=pasture -e PEERTUBE_DB_PASSWORD=pasture \
|
||||||
|
-e PEERTUBE_REDIS_HOSTNAME=redis -e PEERTUBE_REDIS_DB=4 \
|
||||||
|
-e PEERTUBE_SECRET=pasture-peertube-secret-0123456789abcdef -e PEERTUBE_ADMIN_EMAIL=admin@peertube.test \
|
||||||
|
-e PT_INITIAL_ROOT_PASSWORD=$PEERTUBE_ROOT_PASSWORD \
|
||||||
|
-e PEERTUBE_RATES_LIMIT_API_MAX=100000 -e PEERTUBE_RATES_LIMIT_LOGIN_MAX=100000 -e PEERTUBE_RATES_LIMIT_ACTIVITY_PUB_MAX=100000 \
|
||||||
|
-e PEERTUBE_SIGNUP_ENABLED=true \
|
||||||
|
-e NODE_EXTRA_CA_CERTS=/pasture/ca/root.crt \
|
||||||
|
-v "$here/.state/peertube/data:/data:Z" -v "$here/.state/peertube/config:/config:Z" -v "$ca:/pasture/ca:z,ro" \
|
||||||
|
"$PEERTUBE_IMAGE" >/dev/null
|
||||||
|
for _ in $(seq 1 150); do
|
||||||
|
site peertube.test -s -o /dev/null -w '%{http_code}' https://peertube.test:6443/api/v1/config 2>/dev/null | grep -q 200 && break
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
peertube_settle
|
||||||
|
echo "peertube: https://peertube.test:6443"
|
||||||
|
}
|
||||||
|
|
||||||
|
# root's token (the password grant with the instance's own client); transcoding off, signups open, any import allowed
|
||||||
|
peertube_settle() {
|
||||||
|
local client token
|
||||||
|
# PeerTube gives its client only to its own host, named without the port
|
||||||
|
client=$(site peertube.test -s -H "Host: peertube.test" https://peertube.test:6443/api/v1/oauth-clients/local)
|
||||||
|
token=$(site peertube.test -s -X POST https://peertube.test:6443/api/v1/users/token \
|
||||||
|
--data-urlencode "client_id=$(echo "$client" | python3 -c 'import sys,json; print(json.load(sys.stdin)["client_id"])')" \
|
||||||
|
--data-urlencode "client_secret=$(echo "$client" | python3 -c 'import sys,json; print(json.load(sys.stdin)["client_secret"])')" \
|
||||||
|
--data-urlencode grant_type=password --data-urlencode username=root --data-urlencode "password=$PEERTUBE_ROOT_PASSWORD" \
|
||||||
|
| python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')
|
||||||
|
echo "$token" > "$here/.state/peertube.token"
|
||||||
|
site peertube.test -s https://peertube.test:6443/api/v1/config/custom -H "Authorization: Bearer $token" | python3 -c '
|
||||||
|
import sys, json
|
||||||
|
c = json.load(sys.stdin)
|
||||||
|
c["transcoding"]["enabled"] = False
|
||||||
|
c["signup"]["enabled"] = True
|
||||||
|
c["signup"]["requiresEmailVerification"] = False
|
||||||
|
c["signup"]["limit"] = -1
|
||||||
|
c["user"]["videoQuota"] = -1
|
||||||
|
c["user"]["videoQuotaDaily"] = -1
|
||||||
|
print(json.dumps(c))' > "$here/.state/peertube/custom.json"
|
||||||
|
site peertube.test -s -o /dev/null -w '%{http_code}\n' -X PUT https://peertube.test:6443/api/v1/config/custom -H "Authorization: Bearer $token" \
|
||||||
|
-H 'Content-Type: application/json' --data @"$here/.state/peertube/custom.json"
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# PeerTube 8.3: a persona follows a channel; the channel's video arrives as a playable post, its file streamed through
|
||||||
|
# PrivaPub's media proxy with byte ranges; comments both ways thread; a like counts and its undo too; the video's edit
|
||||||
|
# and its deletion reach PrivaPub; statistics. PeerTube's users follow channels and accounts of PeerTube-like servers
|
||||||
|
# only, so nothing here has PeerTube following a persona.
|
||||||
|
PTB=https://peertube.test:6443
|
||||||
|
# PeerTube checks the Host against its own name, without the port
|
||||||
|
pcurl() { curl -sk --resolve peertube.test:6443:127.0.0.1 -H "Host: peertube.test" "$@"; }
|
||||||
|
PTROOT=$(cat "$here/.state/peertube.token" 2>/dev/null)
|
||||||
|
PTPASS=Peertube-Pasture-User-1
|
||||||
|
# a user's token through the instance's own OAuth client
|
||||||
|
pt_token() {
|
||||||
|
local client
|
||||||
|
client=$(pcurl "$PTB/api/v1/oauth-clients/local")
|
||||||
|
pcurl -X POST "$PTB/api/v1/users/token" \
|
||||||
|
--data-urlencode "client_id=$(echo "$client" | j "print(d['client_id'])")" \
|
||||||
|
--data-urlencode "client_secret=$(echo "$client" | j "print(d['client_secret'])")" \
|
||||||
|
--data-urlencode grant_type=password --data-urlencode "username=$1" --data-urlencode "password=$2" | j "print(d['access_token'])"
|
||||||
|
}
|
||||||
|
# a PrivaPub status in alice_peertube's home whose text or title has the given words; a channel shares its videos by
|
||||||
|
# boosting them, so it is the boosted status
|
||||||
|
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 '') or '$1' in ((o.get('privapub') or {}).get('title') or '')), ''))"; }
|
||||||
|
|
||||||
|
echo "peertube"
|
||||||
|
[ -n "$PTROOT" ] && ok "PeerTube root token" || { ko "PeerTube root token"; return 1; }
|
||||||
|
pcurl -o /dev/null -X POST -H "Authorization: Bearer $PTROOT" -H 'Content-Type: application/json' "$PTB/api/v1/users" \
|
||||||
|
-d "{\"username\":\"ptuser\",\"password\":\"$PTPASS\",\"email\":\"ptuser@peertube.test\",\"role\":2,\"videoQuota\":-1,\"videoQuotaDaily\":-1}"
|
||||||
|
TT=$(pt_token ptuser "$PTPASS")
|
||||||
|
TH="Authorization: Bearer $TT"
|
||||||
|
[ -n "$TT" ] && ok "PeerTube token for ptuser" || { ko "PeerTube token for ptuser"; return 1; }
|
||||||
|
channel=$(pcurl -H "$TH" "$PTB/api/v1/users/me" | j "print(d['videoChannels'][0]['name'])")
|
||||||
|
channel_id=$(pcurl -H "$TH" "$PTB/api/v1/users/me" | j "print(d['videoChannels'][0]['id'])")
|
||||||
|
PT=$(privapub_token alice_peertube)
|
||||||
|
PH="Authorization: Bearer $PT"
|
||||||
|
[ -n "$PT" ] && ok "PrivaPub token for alice_peertube" || { ko "PrivaPub token for alice_peertube"; return 1; }
|
||||||
|
|
||||||
|
echo " discovery and follows"
|
||||||
|
channel_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=$channel@peertube.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
|
||||||
|
[ -n "$channel_on_p" ] && ok "PrivaPub resolves the channel @$channel@peertube.test" || ko "PrivaPub cannot resolve the channel"
|
||||||
|
[ "$(curl -s -H "$PH" "$P/api/v1/accounts/$channel_on_p" | j "print(d['group'])")" = "True" ] && ok "the channel is a group account" || ko "the channel is not shown as a group"
|
||||||
|
account_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=ptuser@peertube.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
|
||||||
|
[ -n "$account_on_p" ] && ok "PrivaPub resolves the account @ptuser@peertube.test" || ko "PrivaPub cannot resolve the account"
|
||||||
|
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$channel_on_p/follow"
|
||||||
|
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$channel_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
|
||||||
|
&& ok "alice_peertube follows the channel (Accept arrived)" || ko "the channel's Accept never arrived"
|
||||||
|
until_true 30 '[ "$(pcurl -H "$TH" "$PTB/api/v1/video-channels/$channel/followers" | j "print(d[\"total\"])")" -ge 1 ]' \
|
||||||
|
&& ok "PeerTube lists alice_peertube among the channel's followers" || ko "PeerTube does not list the follower"
|
||||||
|
|
||||||
|
echo " videos"
|
||||||
|
video=$(pcurl -X POST -H "$TH" "$PTB/api/v1/videos/upload" -F "videofile=@$here/town/media/tiny.mp4;type=video/mp4" \
|
||||||
|
-F "channelId=$channel_id" -F "name=Hello PrivaPub from PeerTube" -F "description=a tiny test video" -F privacy=1 -F commentsPolicy=1 -F waitTranscoding=false)
|
||||||
|
video_id=$(echo "$video" | j "print(d['video']['shortUUID'])")
|
||||||
|
[ -n "$video_id" ] && ok "ptuser uploads a video" || ko "upload refused: $(echo "$video" | head -c 200)"
|
||||||
|
until_true 60 '[ -n "$(p_home_id "Hello PrivaPub from PeerTube")" ]' && ok "the video reaches alice_peertube's home, boosted by its channel" || ko "the video never arrived"
|
||||||
|
v_on_p=$(p_home_id "Hello PrivaPub from PeerTube")
|
||||||
|
v_status=$(curl -s -H "$PH" "$P/api/v1/statuses/$v_on_p")
|
||||||
|
[ "$(echo "$v_status" | j "print(d['media_attachments'][0]['type'])")" = "video" ] && ok "the video is a playable attachment" || ko "no video attachment: $(echo "$v_status" | j "print(d['media_attachments'])" | head -c 200)"
|
||||||
|
v_file=$(echo "$v_status" | j "print(d['media_attachments'][0]['url'])")
|
||||||
|
case "$v_file" in
|
||||||
|
*privapub.test/*) ok "its file is served through PrivaPub's proxy" ;;
|
||||||
|
*) ko "its file is not proxied: $v_file" ;;
|
||||||
|
esac
|
||||||
|
range=$(pfetch -s -o /dev/null -w '%{http_code} %{size_download}' -H 'Range: bytes=0-99' "$v_file")
|
||||||
|
[ "$range" = "206 100" ] && ok "the proxy answers a byte range (206, 100 bytes)" || ko "the proxy did not answer the range: $range"
|
||||||
|
|
||||||
|
echo " comments"
|
||||||
|
curl -s -o /dev/null -X POST -H "$PH" $P/api/v1/statuses -d "status=@ptuser@peertube.test lovely video&in_reply_to_id=$v_on_p&visibility=public"
|
||||||
|
until_true 30 '[ "$(pcurl "$PTB/api/v1/videos/$video_id/comment-threads" | j "print(any(\"lovely video\" in c[\"text\"] for c in d[\"data\"]))")" = "True" ]' \
|
||||||
|
&& ok "alice_peertube's reply is a comment on the video" || ko "the reply never became a comment"
|
||||||
|
thread=$(pcurl "$PTB/api/v1/videos/$video_id/comment-threads" | j "print(next(c['id'] for c in d['data'] if 'lovely video' in c['text']))")
|
||||||
|
pcurl -o /dev/null -X POST -H "$TH" -H 'Content-Type: application/json' "$PTB/api/v1/videos/$video_id/comments/$thread" -d '{"text":"thank you from PeerTube"}'
|
||||||
|
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$v_on_p/context" | j "print(any(\"thank you from PeerTube\" in s[\"content\"] for s in d[\"descendants\"]))")" = "True" ]' \
|
||||||
|
&& ok "ptuser's answer threads under the video on PrivaPub" || ko "PeerTube's answer missing from the thread"
|
||||||
|
|
||||||
|
echo " likes"
|
||||||
|
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$v_on_p/favourite"
|
||||||
|
until_true 30 '[ "$(pcurl "$PTB/api/v1/videos/$video_id" | j "print(d[\"likes\"])")" = "1" ]' && ok "alice_peertube's like counts on PeerTube" || ko "like not counted on PeerTube"
|
||||||
|
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$v_on_p/unfavourite"
|
||||||
|
until_true 30 '[ "$(pcurl "$PTB/api/v1/videos/$video_id" | j "print(d[\"likes\"])")" = "0" ]' && ok "alice_peertube's unlike reaches PeerTube" || ko "unlike not applied on PeerTube"
|
||||||
|
|
||||||
|
echo " edits and deletes"
|
||||||
|
pcurl -o /dev/null -X PUT -H "$TH" -H 'Content-Type: application/json' "$PTB/api/v1/videos/$video_id" -d '{"name":"Hello again from PeerTube","description":"renamed"}'
|
||||||
|
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$v_on_p" | j "print(\"Hello again from PeerTube\" in json.dumps(d))")" = "True" ]' \
|
||||||
|
&& ok "the video's new name reaches PrivaPub" || ko "the edit never arrived"
|
||||||
|
pcurl -o /dev/null -X DELETE -H "$TH" "$PTB/api/v1/videos/$video_id"
|
||||||
|
until_true 30 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$v_on_p")" = "404" ]' \
|
||||||
|
&& ok "the video's deletion reaches PrivaPub" || ko "the deleted video is still on PrivaPub"
|
||||||
|
|
||||||
|
echo " unfollow"
|
||||||
|
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$channel_on_p/unfollow"
|
||||||
|
until_true 30 '[ "$(pcurl -H "$TH" "$PTB/api/v1/video-channels/$channel/followers" | j "print(d[\"total\"])")" = "0" ]' \
|
||||||
|
&& ok "the unfollow reaches PeerTube" || ko "PeerTube still lists alice_peertube"
|
||||||
|
|
||||||
|
echo " statistics"
|
||||||
|
stats_check peertube.test peertube
|
||||||
@@ -77,7 +77,13 @@ class LemmyApi(Driver):
|
|||||||
|
|
||||||
# -- communities and people
|
# -- communities and people
|
||||||
def community(self, s, name, title):
|
def community(self, s, name, title):
|
||||||
|
try:
|
||||||
return self.lm(s, "POST", "community", {"name": name, "title": title})["community_view"]["community"]
|
return self.lm(s, "POST", "community", {"name": name, "title": title})["community_view"]["community"]
|
||||||
|
except HttpError as e:
|
||||||
|
# a run on accounts an earlier run made finds the community it made
|
||||||
|
if "already_exists" not in str(e):
|
||||||
|
raise
|
||||||
|
return self.lm(s, "GET", "community", params={"name": name})["community_view"]["community"]
|
||||||
|
|
||||||
def resolve_community(self, s, ref):
|
def resolve_community(self, s, ref):
|
||||||
"""ref: !name@host"""
|
"""ref: !name@host"""
|
||||||
|
|||||||
@@ -261,6 +261,11 @@ class Seeder:
|
|||||||
member=member, state="local")
|
member=member, state="local")
|
||||||
return
|
return
|
||||||
member_uri = self.session(member).actor_uri
|
member_uri = self.session(member).actor_uri
|
||||||
|
# a member an earlier run on these accounts approved asks nothing again
|
||||||
|
if podman.mongo(f"db.Follower.findOne({{LocalActorId: '{group['id']}', ActorURI: '{member_uri}', IsAccepted: true}}, {{_id: 1}})"):
|
||||||
|
self.ledger.add(type="relation", n=s["n"], verb="approve", actor=s["actor"], group=s["args"]["group"],
|
||||||
|
member=member, state="accepted", already=True)
|
||||||
|
return
|
||||||
for _ in range(30):
|
for _ in range(30):
|
||||||
found = podman.mongo(f"db.Follower.findOne({{LocalActorId: '{group['id']}', IsAccepted: false}}, {{ActorURI: 1}})")
|
found = podman.mongo(f"db.Follower.findOne({{LocalActorId: '{group['id']}', IsAccepted: false}}, {{ActorURI: 1}})")
|
||||||
pending = podman.mongo(f"db.Follower.find({{LocalActorId: '{group['id']}', IsAccepted: false}}, {{ActorURI: 1}}).toArray()") or []
|
pending = podman.mongo(f"db.Follower.find({{LocalActorId: '{group['id']}', IsAccepted: false}}, {{ActorURI: 1}}).toArray()") or []
|
||||||
|
|||||||
Reference in new issue
Block a user