fedipod-server 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -6
- package/dist/door.d.ts +4 -0
- package/dist/door.js +31 -0
- package/dist/fedipod.d.ts +16 -0
- package/dist/fedipod.js +13 -0
- package/dist/front.d.ts +3 -0
- package/dist/front.js +94 -0
- package/dist/handler.d.ts +36 -101
- package/dist/handler.js +85 -645
- package/dist/handler.jsonld +2 -30
- package/dist/identities.d.ts +62 -0
- package/dist/identities.js +236 -0
- package/dist/mounts.d.ts +43 -0
- package/dist/mounts.js +123 -0
- package/dist/opt-in.d.ts +46 -0
- package/dist/opt-in.js +176 -0
- package/package.json +10 -18
- package/lib/client/c2s.mjs +0 -636
- package/lib/client/localapi.mjs +0 -87
- package/lib/client/masto/accounts.mjs +0 -310
- package/lib/client/masto/body.mjs +0 -156
- package/lib/client/masto/index.mjs +0 -206
- package/lib/client/masto/instance.mjs +0 -112
- package/lib/client/masto/media.mjs +0 -79
- package/lib/client/masto/oauth.mjs +0 -656
- package/lib/client/masto/render.mjs +0 -518
- package/lib/client/masto/statuses.mjs +0 -372
- package/lib/client/masto/timelines.mjs +0 -438
- package/lib/client/oidc-auth.mjs +0 -62
- package/lib/client/streaming.mjs +0 -154
- package/lib/client/webpush.mjs +0 -98
- package/lib/connections/acctfeed.mjs +0 -257
- package/lib/connections/atproto.mjs +0 -309
- package/lib/connections/bskyfeed.mjs +0 -243
- package/lib/connections/bskygroup.mjs +0 -162
- package/lib/connections/fediacct.mjs +0 -253
- package/lib/connections/import.mjs +0 -357
- package/lib/connections/tagfeed.mjs +0 -212
- package/lib/connections/vault.mjs +0 -114
- package/lib/core/as2.mjs +0 -206
- package/lib/core/contexts/activitystreams.json +0 -379
- package/lib/core/contexts/anno.json +0 -126
- package/lib/core/contexts/did-v1.json +0 -57
- package/lib/core/contexts/fep-5711.json +0 -36
- package/lib/core/contexts/gotosocial.json +0 -86
- package/lib/core/contexts/identity-v1.json +0 -152
- package/lib/core/contexts/index.mjs +0 -63
- package/lib/core/contexts/join-lemmy.json +0 -33
- package/lib/core/contexts/joinmastodon.json +0 -28
- package/lib/core/contexts/litepub-0.1.json +0 -60
- package/lib/core/contexts/map.json +0 -19
- package/lib/core/contexts/miscellany.json +0 -19
- package/lib/core/contexts/quotes.json +0 -18
- package/lib/core/contexts/schemaorg.json +0 -8845
- package/lib/core/contexts/security-data-integrity-v1.json +0 -78
- package/lib/core/contexts/security-data-integrity-v2.json +0 -81
- package/lib/core/contexts/security-multikey-v1.json +0 -35
- package/lib/core/contexts/security-v1.json +0 -74
- package/lib/core/contexts/webfinger.json +0 -10
- package/lib/core/deliver.mjs +0 -387
- package/lib/core/graphview.mjs +0 -327
- package/lib/core/intake/activities.mjs +0 -649
- package/lib/core/intake/activity.mjs +0 -318
- package/lib/core/intake/channel.mjs +0 -144
- package/lib/core/intake/group.mjs +0 -258
- package/lib/core/intake/index.mjs +0 -732
- package/lib/core/intake/notes.mjs +0 -367
- package/lib/core/intake/verify.mjs +0 -142
- package/lib/core/keys.mjs +0 -153
- package/lib/core/lease.mjs +0 -206
- package/lib/core/place.mjs +0 -82
- package/lib/core/polls.mjs +0 -105
- package/lib/core/profile-page.mjs +0 -104
- package/lib/core/proof.mjs +0 -79
- package/lib/core/publisher/collections.mjs +0 -272
- package/lib/core/publisher/index.mjs +0 -522
- package/lib/core/publisher/notes.mjs +0 -519
- package/lib/core/publisher/own.mjs +0 -143
- package/lib/core/publisher/questions.mjs +0 -241
- package/lib/core/publisher/restore.mjs +0 -224
- package/lib/core/shapes/activitystreams.ttl +0 -157
- package/lib/core/shapes/index.mjs +0 -132
- package/lib/core/shapes/shapes-text.mjs +0 -13
- package/lib/core/social.mjs +0 -561
- package/lib/core/storage.mjs +0 -176
- package/lib/core/store.mjs +0 -578
- package/lib/core/wire-quotes.mjs +0 -71
- package/lib/core/wire.mjs +0 -925
- package/lib/device/account.mjs +0 -87
- package/lib/device/admin/index.mjs +0 -13
- package/lib/device/admin/origins.mjs +0 -35
- package/lib/device/admin/routes/connections.mjs +0 -144
- package/lib/device/admin/routes/gateway.mjs +0 -199
- package/lib/device/admin/routes/lifecycle.mjs +0 -200
- package/lib/device/admin/routes/owner.mjs +0 -322
- package/lib/device/admin/routes/setup.mjs +0 -428
- package/lib/device/admin/routes/social.mjs +0 -188
- package/lib/device/admin/server.mjs +0 -95
- package/lib/device/admin/static.mjs +0 -247
- package/lib/device/admin/surface.mjs +0 -315
- package/lib/device/certs.mjs +0 -281
- package/lib/device/cli/commands/account.mjs +0 -586
- package/lib/device/cli/commands/run.mjs +0 -278
- package/lib/device/cli/commands/service.mjs +0 -221
- package/lib/device/cli/commands/setup.mjs +0 -489
- package/lib/device/cli/commands/state.mjs +0 -561
- package/lib/device/cli/context.mjs +0 -288
- package/lib/device/export-collections.mjs +0 -161
- package/lib/device/gateway-move.mjs +0 -58
- package/lib/device/home.mjs +0 -161
- package/lib/device/migrate.mjs +0 -115
- package/lib/device/ports.mjs +0 -27
- package/lib/device/remote.mjs +0 -54
- package/lib/device/setup.mjs +0 -496
- package/lib/device/update.mjs +0 -96
- package/lib/gateway/caches.mjs +0 -36
- package/lib/gateway/directory.mjs +0 -90
- package/lib/gateway/front-core.mjs +0 -966
- package/lib/gateway/gateway-core.mjs +0 -244
- package/lib/gateway/headers.mjs +0 -49
- package/lib/gateway/httpsig.mjs +0 -159
- package/lib/gateway/notices.mjs +0 -84
- package/lib/gateway/quiet.mjs +0 -189
- package/lib/gateway/relay-extras.mjs +0 -89
- package/lib/gateway/token-claims.mjs +0 -16
- package/lib/pod/README.md +0 -61
- package/lib/pod/actor.mjs +0 -96
- package/lib/pod/collection.mjs +0 -77
- package/lib/pod/containers.mjs +0 -140
- package/lib/pod/discovery.mjs +0 -46
- package/lib/pod/featured.mjs +0 -18
- package/lib/pod/followers.mjs +0 -24
- package/lib/pod/following.mjs +0 -10
- package/lib/pod/http.mjs +0 -74
- package/lib/pod/inbox.mjs +0 -166
- package/lib/pod/links.mjs +0 -35
- package/lib/pod/location.mjs +0 -52
- package/lib/pod/media.mjs +0 -27
- package/lib/pod/notes.mjs +0 -78
- package/lib/pod/notifications.mjs +0 -89
- package/lib/pod/outbox.mjs +0 -18
- package/lib/pod/package.json +0 -19
- package/lib/pod/policy.mjs +0 -58
- package/lib/pod/private.mjs +0 -19
- package/lib/pod/root.mjs +0 -104
- package/lib/pod/state.mjs +0 -40
- package/lib/pod/transport.mjs +0 -674
- package/lib/pod/type-index.mjs +0 -101
- package/lib/pod/urls.mjs +0 -91
- package/lib/server/embed.mjs +0 -424
- package/lib/session/README.md +0 -137
- package/lib/session/demo.html +0 -94
- package/lib/session/fedi-account.mjs +0 -417
- package/lib/session/fedi-login.mjs +0 -123
- package/lib/session/oidc-session.mjs +0 -233
- package/lib/session/package.json +0 -49
- package/lib/shared/guard.mjs +0 -213
- package/lib/shared/links.mjs +0 -9
- package/lib/shared/safefetch.mjs +0 -235
- package/lib/shared/ua.mjs +0 -29
- package/phanpy/dist/404.html +0 -40
- package/phanpy/dist/_headers +0 -2
- package/phanpy/dist/apple-touch-icon.png +0 -0
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
- package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
- package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
- package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
- package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
- package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
- package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
- package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
- package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
- package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
- package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
- package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
- package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
- package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
- package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
- package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
- package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
- package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
- package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
- package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
- package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
- package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
- package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
- package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
- package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
- package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
- package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
- package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
- package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
- package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
- package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
- package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
- package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
- package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
- package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
- package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
- package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
- package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
- package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
- package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
- package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
- package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
- package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
- package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
- package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
- package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
- package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
- package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
- package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
- package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
- package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
- package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
- package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
- package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
- package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
- package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
- package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
- package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
- package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
- package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
- package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
- package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
- package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
- package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
- package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
- package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
- package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
- package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
- package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
- package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
- package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
- package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
- package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
- package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
- package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
- package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
- package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
- package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
- package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
- package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
- package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
- package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
- package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
- package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
- package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
- package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
- package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
- package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
- package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
- package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
- package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
- package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
- package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
- package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
- package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
- package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
- package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
- package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
- package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
- package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
- package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
- package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
- package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
- package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
- package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
- package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
- package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
- package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
- package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
- package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
- package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
- package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
- package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
- package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
- package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
- package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
- package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
- package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
- package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
- package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
- package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
- package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
- package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
- package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
- package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
- package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
- package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
- package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
- package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
- package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
- package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
- package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
- package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
- package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
- package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
- package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
- package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
- package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
- package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
- package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
- package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
- package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
- package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
- package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
- package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
- package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
- package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
- package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
- package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
- package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
- package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
- package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
- package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
- package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
- package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
- package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
- package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
- package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
- package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
- package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
- package/phanpy/dist/compose/index.html +0 -20
- package/phanpy/dist/favicon.ico +0 -0
- package/phanpy/dist/index.html +0 -73
- package/phanpy/dist/logo-192.png +0 -0
- package/phanpy/dist/logo-512.png +0 -0
- package/phanpy/dist/logo-badge-72.png +0 -0
- package/phanpy/dist/logo-maskable-512.png +0 -0
- package/phanpy/dist/logo-monochrome-512.png +0 -0
- package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
- package/phanpy/dist/manifest.webmanifest +0 -1
- package/phanpy/dist/og-image-2.jpg +0 -0
- package/phanpy/dist/og-image.png +0 -0
- package/phanpy/dist/robots.txt +0 -2
- package/phanpy/dist/sw.js +0 -2
- package/phanpy/dist/sw.js.map +0 -1
- package/phanpy/dist/version.json +0 -1
- package/run-agent.mjs +0 -899
- package/vendor/gate.cjs +0 -138
- package/vendor/idp-grant.cjs +0 -394
- package/vendor/jfetch.cjs +0 -16
- package/web/admin/actors.js +0 -147
- package/web/admin/bar.css +0 -126
- package/web/admin/bar.js +0 -99
- package/web/admin/client/client.js +0 -66
- package/web/admin/client/index.html +0 -69
- package/web/admin/client-pick.js +0 -79
- package/web/admin/common.js +0 -23
- package/web/admin/connections.js +0 -112
- package/web/admin/gateway.js +0 -133
- package/web/admin/group.js +0 -258
- package/web/admin/index.html +0 -612
- package/web/admin/notices-bar.js +0 -90
- package/web/admin/oauth-signin.mjs +0 -37
- package/web/admin/record.js +0 -393
- package/web/admin/setup/index.html +0 -257
- package/web/admin/setup/setup.js +0 -339
- package/web/admin/tokens.css +0 -68
- package/web/admin/upkeep.js +0 -177
- package/web/admin/window.css +0 -55
- package/web/admin/window.js +0 -136
- package/web/app/README.md +0 -81
- package/web/app/admin-facade.mjs +0 -588
- package/web/app/agent.mjs +0 -672
- package/web/app/atproto-browser.mjs +0 -86
- package/web/app/authorize_interaction.html +0 -56
- package/web/app/authorize_interaction.js +0 -69
- package/web/app/boot.mjs +0 -616
- package/web/app/deliver-relay.mjs +0 -129
- package/web/app/dist/boot.js +0 -34562
- package/web/app/dist/boot.js.map +0 -7
- package/web/app/dist/sw.js +0 -73456
- package/web/app/dist/sw.js.map +0 -7
- package/web/app/fediacct-browser.mjs +0 -146
- package/web/app/gateway-move.mjs +0 -60
- package/web/app/idb-kv.mjs +0 -35
- package/web/app/index.html +0 -258
- package/web/app/keys-browser.mjs +0 -71
- package/web/app/keystore.mjs +0 -82
- package/web/app/oidc-session.mjs +0 -6
- package/web/app/pod-auth.mjs +0 -248
- package/web/app/pod-remote.mjs +0 -84
- package/web/app/shims/_globals.mjs +0 -2
- package/web/app/shims/fedify-sig.mjs +0 -77
- package/web/app/shims/node-crypto.mjs +0 -113
- package/web/app/shims/node-fs.mjs +0 -17
- package/web/app/shims/node-path.mjs +0 -19
- package/web/app/shims/node-url.mjs +0 -7
- package/web/app/shims/prelude.js +0 -58
- package/web/app/shims/safefetch.mjs +0 -58
- package/web/app/shims/shapes-text.mjs +0 -8
- package/web/app/shims/web-push.mjs +0 -14
- package/web/app/signup.mjs +0 -357
- package/web/app/sw-src.mjs +0 -220
- package/web/app/update.js +0 -70
- package/web/front/admin.html +0 -55
- package/web/front/admin.js +0 -89
- package/web/front/install.sh +0 -69
- package/web/front/new-account.html +0 -35
- package/web/front/new-account.js +0 -13
- package/web/front/notices.html +0 -69
- package/web/front/notices.js +0 -107
- package/web/front/run.html +0 -64
- package/web/front/run.js +0 -221
- package/web/front/solid-oidc-client.js +0 -6
- package/web/icons/forum.svg +0 -11
- package/web/icons/gateway.svg +0 -14
- package/web/icons/local.svg +0 -11
package/lib/core/social.mjs
DELETED
|
@@ -1,561 +0,0 @@
|
|
|
1
|
-
// social.mjs — outbound social actions (follow, like, announce, delete)
|
|
2
|
-
// shared by the admin API and the Mastodon-API facade. Each action delivers
|
|
3
|
-
// the activity and mirrors state into the store; the store keeps the
|
|
4
|
-
// activity a later Undo must name.
|
|
5
|
-
|
|
6
|
-
import crypto from 'node:crypto';
|
|
7
|
-
import * as wire from './wire.mjs';
|
|
8
|
-
import * as podNotes from '../pod/notes.mjs';
|
|
9
|
-
import { dropFollower } from './store.mjs';
|
|
10
|
-
|
|
11
|
-
// WebFinger, ours rather than fedify's — v2 dropped lookupWebFinger.
|
|
12
|
-
//
|
|
13
|
-
// Better here anyway: the host comes out of a handle, and a handle can arrive
|
|
14
|
-
// in a MENTION inside a post somebody else wrote, so it is remote input and
|
|
15
|
-
// belongs behind the same SSRF guard as every other outbound request. fedify's
|
|
16
|
-
// lookup did not go through ours. https only, because a handle resolved over
|
|
17
|
-
// plaintext is a handle anyone on the path can answer for.
|
|
18
|
-
//
|
|
19
|
-
// `fetchImpl`, when given, makes the request instead of safeFetch: the agent's
|
|
20
|
-
// own remote read, which in the browser build is the relay — a server-side
|
|
21
|
-
// fetch, so the lookup does not depend on the far pod host answering a
|
|
22
|
-
// browser's cross-origin request. Every other remote read there goes the
|
|
23
|
-
// same way; WebFinger went direct and was the one lookup that could fail
|
|
24
|
-
// while the rest worked.
|
|
25
|
-
export async function lookupWebFinger(acct, { fetchImpl = null } = {}) {
|
|
26
|
-
const clean = String(acct || '').replace(/^acct:/, '');
|
|
27
|
-
const at = clean.lastIndexOf('@');
|
|
28
|
-
if (at < 1) return null;
|
|
29
|
-
const [user, host] = [clean.slice(0, at), clean.slice(at + 1)];
|
|
30
|
-
if (!host || /[/\\?#]/.test(host)) return null;
|
|
31
|
-
const { safeFetch, readCapped } = await import('../shared/safefetch.mjs');
|
|
32
|
-
const url = `https://${host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${clean}`)}`;
|
|
33
|
-
const headers = { accept: 'application/jrd+json, application/json' };
|
|
34
|
-
const res = await (fetchImpl ? fetchImpl(url, { headers }) : safeFetch(url, { headers })).catch(() => null);
|
|
35
|
-
if (!res || res.status >= 400) return null;
|
|
36
|
-
let jrd;
|
|
37
|
-
try { jrd = JSON.parse(await readCapped(res, 256 * 1024)); } catch { return null; }
|
|
38
|
-
// The JRD says who it is about. Nothing checked it, so a host answering every
|
|
39
|
-
// query with one account's document made `@anyone@that.host` resolve to that
|
|
40
|
-
// account — and it was then stored under the handle that was asked for, not
|
|
41
|
-
// the one it turned out to be.
|
|
42
|
-
const subject = String(jrd?.subject || '').replace(/^acct:/, '').toLowerCase();
|
|
43
|
-
if (subject && subject !== clean.toLowerCase()) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
return jrd;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const selfLink = (jrd) => jrd?.links?.find(l => l.rel === 'self' &&
|
|
50
|
-
/application\/(activity\+json|ld\+json)/.test(l.type || ''));
|
|
51
|
-
|
|
52
|
-
// A WebFinger server can name an actor on ANY host, and until this ran we
|
|
53
|
-
// believed it. The handle is remote input — it arrives in the Mention tags of a
|
|
54
|
-
// note somebody else wrote — so evil.example could answer for `@x@evil.example`
|
|
55
|
-
// with a real, busy actor elsewhere, and a reply that carried that mention was
|
|
56
|
-
// addressed, tagged and DELIVERED to a third party who had nothing to do with
|
|
57
|
-
// the thread, over our signature.
|
|
58
|
-
//
|
|
59
|
-
// The host cannot simply be required to match: `@user@example.com` served by
|
|
60
|
-
// mastodon.example is the ordinary delegated setup and the whole point of
|
|
61
|
-
// WebFinger. So ask the ACTOR's own host who it says that actor is, and require
|
|
62
|
-
// it to come back to the same document. A server may name anyone; it cannot
|
|
63
|
-
// make that person's server agree. This is the round trip Mastodon does.
|
|
64
|
-
//
|
|
65
|
-
// Only when the two hosts differ — same-host is self-consistent by
|
|
66
|
-
// construction, so the common case costs no extra request.
|
|
67
|
-
export async function confirmDelegation(asked, doc, lookup = lookupWebFinger) {
|
|
68
|
-
let home;
|
|
69
|
-
try { home = new URL(doc.id).host.toLowerCase(); }
|
|
70
|
-
catch { throw new Error(`actor id is not a URL (${doc.id})`); }
|
|
71
|
-
if (home === asked.toLowerCase()) return;
|
|
72
|
-
const user = doc.preferredUsername;
|
|
73
|
-
if (!user) {
|
|
74
|
-
throw new Error(`${asked} points at ${doc.id} on another host, which names no `
|
|
75
|
-
+ 'username to confirm it by');
|
|
76
|
-
}
|
|
77
|
-
const back = await lookup(`acct:${user}@${home}`);
|
|
78
|
-
if (selfLink(back)?.href !== doc.id) {
|
|
79
|
-
throw new Error(`${asked} claims ${doc.id}, but ${home} does not agree that is `
|
|
80
|
-
+ `@${user}@${home} — refusing to take one host's word for another's actor`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// acct:user@host → verified actor doc (cached by fetchAP), or throws.
|
|
85
|
-
export async function resolveHandle(agent, handle) {
|
|
86
|
-
const clean = String(handle || '').replace(/^@/, '');
|
|
87
|
-
if (!/^[^@]+@[^@]+$/.test(clean)) throw new Error('handle must look like user@host');
|
|
88
|
-
if (agent.store.isBlocked('https://' + clean.split('@')[1] + '/')) throw new Error('domain is blocked');
|
|
89
|
-
// Through the agent's own remote read where it has one — the relay, in the
|
|
90
|
-
// browser build — and directly otherwise.
|
|
91
|
-
const remote = agent.intake?.deliverer?.signedFetch
|
|
92
|
-
? (u, i) => agent.intake.deliverer.signedFetch(u, i) : null;
|
|
93
|
-
const lookup = (a) => lookupWebFinger(a, { fetchImpl: remote });
|
|
94
|
-
const jrd = await lookup('acct:' + clean);
|
|
95
|
-
const self = selfLink(jrd);
|
|
96
|
-
if (!self?.href) throw new Error(`webfinger found no actor for ${clean}`);
|
|
97
|
-
const doc = await agent.intake.fetchAP(self.href);
|
|
98
|
-
if (!doc?.id) throw new Error(`actor document unusable for ${clean}`);
|
|
99
|
-
if (agent.store.isBlocked(doc.id)) throw new Error('actor is blocked');
|
|
100
|
-
await confirmDelegation(clean.slice(clean.lastIndexOf('@') + 1), doc, lookup);
|
|
101
|
-
// Counts live in the collections, not the actor document, so a client asking
|
|
102
|
-
// "who is this" would otherwise be told everyone has none. Two GETs, and only
|
|
103
|
-
// when someone asked by name — never on the drain path.
|
|
104
|
-
await cacheCounts(agent, doc);
|
|
105
|
-
return doc;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// totalItems off the followers/following collections, stored beside the cached
|
|
109
|
-
// actor. Best effort: a collection that will not answer leaves the count unset
|
|
110
|
-
// rather than reported as zero, which is a different claim.
|
|
111
|
-
async function cacheCounts(agent, doc) {
|
|
112
|
-
const total = async (url) => {
|
|
113
|
-
if (!url) return null;
|
|
114
|
-
try {
|
|
115
|
-
const res = await agent.intake.fetchAP(url);
|
|
116
|
-
return Number.isInteger(res?.totalItems) ? res.totalItems : null;
|
|
117
|
-
} catch { return null; }
|
|
118
|
-
};
|
|
119
|
-
const [followers, following] = await Promise.all([total(doc.followers), total(doc.following)]);
|
|
120
|
-
if (followers === null && following === null) return;
|
|
121
|
-
const actors = agent.store.getActors();
|
|
122
|
-
if (!actors[doc.id]) return;
|
|
123
|
-
actors[doc.id] = { ...actors[doc.id], counts: { followers, following } };
|
|
124
|
-
agent.store.write('actors.json', actors);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// `doc` skips the fetch when the caller already resolved the actor; a bulk
|
|
128
|
-
// import passes publish:false and republishes the collections once at the end.
|
|
129
|
-
export async function followActor(agent, actorUrl, { publish = true, doc: fetched = null, serial = Date.now() } = {}) {
|
|
130
|
-
const doc = fetched || await agent.intake.fetchAP(actorUrl);
|
|
131
|
-
if (!doc?.inbox) throw new Error(`actor document unusable (${actorUrl})`);
|
|
132
|
-
if (agent.store.isBlocked(doc.id)) throw new Error('actor is blocked');
|
|
133
|
-
const contacts = agent.store.getContacts();
|
|
134
|
-
let rec = contacts.following.find(f => f.actor === doc.id);
|
|
135
|
-
if (!rec) {
|
|
136
|
-
rec = { actor: doc.id, inbox: doc.inbox, accepted: false };
|
|
137
|
-
contacts.following.push(rec);
|
|
138
|
-
}
|
|
139
|
-
const follow = wire.followActivity({ urls: agent.publisher.urls, targetActor: doc.id, serial });
|
|
140
|
-
rec.followActivity = follow; // kept for a later Undo
|
|
141
|
-
agent.store.setContacts(contacts);
|
|
142
|
-
await agent.deliverer.deliver(doc.inbox, follow);
|
|
143
|
-
if (publish) await agent.publisher.publishCollections({ following: true, pending: true });
|
|
144
|
-
return doc;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export async function followHandle(agent, handle, { serial = Date.now() } = {}) {
|
|
148
|
-
const doc = await resolveHandle(agent, handle);
|
|
149
|
-
await followActor(agent, doc.id, { serial });
|
|
150
|
-
const clean = String(handle || '').replace(/^@/, '');
|
|
151
|
-
const contacts = agent.store.getContacts();
|
|
152
|
-
const rec = contacts.following.find(f => f.actor === doc.id);
|
|
153
|
-
if (rec && !rec.handle) { rec.handle = clean; agent.store.setContacts(contacts); }
|
|
154
|
-
return { ok: true, actor: doc.id };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export async function unfollowActor(agent, actor, { serial = Date.now() } = {}) {
|
|
158
|
-
const contacts = agent.store.getContacts();
|
|
159
|
-
const rec = contacts.following.find(f => f.actor === actor);
|
|
160
|
-
if (!rec) throw new Error('not following that actor');
|
|
161
|
-
if (rec.followActivity) {
|
|
162
|
-
await agent.deliverer.deliver(rec.inbox,
|
|
163
|
-
wire.undoActivity({ urls: agent.publisher.urls, activity: rec.followActivity, serial }));
|
|
164
|
-
}
|
|
165
|
-
contacts.following = contacts.following.filter(f => f.actor !== actor);
|
|
166
|
-
agent.store.setContacts(contacts);
|
|
167
|
-
await agent.publisher.publishCollections({ following: true, pending: true });
|
|
168
|
-
return { ok: true };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export async function favourite(agent, s, { serial = Date.now() } = {}) {
|
|
172
|
-
if (s.favourited) return s;
|
|
173
|
-
const doc = await agent.intake.fetchAP(s.actor);
|
|
174
|
-
if (!doc?.inbox) throw new Error('author inbox unavailable');
|
|
175
|
-
const act = wire.likeActivity({ urls: agent.publisher.urls, noteId: s.noteId, serial, to: s.actor });
|
|
176
|
-
await agent.deliverer.deliver(doc.endpoints?.sharedInbox || doc.inbox, act);
|
|
177
|
-
return agent.store.updateStatus(s.noteId, { favourited: true, likeActivity: act });
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export async function unfavourite(agent, s, { serial = Date.now() } = {}) {
|
|
181
|
-
if (s.likeActivity) {
|
|
182
|
-
const doc = await agent.intake.fetchAP(s.actor).catch(() => null);
|
|
183
|
-
if (doc?.inbox) {
|
|
184
|
-
await agent.deliverer.deliver(doc.endpoints?.sharedInbox || doc.inbox,
|
|
185
|
-
wire.undoActivity({ urls: agent.publisher.urls, activity: s.likeActivity, serial }));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return agent.store.updateStatus(s.noteId, { favourited: false, likeActivity: undefined });
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Announce goes to our followers (that's what boosting is) and to the author.
|
|
192
|
-
export async function reblog(agent, s, { serial = Date.now() } = {}) {
|
|
193
|
-
if (s.reblogged) return s;
|
|
194
|
-
const { urls } = agent.publisher;
|
|
195
|
-
const act = wire.announceActivity({ urls, object: s.noteId, serial });
|
|
196
|
-
const inboxes = new Set(agent.store.getContacts().followers
|
|
197
|
-
.map(f => f.sharedInbox || f.inbox).filter(Boolean));
|
|
198
|
-
if (s.actor !== urls.actor) {
|
|
199
|
-
const doc = await agent.intake.fetchAP(s.actor).catch(() => null);
|
|
200
|
-
if (doc?.inbox) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
|
|
201
|
-
}
|
|
202
|
-
await agent.deliverer.deliverToAll([...inboxes], act);
|
|
203
|
-
// Mark it boosted before recording it: a failed outbox write costs one
|
|
204
|
-
// missing entry, a failed status write would let a retry announce twice.
|
|
205
|
-
const updated = agent.store.updateStatus(s.noteId, { reblogged: true, announceActivity: act });
|
|
206
|
-
await agent.publisher.recordOutbox(act);
|
|
207
|
-
if (await agent.store.commit?.() === false) agent.log(`boost sent but its timeline row was refused: ${s.noteId}`);
|
|
208
|
-
return updated;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export async function unreblog(agent, s, { serial = Date.now() } = {}) {
|
|
212
|
-
if (s.announceActivity) {
|
|
213
|
-
const undo = wire.undoActivity({ urls: agent.publisher.urls, activity: s.announceActivity, serial });
|
|
214
|
-
const inboxes = new Set(agent.store.getContacts().followers
|
|
215
|
-
.map(f => f.sharedInbox || f.inbox).filter(Boolean));
|
|
216
|
-
const doc = await agent.intake.fetchAP(s.actor).catch(() => null);
|
|
217
|
-
if (doc?.inbox && s.actor !== agent.publisher.urls.actor) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
|
|
218
|
-
await agent.deliverer.deliverToAll([...inboxes], undo);
|
|
219
|
-
await agent.publisher.unrecordOutbox(i => i?.id === s.announceActivity.id, { record: () => undo });
|
|
220
|
-
}
|
|
221
|
-
return agent.store.updateStatus(s.noteId, { reblogged: false, announceActivity: undefined });
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Group: end a following we will no longer carry. Forgetting them locally is
|
|
225
|
-
// not enough — without the Reject their server keeps the relationship and they
|
|
226
|
-
// keep receiving everything the group announces. Muting them too, because an
|
|
227
|
-
// ejection anyone can undo by re-following is not an ejection; a re-follow is
|
|
228
|
-
// still auto-accepted, but nothing of theirs gets carried.
|
|
229
|
-
export async function ejectFollower(agent, actor) {
|
|
230
|
-
const { urls } = agent.publisher;
|
|
231
|
-
const contacts = agent.store.getContacts();
|
|
232
|
-
const rec = contacts.followers.find(f => f.actor === actor);
|
|
233
|
-
if (!rec) throw new Error('not a member');
|
|
234
|
-
dropFollower(contacts, actor, 'ejected');
|
|
235
|
-
agent.store.setContacts(contacts);
|
|
236
|
-
const muted = agent.store.getMuted();
|
|
237
|
-
muted.actors = [...new Set([...muted.actors, actor])];
|
|
238
|
-
agent.store.setMuted(muted);
|
|
239
|
-
await agent.publisher.publishCollections({ followers: true });
|
|
240
|
-
// The ban is broadcast to the membership (FEP-1b12), so their servers can
|
|
241
|
-
// mirror it. Never sent to the ejected member — they get the Reject below.
|
|
242
|
-
await announceModeration(agent,
|
|
243
|
-
wire.blockActivity({ urls, targetActor: actor, serial: Date.now() }))
|
|
244
|
-
.catch(e => agent.log?.(`moderation announce failed: ${e.message}`));
|
|
245
|
-
// A Bluesky-side member's departure sticks by blocking the DID.
|
|
246
|
-
if (rec.bsky) {
|
|
247
|
-
await agent.bskygroup?.blockDid(rec.bsky.did)
|
|
248
|
-
.catch(e => agent.log?.(`bluesky block failed for ${rec.bsky.handle}: ${e.message}`));
|
|
249
|
-
return { ok: true, actor, told: true };
|
|
250
|
-
}
|
|
251
|
-
if (rec.inbox) {
|
|
252
|
-
await agent.deliverer.deliver(rec.inbox, wire.rejectActivity({
|
|
253
|
-
urls,
|
|
254
|
-
followActivity: { id: rec.followId, type: 'Follow', actor, object: urls.actor },
|
|
255
|
-
serial: Date.now(),
|
|
256
|
-
}));
|
|
257
|
-
}
|
|
258
|
-
return { ok: true, actor, told: !!rec.inbox };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Group: answer a held join request. Admitting is the Accept onFollow would
|
|
262
|
-
// have sent immediately had the group not been gated.
|
|
263
|
-
export async function admitRequest(agent, actor, { publish = true, serial = Date.now() } = {}) {
|
|
264
|
-
const reqs = agent.store.getRequests();
|
|
265
|
-
const req = reqs.find(r => r.actor === actor);
|
|
266
|
-
if (!req) throw new Error('no such request');
|
|
267
|
-
const contacts = agent.store.getContacts();
|
|
268
|
-
if (!contacts.followers.some(f => f.actor === actor)) {
|
|
269
|
-
// Admitting a bridged Bluesky member retires their Bluesky-only record,
|
|
270
|
-
// the same way an ungated group's follow does: one person, one membership.
|
|
271
|
-
const bridgedDid = /^https:\/\/bsky\.brid\.gy\/ap\/(did:[^/]+)$/.exec(actor)?.[1];
|
|
272
|
-
if (bridgedDid) contacts.followers = contacts.followers.filter(f => f.bsky?.did !== bridgedDid);
|
|
273
|
-
contacts.followers.push(req.bsky
|
|
274
|
-
? { actor, bsky: req.bsky }
|
|
275
|
-
: { actor, inbox: req.inbox, sharedInbox: req.sharedInbox, followId: req.activity?.id });
|
|
276
|
-
agent.store.setContacts(contacts);
|
|
277
|
-
agent.store.addNotification({ type: 'follow', actor, ...(req.bsky ? { bsky: true } : {}) });
|
|
278
|
-
}
|
|
279
|
-
agent.store.setRequests(reqs.filter(r => r.actor !== actor));
|
|
280
|
-
// A Bluesky-only member has no inbox and no Accept to receive; the follow
|
|
281
|
-
// already stands on their side. The one-time bridge nudge goes out instead.
|
|
282
|
-
if (req.bsky) {
|
|
283
|
-
await agent.bskygroup?.nudgeOnce(contacts.followers.find(f => f.actor === actor));
|
|
284
|
-
return { ok: true, actor };
|
|
285
|
-
}
|
|
286
|
-
// A bulk admit passes publish:false and republishes once for the lot.
|
|
287
|
-
if (publish) await agent.publisher.publishCollections({ followers: true, pending: true });
|
|
288
|
-
await agent.deliverer.deliver(req.inbox, wire.acceptActivity({
|
|
289
|
-
urls: agent.publisher.urls, followActivity: req.activity, serial,
|
|
290
|
-
}));
|
|
291
|
-
return { ok: true, actor };
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Refusing is not a ban: they may ask again, and the queue is where you would
|
|
295
|
-
// see that. Eject is the sticky one, because it mutes.
|
|
296
|
-
export async function refuseRequest(agent, actor, { serial = Date.now() } = {}) {
|
|
297
|
-
const reqs = agent.store.getRequests();
|
|
298
|
-
const req = reqs.find(r => r.actor === actor);
|
|
299
|
-
if (!req) throw new Error('no such request');
|
|
300
|
-
agent.store.setRequests(reqs.filter(r => r.actor !== actor));
|
|
301
|
-
await agent.publisher?.publishCollections({ pending: true }).catch(() => {});
|
|
302
|
-
// No Reject can reach a Bluesky-only requester, so the refusal is enforced
|
|
303
|
-
// where it can be: their DID is blocked.
|
|
304
|
-
if (req.bsky) {
|
|
305
|
-
await agent.bskygroup?.blockDid(req.bsky.did)
|
|
306
|
-
.catch(e => agent.log?.(`bluesky block failed for ${req.bsky.handle}: ${e.message}`));
|
|
307
|
-
return { ok: true, actor };
|
|
308
|
-
}
|
|
309
|
-
await agent.deliverer.deliver(req.inbox, wire.rejectActivity({
|
|
310
|
-
urls: agent.publisher.urls, followActivity: req.activity, serial,
|
|
311
|
-
}));
|
|
312
|
-
return { ok: true, actor };
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Group: unsay an Announce. The work is in Intake because an upstream Delete
|
|
316
|
-
// has to do the same thing, and both must use the recipient set amplify used.
|
|
317
|
-
export async function retractAnnouncement(agent, noteId) {
|
|
318
|
-
return agent.intake.retract(noteId);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Remove an own note everywhere: Delete→followers, remote note + outbox,
|
|
322
|
-
// local pod RDF, statuses mirror.
|
|
323
|
-
//
|
|
324
|
-
// Two of the three pod deletes have to land, and both are checked. While the
|
|
325
|
-
// note document answers, anyone holding its id can still read a post its author
|
|
326
|
-
// deleted — and the `-create` beside it EMBEDS the whole note, so leaving that
|
|
327
|
-
// one behind publishes the same content under another name. A failure keeps the
|
|
328
|
-
// post here and says so, rather than reporting a deletion that did not happen:
|
|
329
|
-
// removing it locally would leave nothing to try again with, and nothing to see
|
|
330
|
-
// that it is still up.
|
|
331
|
-
// A vote is the fediverse convention: one bare Note per chosen option, whose
|
|
332
|
-
// name IS the option, inReplyTo the question, addressed to the question's
|
|
333
|
-
// author alone. The documents are stored for dereference but never listed —
|
|
334
|
-
// not in the outbox, not in the local index.
|
|
335
|
-
export async function votePoll(agent, s, choices) {
|
|
336
|
-
const { urls } = agent.publisher;
|
|
337
|
-
const poll = s.poll || {};
|
|
338
|
-
const opts = poll.options || [];
|
|
339
|
-
if (poll.closed || (poll.expiresAt && Date.parse(poll.expiresAt) < Date.now())) {
|
|
340
|
-
return { ok: false, error: 'this poll has closed' };
|
|
341
|
-
}
|
|
342
|
-
if (poll.voted) return { ok: false, error: 'already voted' };
|
|
343
|
-
// Your own poll is the one you are counting, not one you are answering.
|
|
344
|
-
if (s.actor === urls.actor) return { ok: false, error: 'this is your own poll' };
|
|
345
|
-
const picks = [...new Set(choices)].filter(i => i >= 0 && i < opts.length);
|
|
346
|
-
if (!picks.length) return { ok: false, error: 'invalid choice' };
|
|
347
|
-
if (!poll.multiple && picks.length > 1) return { ok: false, error: 'this poll takes one choice' };
|
|
348
|
-
const author = await agent.intake.fetchAP(s.actor).catch(() => null);
|
|
349
|
-
const inbox = author?.endpoints?.sharedInbox || author?.inbox;
|
|
350
|
-
if (!inbox) return { ok: false, error: 'could not reach the poll author' };
|
|
351
|
-
const published = new Date().toISOString();
|
|
352
|
-
for (const i of picks) {
|
|
353
|
-
const id = urls.notes + published.slice(0, 10) + '-vote-' + crypto.randomBytes(4).toString('hex');
|
|
354
|
-
const note = {
|
|
355
|
-
'@context': wire.AS_CTX, id, type: 'Note',
|
|
356
|
-
attributedTo: urls.actor, name: opts[i].title,
|
|
357
|
-
inReplyTo: s.noteId, published, to: [s.actor],
|
|
358
|
-
};
|
|
359
|
-
const create = {
|
|
360
|
-
'@context': wire.AS_CTX, id: wire.createActivityId(id), type: 'Create',
|
|
361
|
-
actor: urls.actor, published, to: [s.actor], object: note,
|
|
362
|
-
};
|
|
363
|
-
await podNotes.write(agent.remote, id, note);
|
|
364
|
-
await podNotes.writeCreate(agent.remote, create.id, create);
|
|
365
|
-
await agent.deliverer.deliverToAll([inbox], create);
|
|
366
|
-
}
|
|
367
|
-
agent.store.updateStatus(s.noteId, {
|
|
368
|
-
poll: {
|
|
369
|
-
...poll, voted: true, ownVotes: picks,
|
|
370
|
-
options: opts.map((o, j) => (picks.includes(j) ? { ...o, votes: (o.votes || 0) + 1 } : o)),
|
|
371
|
-
},
|
|
372
|
-
});
|
|
373
|
-
return { ok: true };
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export async function deleteNote(agent, s) {
|
|
377
|
-
const { urls } = agent.publisher;
|
|
378
|
-
// The deletion goes where the post went: followers unless it was direct, and
|
|
379
|
-
// everyone it named or was blind-copied to. Addressed as the post was, read
|
|
380
|
-
// from the post itself while it is still there.
|
|
381
|
-
const doc = await Promise.resolve().then(() => podNotes.read(agent.remote, s.noteId)).catch(() => null);
|
|
382
|
-
const shape = wire.addressing(urls, s.visibility || 'public', (s.mentions || []).map(m => m.href).filter(Boolean));
|
|
383
|
-
const to = Array.isArray(doc?.to) ? doc.to : shape.to;
|
|
384
|
-
const cc = Array.isArray(doc?.cc) ? doc.cc : shape.cc;
|
|
385
|
-
const named = [...to, ...cc, ...(s.addressed || []), ...(s.replyActor ? [s.replyActor] : [])]
|
|
386
|
-
.filter(a => typeof a === 'string' && a !== wire.PUBLIC && a !== urls.followers && a !== urls.actor);
|
|
387
|
-
const inboxes = [
|
|
388
|
-
...(s.visibility === 'direct' ? [] : agent.store.getContacts().followers.map(f => f.sharedInbox || f.inbox)),
|
|
389
|
-
...(agent.publisher.inboxesFor ? await agent.publisher.inboxesFor(named).catch(() => []) : []),
|
|
390
|
-
].filter(Boolean);
|
|
391
|
-
const del = wire.deleteActivity({ urls, noteId: s.noteId, to, cc });
|
|
392
|
-
await agent.deliverer.deliverToAll(inboxes, del);
|
|
393
|
-
|
|
394
|
-
// The note is REPLACED with a Tombstone rather than removed, so a peer that
|
|
395
|
-
// dereferences it learns the post was deleted instead of a bare 404 (§7.4).
|
|
396
|
-
// The Tombstone must stay readable; the container above it is owner-only, so
|
|
397
|
-
// the ACL cannot be inherited. The Create activity is the withdrawn action,
|
|
398
|
-
// not the object, so it is deleted outright.
|
|
399
|
-
const stuck = [];
|
|
400
|
-
const deletedAt = new Date().toISOString();
|
|
401
|
-
// Only inside the posts folders: an object that lives elsewhere on the pod
|
|
402
|
-
// (an annotation a client keeps in its own place, or anything it named) is
|
|
403
|
-
// that client's document, and a deletion here must not overwrite it — the
|
|
404
|
-
// profile, say — with a Tombstone. Its Create and outbox entry still go.
|
|
405
|
-
const ours = [urls.notes, urls.privateNotes].some(c => String(s.noteId).startsWith(c));
|
|
406
|
-
try {
|
|
407
|
-
if (ours) await podNotes.writeTombstone(agent.remote, s.noteId, wire.noteTombstone(s.noteId, deletedAt));
|
|
408
|
-
} catch { stuck.push(s.noteId); }
|
|
409
|
-
// The Create becomes a Tombstone too, not a gap: it embedded the post, so
|
|
410
|
-
// its words go, and whoever asks for it is told it is gone (410 through the
|
|
411
|
-
// Gateway) rather than that it never was.
|
|
412
|
-
// An object kept outside the posts folders has its Create inside them, under
|
|
413
|
-
// the name it was given (publishObject).
|
|
414
|
-
const createId = ours ? wire.createActivityId(s.noteId) : (s.slug ? wire.createActivityId(urls.notes + s.slug) : null);
|
|
415
|
-
try {
|
|
416
|
-
if (createId) await podNotes.writeTombstone(agent.remote, createId, wire.noteTombstone(createId, deletedAt, 'Create'));
|
|
417
|
-
} catch { stuck.push(createId); }
|
|
418
|
-
if (stuck.length) {
|
|
419
|
-
agent.log?.(`delete ${s.noteId}: the pod kept ${stuck.length} document(s) — the post is STILL PUBLISHED`);
|
|
420
|
-
return {
|
|
421
|
-
ok: false, stillPublished: stuck,
|
|
422
|
-
error: 'the pod would not remove the post — it is still published, and has been kept here so you can try again',
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
// Empty, and referenced by nothing once the note is gone.
|
|
426
|
-
await podNotes.dropReplies(agent.remote, wire.repliesId(s.noteId));
|
|
427
|
-
|
|
428
|
-
// The Bluesky mirror goes with the post. Best-effort: a mirror the far side
|
|
429
|
-
// already lost is not a reason to keep the post here.
|
|
430
|
-
if (s.atproto?.uri && agent.atproto?.connected()) {
|
|
431
|
-
await agent.atproto.deleteCrossPost(s.atproto.uri)
|
|
432
|
-
.then(() => agent.log?.(`bluesky mirror deleted: ${s.atproto.uri}`))
|
|
433
|
-
.catch(e => agent.log?.(`bluesky mirror not deleted (${e.message}): ${s.atproto.uri}`));
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// The note's outbox entry, and our own boost of it if there was one. The
|
|
437
|
-
// Delete takes the post's place — only for a post the outbox listed, so a
|
|
438
|
-
// private post's deletion stays as private as the post was.
|
|
439
|
-
const itsOwn = (i) => i === s.noteId || (i?.id && i.id === s.announceActivity?.id)
|
|
440
|
-
// its edits too: an Update carries the post's words, which must not
|
|
441
|
-
// outlive the post on the public record
|
|
442
|
-
|| (i?.type === 'Update' && (typeof i.object === 'string' ? i.object : i.object?.id) === s.noteId);
|
|
443
|
-
await agent.publisher.unrecordOutbox(itsOwn,
|
|
444
|
-
{ record: (gone) => (gone.includes(s.noteId) ? del : null) });
|
|
445
|
-
agent.store.removeStatus(s.noteId);
|
|
446
|
-
return { ok: true };
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// FEP-1b12: a GROUP's moderation is announced to its followers, so their
|
|
450
|
-
// servers can mirror the action. Group moderation only — a person's blocks
|
|
451
|
-
// never go on the wire (that is also the spec's own SHOULD NOT), which is why
|
|
452
|
-
// the guard lives here rather than in every caller. Never delivered to the
|
|
453
|
-
// moderated party; the announce goes to the membership.
|
|
454
|
-
export async function announceModeration(agent, activity) {
|
|
455
|
-
if (agent.store.getConfig()?.kind !== 'group') return null;
|
|
456
|
-
const { urls } = agent.publisher;
|
|
457
|
-
const act = wire.announceActivity({ urls, object: activity, serial: Date.now(), audience: urls.actor });
|
|
458
|
-
const inboxes = [...new Set(agent.store.getContacts().followers
|
|
459
|
-
.map(f => f.sharedInbox || f.inbox).filter(Boolean))];
|
|
460
|
-
await agent.deliverer.deliverToAll(inboxes, act);
|
|
461
|
-
await agent.publisher.recordOutbox(act);
|
|
462
|
-
return act;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// A block is state, not speech: the list changes and the follow ends, and
|
|
466
|
-
// nothing is delivered TO THE BLOCKED PARTY (§6.10). Intake already refuses a
|
|
467
|
-
// blocked author on arrival. A group additionally announces the ban to its
|
|
468
|
-
// members (FEP-1b12) — announceModeration keeps a person's blocks silent.
|
|
469
|
-
export async function blockActor(agent, actorUrl, { serial = Date.now() } = {}) {
|
|
470
|
-
if (!/^https?:\/\/\S+$/.test(String(actorUrl || ''))) throw new Error('actor must be a URL');
|
|
471
|
-
const b = agent.store.getBlocklist();
|
|
472
|
-
if (!b.actors.includes(actorUrl)) b.actors.push(actorUrl);
|
|
473
|
-
agent.store.setBlocklist(b);
|
|
474
|
-
await unfollowActor(agent, actorUrl).catch(() => {}); // not following is fine
|
|
475
|
-
await agent.publisher?.publishCollections({ blocked: true }).catch(() => {});
|
|
476
|
-
await announceModeration(agent,
|
|
477
|
-
wire.blockActivity({ urls: agent.publisher.urls, targetActor: actorUrl, serial }))
|
|
478
|
-
.catch(e => agent.log?.(`moderation announce failed: ${e.message}`));
|
|
479
|
-
return { ok: true, actor: actorUrl };
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
export async function unblockActor(agent, actorUrl, { serial = Date.now() } = {}) {
|
|
483
|
-
const b = agent.store.getBlocklist();
|
|
484
|
-
b.actors = b.actors.filter(a => a !== actorUrl);
|
|
485
|
-
agent.store.setBlocklist(b);
|
|
486
|
-
await agent.publisher?.publishCollections({ blocked: true }).catch(() => {});
|
|
487
|
-
// The unban is announced as the Undo of the ban, the ban re-stated inside it
|
|
488
|
-
// — its id was a fragment nobody stored, so receivers match on the shape.
|
|
489
|
-
await announceModeration(agent, wire.undoActivity({
|
|
490
|
-
urls: agent.publisher.urls,
|
|
491
|
-
activity: { type: 'Block', actor: agent.publisher.urls.actor, object: actorUrl },
|
|
492
|
-
serial,
|
|
493
|
-
})).catch(e => agent.log?.(`moderation announce failed: ${e.message}`));
|
|
494
|
-
return { ok: true, actor: actorUrl };
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// Apply (or refuse) one queued moderation request — an action a LISTED
|
|
498
|
-
// moderator asked for over federation. Deliveries carry nothing that proves
|
|
499
|
-
// the sender, which is exactly why these wait in a queue for the operator
|
|
500
|
-
// instead of running on arrival; applying one is the operator vouching for it.
|
|
501
|
-
export async function applyModeration(agent, entry) {
|
|
502
|
-
const { urls } = agent.publisher;
|
|
503
|
-
const target = typeof entry.activity?.object === 'string'
|
|
504
|
-
? entry.activity.object : entry.activity?.object?.id;
|
|
505
|
-
switch (entry.type) {
|
|
506
|
-
case 'Block': {
|
|
507
|
-
if (!target) throw new Error('the queued Block names nobody');
|
|
508
|
-
// A member is ejected (which mutes and announces the ban); a stranger is
|
|
509
|
-
// simply blocked. Either way the ban is broadcast once.
|
|
510
|
-
const member = agent.store.getContacts().followers.some(f => f.actor === target);
|
|
511
|
-
return member ? ejectFollower(agent, target) : blockActor(agent, target);
|
|
512
|
-
}
|
|
513
|
-
case 'Undo': {
|
|
514
|
-
const inner = entry.activity?.object;
|
|
515
|
-
const unbanned = typeof inner === 'object' && inner?.type === 'Block'
|
|
516
|
-
? (typeof inner.object === 'string' ? inner.object : inner.object?.id) : null;
|
|
517
|
-
if (!unbanned) throw new Error('the queued Undo does not name a Block');
|
|
518
|
-
const muted = agent.store.getMuted();
|
|
519
|
-
muted.actors = muted.actors.filter(a => a !== unbanned);
|
|
520
|
-
agent.store.setMuted(muted);
|
|
521
|
-
return unblockActor(agent, unbanned);
|
|
522
|
-
}
|
|
523
|
-
case 'Delete': {
|
|
524
|
-
if (!target) throw new Error('the queued Delete names nothing');
|
|
525
|
-
// Removing a post from the group is unsaying its carry.
|
|
526
|
-
return agent.intake.retract(target);
|
|
527
|
-
}
|
|
528
|
-
case 'Add':
|
|
529
|
-
case 'Remove': {
|
|
530
|
-
if (!target) throw new Error('the queued roster change names nobody');
|
|
531
|
-
const cfg = { ...agent.store.getConfig() };
|
|
532
|
-
const prev = cfg.moderators || [];
|
|
533
|
-
cfg.moderators = entry.type === 'Add'
|
|
534
|
-
? [...new Set([...prev, target])]
|
|
535
|
-
: prev.filter(m => m !== target);
|
|
536
|
-
agent.store.setConfig(cfg);
|
|
537
|
-
if (agent.publisher) agent.publisher.config.moderators = cfg.moderators;
|
|
538
|
-
await agent.publisher.publishProfile();
|
|
539
|
-
await announceModeration(agent, wire.addRemoveActivity({
|
|
540
|
-
urls, type: entry.type, object: target, target: urls.moderators, serial: Date.now(),
|
|
541
|
-
})).catch(e => agent.log?.(`moderation announce failed: ${e.message}`));
|
|
542
|
-
return { ok: true, moderators: cfg.moderators };
|
|
543
|
-
}
|
|
544
|
-
default: throw new Error(`no way to apply a queued ${entry.type}`);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Pin or unpin an own post. Republishing the featured collection is what makes
|
|
549
|
-
// the pin real to anyone else — the actor document names that collection, and
|
|
550
|
-
// a visitor's server reads it, so the profile is republished too (it skips
|
|
551
|
-
// itself when nothing changed).
|
|
552
|
-
export async function pinStatus(agent, s, pinned) {
|
|
553
|
-
if (s.actor !== agent.publisher.urls.actor) throw new Error('not your status');
|
|
554
|
-
const updated = agent.store.updateStatus(s.noteId, { pinned: !!pinned });
|
|
555
|
-
await agent.publisher.publishFeatured().catch(e => agent.log?.(`featured: ${e.message}`));
|
|
556
|
-
if (agent.publisher.publishProfilePage) {
|
|
557
|
-
await agent.publisher.publishProfilePage().catch(e => agent.log?.(`profile page: ${e.message}`));
|
|
558
|
-
}
|
|
559
|
-
agent.publisher.publishProfile?.().catch(() => {});
|
|
560
|
-
return updated || s;
|
|
561
|
-
}
|