fedipod-server 0.28.1 → 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 +38 -34
- package/config/server.json +22 -0
- package/dist/claims.js +3 -2
- package/dist/directory.d.ts +2 -0
- 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 +39 -101
- package/dist/handler.js +86 -617
- package/dist/handler.jsonld +6 -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/dist/store-pod.js +37 -0
- package/package.json +10 -18
- package/lib/client/c2s.mjs +0 -582
- package/lib/client/localapi.mjs +0 -87
- package/lib/client/masto/accounts.mjs +0 -309
- 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 -371
- package/lib/core/graphview.mjs +0 -327
- package/lib/core/intake/activities.mjs +0 -633
- package/lib/core/intake/activity.mjs +0 -318
- package/lib/core/intake/channel.mjs +0 -144
- package/lib/core/intake/group.mjs +0 -256
- package/lib/core/intake/index.mjs +0 -701
- package/lib/core/intake/notes.mjs +0 -360
- 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/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 -237
- package/lib/core/publisher/index.mjs +0 -500
- package/lib/core/publisher/notes.mjs +0 -453
- package/lib/core/publisher/questions.mjs +0 -239
- 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 -531
- package/lib/core/storage.mjs +0 -176
- package/lib/core/store.mjs +0 -568
- package/lib/core/wire-quotes.mjs +0 -71
- package/lib/core/wire.mjs +0 -889
- 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 -412
- 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 -447
- 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 -459
- 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 -970
- package/lib/gateway/gateway-core.mjs +0 -165
- 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/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/media.mjs +0 -27
- package/lib/pod/notes.mjs +0 -80
- 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 -525
- package/lib/pod/urls.mjs +0 -85
- package/lib/server/embed.mjs +0 -423
- package/lib/session/README.md +0 -137
- package/lib/session/demo.html +0 -94
- package/lib/session/fedi-account.mjs +0 -408
- 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 -897
- 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 -145
- 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 -603
- package/web/admin/notices-bar.js +0 -90
- package/web/admin/oauth-signin.mjs +0 -37
- package/web/admin/record.js +0 -390
- package/web/admin/setup/index.html +0 -241
- package/web/admin/setup/setup.js +0 -326
- 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 -663
- 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 -576
- package/web/app/deliver-relay.mjs +0 -129
- package/web/app/dist/boot.js +0 -34218
- package/web/app/dist/boot.js.map +0 -7
- package/web/app/dist/sw.js +0 -72810
- 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 -242
- 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 -318
- 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 -58
- package/web/front/run.js +0 -195
- 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
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
// notes.mjs — a note of the actor's own going up: the containers it needs
|
|
2
|
-
// (media, and the owner-only one private posts live in), who it mentions,
|
|
3
|
-
// publishing it, and editing it afterwards.
|
|
4
|
-
|
|
5
|
-
import crypto from 'node:crypto';
|
|
6
|
-
import * as wire from '../wire.mjs';
|
|
7
|
-
import * as wireQuotes from '../wire-quotes.mjs';
|
|
8
|
-
import * as containers from '../../pod/containers.mjs';
|
|
9
|
-
import * as podNotes from '../../pod/notes.mjs';
|
|
10
|
-
|
|
11
|
-
// Media container on the remote pod — public-Read like notes, created lazily
|
|
12
|
-
// at first upload (idempotent; the flag only saves round-trips).
|
|
13
|
-
// Ask before writing. `_mediaReady` is per PROCESS, and a service worker is
|
|
14
|
-
// restarted whenever the browser feels like it — so on the browser build this
|
|
15
|
-
// re-created a container that has existed since sign-up, and rewrote its ACL,
|
|
16
|
-
// on every restart. A HEAD is one request and usually the only one.
|
|
17
|
-
export async function ensureMediaContainer(publisher) {
|
|
18
|
-
if (publisher._mediaReady) return;
|
|
19
|
-
if (await containers.exists(publisher.remote, publisher.urls.media)) { publisher._mediaReady = true; return; }
|
|
20
|
-
await containers.provisionPublic(publisher.remote, publisher.urls.media);
|
|
21
|
-
publisher._mediaReady = true;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Whether the canary this class writes is already there. Only a definite
|
|
25
|
-
// "yes" counts: anything else falls through to the write, which is
|
|
26
|
-
// idempotent anyway, so a failed probe costs a request and never correctness.
|
|
27
|
-
export function containerExists(publisher, base) { return containers.exists(publisher.remote, base); }
|
|
28
|
-
|
|
29
|
-
// The owner-only container that followers-only and direct posts live in.
|
|
30
|
-
// Its ACL is set once; every note under it inherits.
|
|
31
|
-
export async function ensurePrivateContainer(publisher) {
|
|
32
|
-
if (publisher._privateContainer) return;
|
|
33
|
-
// Same reasoning as ensureMediaContainer: probe before provisioning, so a
|
|
34
|
-
// worker restart is not a re-provision. The ACL is NOT skipped on the
|
|
35
|
-
// strength of the container existing alone — ensurePrivateAcls re-checks
|
|
36
|
-
// that separately on every connect, which is the control that matters here.
|
|
37
|
-
if (await containers.exists(publisher.remote, publisher.urls.privateNotes)) { publisher._privateContainer = true; return; }
|
|
38
|
-
await containers.provisionOwnerOnly(publisher.remote, publisher.urls.privateNotes);
|
|
39
|
-
publisher._privateContainer = true;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Whether the pod actually enforces that ACL: a bare, unauthenticated read
|
|
43
|
-
// of the private container's canary must be refused. Returns true, or the
|
|
44
|
-
// reason private posts stay off. A definite answer is cached for the run; a
|
|
45
|
-
// network failure is not, so a pod that was briefly unreachable is asked
|
|
46
|
-
// again rather than refused forever.
|
|
47
|
-
export async function privateReady(publisher) {
|
|
48
|
-
if (publisher._privateVerdict !== undefined) return publisher._privateVerdict;
|
|
49
|
-
try {
|
|
50
|
-
await publisher.ensurePrivateContainer();
|
|
51
|
-
// The probe is credential-free and bypasses RemotePod's url map, so a
|
|
52
|
-
// fronted identity must map the advertised private container back to the
|
|
53
|
-
// pod itself — the ACL we are testing is the pod's.
|
|
54
|
-
const pn = publisher.urls.toPod ? publisher.urls.toPod(publisher.urls.privateNotes) : publisher.urls.privateNotes;
|
|
55
|
-
const verdict = await containers.probePrivateEnforcement(publisher.probeFetch || fetch, pn + '.keep');
|
|
56
|
-
publisher._privateVerdict = verdict === true ? true : `${verdict} — private posts stay off it`;
|
|
57
|
-
return publisher._privateVerdict;
|
|
58
|
-
} catch (e) {
|
|
59
|
-
return `could not verify that the pod protects private posts (${e.message})`;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Compose → wire note on remote pod + RDF truth locally + deliver Create.
|
|
64
|
-
// Who this text mentions, resolved. A mention nobody can resolve stays plain
|
|
65
|
-
// text rather than failing the post. The text itself decides: trim a handle
|
|
66
|
-
// out and that person is not notified, which is what every fediverse client
|
|
67
|
-
// leads people to expect. A Group named in the parent is the one thing
|
|
68
|
-
// carried forward regardless — drop it and the group stops carrying the
|
|
69
|
-
// thread.
|
|
70
|
-
export async function mentionsFor(publisher, content, inReplyTo) {
|
|
71
|
-
const inText = new Set(wire.mentionsIn(content));
|
|
72
|
-
const carried = inReplyTo
|
|
73
|
-
? (publisher.store.getStatuses().find(s => s.noteId === inReplyTo)?.mentions || [])
|
|
74
|
-
.map(m => String(m.name || '').replace(/^@/, '')).filter(Boolean)
|
|
75
|
-
: [];
|
|
76
|
-
const mentions = [];
|
|
77
|
-
for (const handle of [...new Set([...inText, ...carried])]) {
|
|
78
|
-
if (!publisher.resolveMention) break;
|
|
79
|
-
const doc = await publisher.resolveMention(handle).catch(() => null);
|
|
80
|
-
if (!doc?.id) { publisher.log(`mention @${handle} did not resolve — left as text`); continue; }
|
|
81
|
-
if (!inText.has(handle) && doc.type !== 'Group') continue; // author trimmed them out
|
|
82
|
-
mentions.push({ handle, actor: doc.id, page: doc.url || null, inbox: doc.endpoints?.sharedInbox || doc.inbox });
|
|
83
|
-
}
|
|
84
|
-
return mentions;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Who a reply is answering. Their server gets the post whether or not the
|
|
88
|
-
// text names them — that is how the thread stays whole where they are — but
|
|
89
|
-
// no Mention tag is added, so a handle the author trimmed out still means no
|
|
90
|
-
// notification. Nothing for a direct post: it goes to whom it names. The
|
|
91
|
-
// parent is read from the timeline row; a parent the agent never held (a
|
|
92
|
-
// client-to-server reply to any address) is fetched to find its author.
|
|
93
|
-
export async function replyTarget(publisher, inReplyTo, mentions = [], visibility = 'public') {
|
|
94
|
-
if (!inReplyTo || visibility === 'direct' || !publisher.resolveActor) return null;
|
|
95
|
-
const parent = publisher.store.getStatuses().find(s => s.noteId === inReplyTo);
|
|
96
|
-
let actor = parent?.actor || null;
|
|
97
|
-
if (!parent) {
|
|
98
|
-
const doc = await publisher.resolveActor(inReplyTo).catch(() => null);
|
|
99
|
-
const by = doc?.attributedTo;
|
|
100
|
-
actor = typeof by === 'string' ? by : (Array.isArray(by) ? by[0] : by)?.id || null;
|
|
101
|
-
if (typeof actor === 'object') actor = actor?.id || null;
|
|
102
|
-
}
|
|
103
|
-
if (!actor || actor === publisher.urls.actor) return null;
|
|
104
|
-
if (mentions.some(m => m.actor === actor)) return null;
|
|
105
|
-
if (publisher.store.isBlocked?.(actor)) return null;
|
|
106
|
-
const doc = await publisher.resolveActor(actor).catch(() => null);
|
|
107
|
-
const inbox = doc?.endpoints?.sharedInbox || doc?.inbox || null;
|
|
108
|
-
if (!inbox) publisher.log(`reply: no inbox found for ${actor} — they get it only through followers`);
|
|
109
|
-
return { actor, inbox };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Inboxes for actors a client addressed by id — the people in a post's
|
|
113
|
-
// to/cc and the ones in bto/bcc, who are delivered to and never listed.
|
|
114
|
-
export async function inboxesFor(publisher, actorIds = []) {
|
|
115
|
-
const out = [];
|
|
116
|
-
for (const id of [...new Set(actorIds.filter(a => typeof a === 'string' && a && a !== publisher.urls.actor))]) {
|
|
117
|
-
if (!publisher.resolveActor) break;
|
|
118
|
-
const doc = await publisher.resolveActor(id).catch(() => null);
|
|
119
|
-
const inbox = doc?.endpoints?.sharedInbox || doc?.inbox;
|
|
120
|
-
if (inbox) out.push(inbox);
|
|
121
|
-
else publisher.log(`addressed actor ${id} has no inbox the agent can find — not delivered to`);
|
|
122
|
-
}
|
|
123
|
-
return out;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// A direct post goes only to the people it names, so one that names nobody
|
|
127
|
-
// the agent can find would be kept in the private container and delivered
|
|
128
|
-
// to no one, with nothing said. Refused instead, before anything is written,
|
|
129
|
-
// with the handles that did not resolve — that is what the sender can act on.
|
|
130
|
-
export function assertDirectAddressed(content, mentions) {
|
|
131
|
-
const wanted = [...new Set(wire.mentionsIn(content))];
|
|
132
|
-
const missing = wanted.filter((h) => !mentions.some((m) => m.handle === h));
|
|
133
|
-
let why = null;
|
|
134
|
-
if (!wanted.length) why = 'a direct message names who it is for — mention them as @name@host';
|
|
135
|
-
else if (missing.length) why = `could not find ${missing.map((h) => '@' + h).join(', ')} — the direct message was not sent`;
|
|
136
|
-
if (!why) return;
|
|
137
|
-
const e = new Error(why);
|
|
138
|
-
e.code = 'unaddressed';
|
|
139
|
-
throw e;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// A client may name the document it is creating (the Slug of a client-to-server
|
|
143
|
-
// POST). Only a plain name is taken, and only when nothing is there already;
|
|
144
|
-
// otherwise the agent mints one as it always has.
|
|
145
|
-
const SLUG_OK = /^[A-Za-z0-9._-]{1,64}$/u;
|
|
146
|
-
export const safeSlug = (s) => (typeof s === 'string' && SLUG_OK.test(s) && !/^\.+$/u.test(s) ? s : null);
|
|
147
|
-
async function slugFor(publisher, container, wanted, published) {
|
|
148
|
-
const name = safeSlug(wanted);
|
|
149
|
-
if (name && !(await podNotes.read(publisher.remote, container + name).catch(() => null))) return name;
|
|
150
|
-
return published.slice(0, 10) + '-' + crypto.randomBytes(4).toString('hex');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// What the author's own timeline shows for an object: its content, else its
|
|
154
|
-
// text under one of the names other vocabularies use, else a link to it.
|
|
155
|
-
export function rowContent(obj) {
|
|
156
|
-
if (typeof obj?.content === 'string' && obj.content.trim()) return wire.sanitizeHtml(obj.content);
|
|
157
|
-
const plain = [obj?.bodyValue, obj?.name, obj?.summary].find((v) => typeof v === 'string' && v.trim());
|
|
158
|
-
if (plain) return wire.contentHtml(plain);
|
|
159
|
-
const esc = (v) => String(v).replace(/[&<>"]/gu, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
|
160
|
-
return `<p><a href="${esc(obj?.id || '')}">${esc(obj?.type || 'object')}</a></p>`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// `quote`: the post this one quotes, `{ id, actor }` — the quoted author is
|
|
164
|
-
// asked to allow it (FEP-044f). A quote of one of this actor's own posts is
|
|
165
|
-
// allowed on the spot.
|
|
166
|
-
export async function publishNote(publisher, content, { inReplyTo, attachments, visibility = 'public', spoilerText = null, sensitive = false, slug: wanted = null, also = [], deliverTo = [], quote = null } = {}) {
|
|
167
|
-
const { urls } = publisher;
|
|
168
|
-
const priv = visibility === 'private' || visibility === 'direct';
|
|
169
|
-
if (priv) {
|
|
170
|
-
const ready = await publisher.privateReady();
|
|
171
|
-
if (ready !== true) throw new Error(ready);
|
|
172
|
-
}
|
|
173
|
-
const published = new Date().toISOString();
|
|
174
|
-
const slug = await slugFor(publisher, priv ? urls.privateNotes : urls.notes, wanted, published);
|
|
175
|
-
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
176
|
-
if (visibility === 'direct') assertDirectAddressed(content, mentions);
|
|
177
|
-
const reply = await replyTarget(publisher, inReplyTo, mentions, visibility);
|
|
178
|
-
const selfQuote = quote?.id && quote.actor === urls.actor;
|
|
179
|
-
const quoted = quote?.id ? {
|
|
180
|
-
id: quote.id,
|
|
181
|
-
// Our own post: the authorization is minted here, before the note goes out.
|
|
182
|
-
authorization: selfQuote ? await grantQuote(publisher, quote.id, (priv ? urls.privateNotes : urls.notes) + slug) : null,
|
|
183
|
-
} : null;
|
|
184
|
-
const note = wire.noteDoc({ urls, slug, content, published, inReplyTo, attachments, mentions,
|
|
185
|
-
visibility, summary: spoilerText, sensitive, container: priv ? urls.privateNotes : urls.notes,
|
|
186
|
-
also: [...also, ...(reply ? [reply.actor] : []), ...(quote?.actor && !selfQuote ? [quote.actor] : [])], quote: quoted });
|
|
187
|
-
const addressedInboxes = await inboxesFor(publisher, [...also, ...deliverTo, ...(quote?.actor && !selfQuote ? [quote.actor] : [])]);
|
|
188
|
-
|
|
189
|
-
await podNotes.write(publisher.remote, note.id, note);
|
|
190
|
-
// Empty, but present: a dangling `replies` that 404s is worse than none.
|
|
191
|
-
await podNotes.writeEmptyReplies(publisher.remote, wire.repliesId(note.id),
|
|
192
|
-
wire.collection(wire.repliesId(note.id), []));
|
|
193
|
-
// The outbox is the PUBLIC index; a private or direct post is not in it.
|
|
194
|
-
if (!priv) await publisher.recordOutbox(note.id);
|
|
195
|
-
|
|
196
|
-
const request = quote?.id && !selfQuote
|
|
197
|
-
? wireQuotes.quoteRequestActivity({ urls, note, quoted: quote.id, quotedActor: quote.actor, serial: Date.now() })
|
|
198
|
-
: null;
|
|
199
|
-
publisher.store.addStatus({
|
|
200
|
-
noteId: note.id, actor: urls.actor, content: note.content, published, inReplyTo,
|
|
201
|
-
kind: 'post', slug, text: content, visibility,
|
|
202
|
-
...(spoilerText ? { spoiler: spoilerText } : {}),
|
|
203
|
-
...(note.sensitive ? { sensitive: true } : {}),
|
|
204
|
-
...(attachments?.length ? { attachments } : {}),
|
|
205
|
-
...rowTags(note),
|
|
206
|
-
...(quote?.id ? {
|
|
207
|
-
quote: quote.id,
|
|
208
|
-
quoteState: selfQuote ? 'accepted' : 'pending',
|
|
209
|
-
...(quoted.authorization ? { quoteAuthorization: quoted.authorization } : {}),
|
|
210
|
-
...(request ? { quoteRequest: { id: request.id, actor: quote.actor } } : {}),
|
|
211
|
-
} : {}),
|
|
212
|
-
});
|
|
213
|
-
// The row is what the author's own timeline reads. Land it before answering
|
|
214
|
-
// — the same PUT the debounce would send in 300 ms — so a worker killed or a
|
|
215
|
-
// write refused after the answer cannot leave a post that stands on the pod
|
|
216
|
-
// and reached followers but never shows to its author.
|
|
217
|
-
if (await publisher.store.commit?.() === false) publisher.log(`post published but its timeline row was refused: ${note.id}`);
|
|
218
|
-
|
|
219
|
-
const create = wire.createActivity(note, urls);
|
|
220
|
-
// Published as its own document: a group that carries this post wraps the
|
|
221
|
-
// whole activity, and the receiving server resolves it by fetching this id.
|
|
222
|
-
// It inherits the notes container's public-Read acl:default.
|
|
223
|
-
await podNotes.writeCreate(publisher.remote, create.id, create);
|
|
224
|
-
const contacts = publisher.store.getContacts();
|
|
225
|
-
// A direct post goes to the people it names and to nobody else.
|
|
226
|
-
const inboxes = [...new Set([
|
|
227
|
-
...(visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
|
|
228
|
-
...mentions.map(m => m.inbox),
|
|
229
|
-
reply?.inbox,
|
|
230
|
-
...addressedInboxes,
|
|
231
|
-
].filter(Boolean))];
|
|
232
|
-
await publisher.deliverer.deliverToAll(inboxes, create);
|
|
233
|
-
publisher.log(`note published: ${note.id} → ${inboxes.length} inbox(es)`);
|
|
234
|
-
// The quoted author is asked after the note is out: their Accept names the
|
|
235
|
-
// note, so it has to exist where they will look for it.
|
|
236
|
-
if (request) {
|
|
237
|
-
const [inbox] = await inboxesFor(publisher, [quote.actor]);
|
|
238
|
-
if (inbox) await publisher.deliverer.deliver(inbox, request);
|
|
239
|
-
else publisher.log(`quote of ${quote.id}: no inbox found for ${quote.actor} — the quote stays pending`);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Bluesky mirror: PUBLIC posts only — unlisted, followers-only and direct
|
|
243
|
-
// are never carried off the fediverse. A mirror failure never fails the
|
|
244
|
-
// post; it is logged and recorded on the status for the admin page.
|
|
245
|
-
if (visibility === 'public' && publisher.atproto?.connected()
|
|
246
|
-
&& publisher.store.getConfig()?.atproto?.crossPost) {
|
|
247
|
-
try {
|
|
248
|
-
const mirror = await publisher.atproto.crossPost(
|
|
249
|
-
{ text: content, published, attachments }, { noteUrl: note.id });
|
|
250
|
-
publisher.store.updateStatus(note.id, { atproto: mirror });
|
|
251
|
-
publisher.log(`cross-posted to bluesky: ${mirror.uri}${mirror.truncated ? ' (truncated, links back)' : ''}`);
|
|
252
|
-
} catch (e) {
|
|
253
|
-
publisher.store.updateStatus(note.id, { atproto: { error: e.message } });
|
|
254
|
-
publisher.log(`bluesky cross-post failed: ${e.message}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return note;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// An edit keeps the note's id, slug and published time; `updated` is the
|
|
261
|
-
// edit's own stamp. The pod documents are overwritten in place — the Create
|
|
262
|
-
// too, so a group's Announce resolves to the edited text — and an Update
|
|
263
|
-
// goes everywhere the Create went.
|
|
264
|
-
// Any object as a post: what a client-to-server Create carries when it is not
|
|
265
|
-
// a Note or a Question — a Web Annotation, say. Stored as sent, with its own
|
|
266
|
-
// context, under this actor; the Create around it is what followers receive.
|
|
267
|
-
// Servers that know the type show it; the rest ignore it, which is the
|
|
268
|
-
// expected outcome.
|
|
269
|
-
//
|
|
270
|
-
// An object already living on this pod (an id under it) is not copied: the
|
|
271
|
-
// Create names it where it is. Anything else is written into the notes
|
|
272
|
-
// container under the client's slug when it is free, else a minted one.
|
|
273
|
-
export async function publishObject(publisher, object, { visibility = 'public', slug: wanted = null, also = [], deliverTo = [] } = {}) {
|
|
274
|
-
const { urls } = publisher;
|
|
275
|
-
const priv = visibility === 'private' || visibility === 'direct';
|
|
276
|
-
if (priv) {
|
|
277
|
-
const ready = await publisher.privateReady();
|
|
278
|
-
if (ready !== true) throw new Error(ready);
|
|
279
|
-
}
|
|
280
|
-
const published = new Date().toISOString();
|
|
281
|
-
const container = priv ? urls.privateNotes : urls.notes;
|
|
282
|
-
const pod = publisher.config?.remotePod || urls.base;
|
|
283
|
-
const ownId = typeof object.id === 'string' && /^https?:\/\//u.test(object.id) && object.id.startsWith(pod)
|
|
284
|
-
? object.id : null;
|
|
285
|
-
const name = await slugFor(publisher, container, wanted, published);
|
|
286
|
-
const addressed = wire.addressing(urls, visibility, visibility === 'direct' ? also : []);
|
|
287
|
-
if (visibility !== 'direct') addressed.cc = [...new Set([...addressed.cc, ...also])];
|
|
288
|
-
let doc = null;
|
|
289
|
-
const id = ownId || container + name;
|
|
290
|
-
if (!ownId) {
|
|
291
|
-
// bto and bcc are for delivery alone: they never appear in what is
|
|
292
|
-
// stored, served or sent (ActivityPub §6).
|
|
293
|
-
const { bto, bcc, ...sent } = object; // eslint-disable-line no-unused-vars
|
|
294
|
-
doc = { ...sent, id, attributedTo: urls.actor, published: object.published || published, to: addressed.to, cc: addressed.cc };
|
|
295
|
-
if (!doc['@context']) doc['@context'] = wire.AS_CTX;
|
|
296
|
-
if (typeof doc.content === 'string') doc.content = wire.sanitizeHtml(doc.content);
|
|
297
|
-
await podNotes.write(publisher.remote, id, doc);
|
|
298
|
-
}
|
|
299
|
-
if (!priv) await publisher.recordOutbox(id);
|
|
300
|
-
const row = doc || object;
|
|
301
|
-
publisher.store.addStatus({
|
|
302
|
-
noteId: id, actor: urls.actor, content: rowContent(row), published: row.published || published,
|
|
303
|
-
kind: 'post', slug: name, visibility,
|
|
304
|
-
});
|
|
305
|
-
if (await publisher.store.commit?.() === false) publisher.log(`object published but its timeline row was refused: ${id}`);
|
|
306
|
-
|
|
307
|
-
// The Create sits beside the object; for an object living elsewhere on the
|
|
308
|
-
// pod it sits in the notes container, whose ACL is public Read.
|
|
309
|
-
const createId = doc ? wire.createActivityId(id) : wire.createActivityId(container + name);
|
|
310
|
-
const create = {
|
|
311
|
-
'@context': wire.AS_CTX, id: createId, type: 'Create', actor: urls.actor,
|
|
312
|
-
published: row.published || published, to: addressed.to, cc: addressed.cc,
|
|
313
|
-
object: doc || id,
|
|
314
|
-
};
|
|
315
|
-
await podNotes.writeCreate(publisher.remote, create.id, create);
|
|
316
|
-
const contacts = publisher.store.getContacts();
|
|
317
|
-
const inboxes = [...new Set([
|
|
318
|
-
...(visibility === 'direct' ? [] : contacts.followers.map((f) => f.sharedInbox || f.inbox)),
|
|
319
|
-
...(await inboxesFor(publisher, [...also, ...deliverTo])),
|
|
320
|
-
].filter(Boolean))];
|
|
321
|
-
await publisher.deliverer.deliverToAll(inboxes, create);
|
|
322
|
-
publisher.log(`${row.type || 'object'} published: ${id} → ${inboxes.length} inbox(es)`);
|
|
323
|
-
return { id, createId, copied: !ownId };
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// What the author's timeline row keeps of a note's tags: the people it
|
|
327
|
-
// mentions and the hashtags it carries, each as the client-facing shape.
|
|
328
|
-
export function rowTags(note) {
|
|
329
|
-
const tags = [].concat(note.tag || []);
|
|
330
|
-
const mentions = tags.filter(t => t.type === 'Mention').map(t => ({ href: t.href, name: t.name }));
|
|
331
|
-
const hashtags = tags.filter(t => t.type === 'Hashtag').map(t => ({ name: String(t.name).replace(/^#/, ''), url: t.href }));
|
|
332
|
-
return { ...(mentions.length ? { mentions } : {}), ...(hashtags.length ? { tags: hashtags } : {}) };
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export async function updateNote(publisher, s, { content, spoilerText = null, sensitive = null, attachments = null } = {}) {
|
|
336
|
-
const { urls } = publisher;
|
|
337
|
-
const updated = new Date().toISOString();
|
|
338
|
-
const inText = new Set(wire.mentionsIn(content));
|
|
339
|
-
const mentions = [];
|
|
340
|
-
for (const handle of inText) {
|
|
341
|
-
if (!publisher.resolveMention) break;
|
|
342
|
-
const doc = await publisher.resolveMention(handle).catch(() => null);
|
|
343
|
-
if (!doc?.id) { publisher.log(`mention @${handle} did not resolve — left as text`); continue; }
|
|
344
|
-
mentions.push({ handle, actor: doc.id, page: doc.url || null, inbox: doc.endpoints?.sharedInbox || doc.inbox });
|
|
345
|
-
}
|
|
346
|
-
const atts = attachments ?? s.attachments ?? [];
|
|
347
|
-
// The note stays in the container its visibility put it in; a recovered
|
|
348
|
-
// post may carry no slug, but the note id already contains it.
|
|
349
|
-
const container = String(s.noteId).startsWith(urls.privateNotes) ? urls.privateNotes : urls.notes;
|
|
350
|
-
const slug = s.slug || String(s.noteId).slice(container.length);
|
|
351
|
-
const reply = await replyTarget(publisher, s.inReplyTo, mentions, s.visibility || 'public');
|
|
352
|
-
const note = wire.noteDoc({
|
|
353
|
-
urls, slug, content, published: s.published, inReplyTo: s.inReplyTo,
|
|
354
|
-
attachments: atts, mentions, visibility: s.visibility || 'public',
|
|
355
|
-
summary: spoilerText, sensitive: sensitive ?? !!s.sensitive, updated, container,
|
|
356
|
-
also: reply ? [reply.actor] : [],
|
|
357
|
-
quote: rowQuote(s),
|
|
358
|
-
});
|
|
359
|
-
await podNotes.write(publisher.remote, note.id, note);
|
|
360
|
-
await podNotes.writeCreate(publisher.remote, wire.createActivityId(note.id), wire.createActivity(note, urls));
|
|
361
|
-
const patched = publisher.store.updateStatus(s.noteId, {
|
|
362
|
-
content: note.content, text: content, editedAt: updated,
|
|
363
|
-
spoiler: spoilerText || undefined,
|
|
364
|
-
sensitive: note.sensitive || undefined,
|
|
365
|
-
attachments: atts.length ? atts : undefined,
|
|
366
|
-
mentions: rowTags(note).mentions,
|
|
367
|
-
tags: rowTags(note).tags,
|
|
368
|
-
});
|
|
369
|
-
const update = wire.updateActivity(note, urls);
|
|
370
|
-
const contacts = publisher.store.getContacts();
|
|
371
|
-
const inboxes = [...new Set([
|
|
372
|
-
...(s.visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
|
|
373
|
-
...mentions.map(m => m.inbox),
|
|
374
|
-
reply?.inbox,
|
|
375
|
-
].filter(Boolean))];
|
|
376
|
-
await publisher.deliverer.deliverToAll(inboxes, update);
|
|
377
|
-
publisher.log(`note edited: ${note.id} → ${inboxes.length} inbox(es)`);
|
|
378
|
-
return patched;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// What a note's document says about its quote, read off the timeline row: the
|
|
382
|
-
// quoted post and, once granted, the authorization. A quote the author
|
|
383
|
-
// refused is no longer said.
|
|
384
|
-
export function rowQuote(s) {
|
|
385
|
-
if (!s?.quote || s.quoteState === 'rejected') return null;
|
|
386
|
-
return { id: s.quote, ...(s.quoteAuthorization ? { authorization: s.quoteAuthorization } : {}) };
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// The note's document again, unchanged in what it says, changed in what it
|
|
390
|
-
// says about its quote. Not an edit — no `updated` stamp — so nobody shows it
|
|
391
|
-
// as edited; the Update goes where the Create went, plus to the quoted author.
|
|
392
|
-
async function restateNote(publisher, s, { quote }) {
|
|
393
|
-
const { urls } = publisher;
|
|
394
|
-
const content = s.text ?? '';
|
|
395
|
-
const mentions = await publisher._mentionsFor(content, s.inReplyTo);
|
|
396
|
-
const container = String(s.noteId).startsWith(urls.privateNotes) ? urls.privateNotes : urls.notes;
|
|
397
|
-
const slug = s.slug || String(s.noteId).slice(container.length);
|
|
398
|
-
const reply = await replyTarget(publisher, s.inReplyTo, mentions, s.visibility || 'public');
|
|
399
|
-
const note = wire.noteDoc({
|
|
400
|
-
urls, slug, content, published: s.published, inReplyTo: s.inReplyTo,
|
|
401
|
-
attachments: s.attachments || [], mentions, visibility: s.visibility || 'public',
|
|
402
|
-
summary: s.spoiler || null, sensitive: !!s.sensitive, updated: s.editedAt || null, container,
|
|
403
|
-
also: [...(reply ? [reply.actor] : []), ...(s.quoteRequest?.actor ? [s.quoteRequest.actor] : [])],
|
|
404
|
-
quote,
|
|
405
|
-
});
|
|
406
|
-
await podNotes.write(publisher.remote, note.id, note);
|
|
407
|
-
await podNotes.writeCreate(publisher.remote, wire.createActivityId(note.id), wire.createActivity(note, urls));
|
|
408
|
-
const update = wire.updateActivity(note, urls, { serial: Date.now() });
|
|
409
|
-
const contacts = publisher.store.getContacts();
|
|
410
|
-
const inboxes = [...new Set([
|
|
411
|
-
...(s.visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
|
|
412
|
-
...mentions.map(m => m.inbox),
|
|
413
|
-
reply?.inbox,
|
|
414
|
-
...(await inboxesFor(publisher, s.quoteRequest?.actor ? [s.quoteRequest.actor] : [])),
|
|
415
|
-
].filter(Boolean))];
|
|
416
|
-
await publisher.deliverer.deliverToAll(inboxes, update);
|
|
417
|
-
return inboxes.length;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// The quoted author said yes: their authorization goes onto the note, and
|
|
421
|
-
// everyone who has the note is told.
|
|
422
|
-
export async function authorizeQuote(publisher, s, authorizationId) {
|
|
423
|
-
const patched = publisher.store.updateStatus(s.noteId, { quoteState: 'accepted', quoteAuthorization: authorizationId });
|
|
424
|
-
const n = await restateNote(publisher, patched || s, { quote: { id: s.quote, authorization: authorizationId } });
|
|
425
|
-
publisher.log(`quote allowed by ${s.quoteRequest?.actor || 'the author'}: ${s.noteId} → ${n} inbox(es)`);
|
|
426
|
-
return patched;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// The quoted author said no: the note stops naming their post.
|
|
430
|
-
export async function withdrawQuote(publisher, s) {
|
|
431
|
-
const patched = publisher.store.updateStatus(s.noteId, { quoteState: 'rejected', quoteAuthorization: undefined });
|
|
432
|
-
const n = await restateNote(publisher, patched || s, { quote: null });
|
|
433
|
-
publisher.log(`quote refused by ${s.quoteRequest?.actor || 'the author'}: ${s.noteId} → ${n} inbox(es)`);
|
|
434
|
-
return patched;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Somebody may quote one of this actor's posts: the authorization is written
|
|
438
|
-
// beside the post — public-Read, like the post — and remembered on its row,
|
|
439
|
-
// so a receiver that checks the quote finds it and a re-asked question gets
|
|
440
|
-
// the same answer. Returns the authorization's id.
|
|
441
|
-
const MAX_QUOTED_BY = 200;
|
|
442
|
-
export async function grantQuote(publisher, noteId, instrumentId) {
|
|
443
|
-
const { urls } = publisher;
|
|
444
|
-
const id = wireQuotes.quoteAuthorizationId(noteId, instrumentId);
|
|
445
|
-
await podNotes.write(publisher.remote, id, wireQuotes.quoteAuthorizationDoc({ urls, id, instrument: instrumentId, target: noteId }));
|
|
446
|
-
const s = publisher.store.getStatuses().find(x => x.noteId === noteId);
|
|
447
|
-
if (s) {
|
|
448
|
-
const quotedBy = (s.quotedBy || []).filter(q => q.note !== instrumentId);
|
|
449
|
-
quotedBy.unshift({ note: instrumentId, authorization: id });
|
|
450
|
-
publisher.store.updateStatus(noteId, { quotedBy: quotedBy.slice(0, MAX_QUOTED_BY) });
|
|
451
|
-
}
|
|
452
|
-
return id;
|
|
453
|
-
}
|