Events integration with ActivityPub #56
Labels
No labels
Doing
P1: critical
P2: important
P3: useful
P4: nice to have
P5: reminder
To Do
bug
duplicate
invalid
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
peacelink/phpeace#56
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Native ActivityPub federation for the PeaceLink calendar
Goal
Make the PeaceLink calendar a fediverse actor (e.g.
@calendario@peacelink.it) that people on Mastodon, Gancio, Mobilizon and other ActivityPub servers can follow. Every approved event is published as an ActivityPubEvent; edits, cancellations and removals are propagated to followers.Events stay hosted on
peacelink.it: no second platform, no sync with an external service.Current state
https://www.peacelink.it/js/events.php(approved events only, CET/CEST, Italian labels, 5 min cache)mcp-server.peacelink.it(get_upcoming_events)urlfield), iCal/webcal/Google export linksScope
In scope (v1)
Create{Event}activitiesFollow,Undo{Follow},Delete(of remote actors)Create,Update,Deletetriggered by the event lifecycle in PhPeaceEventobject forAccept: application/activity+json)Out of scope (v1, possible later)
Joinactivities1. WebFinger
GET /.well-known/webfinger?resource=acct:calendario@peacelink.itapplication/jrd+jsonpeacelink.ititself (the domain in the handle). Ifpeacelink.it(withoutwww) redirects towww, check that the redirect preserves path and query, or serve WebFinger on both hosts.Access-Control-Allow-Origin: *is useful for web clients.Optional but recommended: NodeInfo (
/.well-known/nodeinfo→/nodeinfo/2.1) so servers can identify the software (PhPeace).2. Actor
GET https://www.peacelink.it/calendario/ap/actor(Content-Typeapplication/activity+json)type: Servicemarks it as a bot account on Mastodon.https://www.peacelink.it/...consistently for all IDs. IDs must never change once published.3. Event object
Each approved event gets a stable ActivityPub ID. Suggested: reuse the event page URL as
id, so content negotiation (section 8) resolves it.Mapping from the PhPeace event to
Event:idurltype"Event"nametitlecontentdescriptionsummarystartTimestart2026-10-12T18:00:00+02:00); compute CET/CEST viaEurope/RomeendTimeendpublishedupdatedUpdateattributedToto["https://www.w3.org/ns/activitystreams#Public"]cc[followers collection]locationPlace(see below)is_online = 1attachmentImagefromimage/largemediaType,name(alt text = title if nothing better)tagHashtags#pace,#disarmo, region hashtag, eventtypecategorytypetimezone"Europe/Rome"ical:statusCONFIRMED/CANCELLEDisOnlineis_onlineLocation:
For online events: omit
locationor set aPlace/VirtualLocationwith the streamlink; keeplinkalso incontent.All-day events: Mobilizon/Gancio do not have a uniform all-day flag; use
startTimeat 00:00 andendTimeat 23:59 local, and say it incontent.Read FEP-8a8e ("A common approach to using the Event object type") before implementing; it documents what Mobilizon, Gancio, Friendica etc. actually emit and consume. Test against at least Mastodon, Gancio and Mobilizon.
Content sanitization
p, br, a, strong, em, ul, ol, li, blockquotecontentwith date/time, place and the event URL in human-readable Italian, because Mastodon does not render Event fields: it converts theEventinto a status showing essentiallyname+url. A meaningfulcontent/summaryis what Mastodon users will actually read.Privacy
email(organizer email). Once delivered it is copied to many servers and cannot be reliably retracted; it also feeds spam harvesters.contact_nameis fine to include incontent.place_detailsmay contain personal info (phone numbers etc.).4. Outbox
GET /calendario/ap/outbox→OrderedCollectionwithtotalItemsand paginatedfirst/next(OrderedCollectionPage), newest first, containingCreateactivities.Activity example:
Note: Mastodon does not backfill from the outbox when someone follows. New followers only see events published after they follow. Acceptable for v1.
5. Inbox
Endpoints:
POST /calendario/ap/inbox(actor inbox)POST /ap/inbox(shared inbox, same handler)For every request:
Digestheader matches the body.202 Acceptedquickly.Handled activities:
Follow(object = our actor)Accept{Follow}to follower's inboxUndo{Follow}Delete(actor deleted itself)Like,Announce,Create{Note}replies...)The
Acceptmust embed or reference the originalFollowactivity (embed the full object; Mastodon expects it).Followers collection
GET /calendario/ap/followers: returnOrderedCollectionwithtotalItemsonly (don't expose the list).6. Event lifecycle hooks in PhPeace
Create{Event}Update{Event}with newupdatedUpdate{Event}withical:status: CANCELLEDand a "ANNULLATO" prefix inname/contentDelete{Tombstone}with the event ID; event URL returns 410 for AP requestsDebounce updates: editors often save several times in a row. Wait e.g. 10 minutes after the last edit before sending
Update, and skip it if the federated representation hash is unchanged.Store per event:
ap_published_at,ap_last_hash,ap_deleted.7. HTTP Signatures
Outgoing (all POSTs to remote inboxes, and GETs of remote actors when verifying):
Host,Date(RFC 7231 format),Digest: SHA-256=<base64>,Content-Type: application/activity+jsonSignature: keyId="https://www.peacelink.it/calendario/ap/actor#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="<base64>"(request-target): post /inbox,host: ...,date: ...,digest: ...joined by\nUser-Agentidentifying PhPeace and the site URLIncoming verification:
Signatureheader, fetchkeyId(the remote actor, cached), verify with the public keyDatewithin ±12 hoursSome servers (secure/authorized fetch mode) require signed GETs too: sign GET requests for remote actors with headers
(request-target) host date.Use an existing PHP library if one fits PhPeace's dependencies, otherwise this is ~150 lines with
openssl_sign/openssl_verify.8. Content negotiation on event pages
Event page URL with
Accept: application/activity+jsonorapplication/ld+json; profile="https://www.w3.org/ns/activitystreams"→ return theEventJSON instead of HTML. Also add to the HTML head:This lets users paste an event URL into Mastodon/Mobilizon search and get the event. Check the caching layer (Varnish/CDN) varies on
Accept, or serve the JSON from a separate path and return a redirect/Linkheader.9. Delivery queue
ap_delivery: id, activity_id, target_inbox, payload, attempts, next_attempt_at, last_error, status10. Data model (new tables)
ap_keys(or config/secret): private/public keyap_followers: actor_id (unique), inbox, shared_inbox, followed_atap_remote_actors: actor_id, public_key_pem, fetched_at (cache)ap_delivery: as aboveap_published_at,ap_last_hash,ap_deleted, plus a reliableupdated_atif missing11. Small improvements to
/js/events.php(useful anyway)idandupdatedfield per eventstart/end(or an explicittimezonefield)emailpublicly12. Testing
curl -H 'Accept: application/activity+json'Acceptarrives and the follow is confirmedUpdatereaches remote servers; delete → removed remotelyAcceptance criteria
@calendario@peacelink.itis searchable and followable from MastodonOpen questions
calendario,eventi, orcalendar?@puglia@peacelink.it)? Theregionfilter makes it cheap; could be v2.#pace #disarmo+ region + event type?)