fedipod-server 0.10.0 → 0.12.1
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 +15 -6
- package/dist/claims.js +1 -1
- package/dist/handler.d.ts +7 -1
- package/dist/handler.js +58 -12
- package/dist/handler.jsonld +5 -1
- package/dist/store-pod.js +18 -4
- package/lib/{c2s.mjs → client/c2s.mjs} +8 -3
- package/lib/{localapi.mjs → client/localapi.mjs} +2 -2
- package/lib/client/masto/accounts.mjs +264 -0
- package/lib/client/masto/body.mjs +69 -0
- package/lib/client/masto/index.mjs +183 -0
- package/lib/client/masto/instance.mjs +104 -0
- package/lib/client/masto/media.mjs +133 -0
- package/lib/client/masto/oauth.mjs +599 -0
- package/lib/client/masto/render.mjs +459 -0
- package/lib/client/masto/statuses.mjs +331 -0
- package/lib/client/masto/timelines.mjs +316 -0
- package/lib/{streaming.mjs → client/streaming.mjs} +1 -1
- package/lib/{acctfeed.mjs → connections/acctfeed.mjs} +1 -1
- package/lib/{atproto.mjs → connections/atproto.mjs} +15 -16
- package/lib/{bskygroup.mjs → connections/bskygroup.mjs} +1 -1
- package/lib/{fediacct.mjs → connections/fediacct.mjs} +31 -35
- package/lib/{import.mjs → connections/import.mjs} +2 -2
- package/lib/{tagfeed.mjs → connections/tagfeed.mjs} +21 -4
- package/lib/connections/vault.mjs +114 -0
- package/lib/core/as2.mjs +124 -0
- package/lib/core/contexts/activitystreams.json +379 -0
- package/lib/core/contexts/did-v1.json +57 -0
- package/lib/core/contexts/fep-5711.json +36 -0
- package/lib/core/contexts/gotosocial.json +86 -0
- package/lib/core/contexts/identity-v1.json +152 -0
- package/lib/core/contexts/index.mjs +45 -0
- package/lib/core/contexts/join-lemmy.json +33 -0
- package/lib/core/contexts/joinmastodon.json +28 -0
- package/lib/core/contexts/map.json +16 -0
- package/lib/core/contexts/miscellany.json +19 -0
- package/lib/core/contexts/schemaorg.json +8845 -0
- package/lib/core/contexts/security-data-integrity-v1.json +78 -0
- package/lib/core/contexts/security-data-integrity-v2.json +81 -0
- package/lib/core/contexts/security-multikey-v1.json +35 -0
- package/lib/core/contexts/security-v1.json +74 -0
- package/lib/core/contexts/webfinger.json +10 -0
- package/lib/{deliver.mjs → core/deliver.mjs} +8 -2
- package/lib/core/intake/activities.mjs +437 -0
- package/lib/core/intake/activity.mjs +240 -0
- package/lib/core/intake/channel.mjs +144 -0
- package/lib/core/intake/group.mjs +222 -0
- package/lib/core/intake/index.mjs +629 -0
- package/lib/core/intake/notes.mjs +288 -0
- package/lib/core/intake/verify.mjs +141 -0
- package/lib/{keys.mjs → core/keys.mjs} +1 -1
- package/lib/core/publisher/collections.mjs +229 -0
- package/lib/core/publisher/index.mjs +421 -0
- package/lib/core/publisher/notes.mjs +188 -0
- package/lib/core/publisher/questions.mjs +233 -0
- package/lib/core/publisher/restore.mjs +196 -0
- package/lib/core/shapes/activitystreams.ttl +129 -0
- package/lib/core/shapes/index.mjs +107 -0
- package/lib/core/shapes/shapes-text.mjs +13 -0
- package/lib/{social.mjs → core/social.mjs} +19 -10
- package/lib/{storage.mjs → core/storage.mjs} +2 -2
- package/lib/{store.mjs → core/store.mjs} +12 -1
- package/lib/{wire.mjs → core/wire.mjs} +48 -67
- package/lib/device/admin/index.mjs +13 -0
- package/lib/device/admin/origins.mjs +35 -0
- package/lib/device/admin/routes/connections.mjs +144 -0
- package/lib/device/admin/routes/gateway.mjs +199 -0
- package/lib/device/admin/routes/lifecycle.mjs +191 -0
- package/lib/device/admin/routes/owner.mjs +322 -0
- package/lib/device/admin/routes/setup.mjs +393 -0
- package/lib/device/admin/routes/social.mjs +188 -0
- package/lib/device/admin/server.mjs +95 -0
- package/lib/device/admin/static.mjs +244 -0
- package/lib/device/admin/surface.mjs +274 -0
- package/lib/device/cli/commands/account.mjs +586 -0
- package/lib/device/cli/commands/run.mjs +278 -0
- package/lib/device/cli/commands/service.mjs +221 -0
- package/lib/device/cli/commands/setup.mjs +410 -0
- package/lib/device/cli/commands/state.mjs +559 -0
- package/lib/device/cli/context.mjs +288 -0
- package/lib/{export-collections.mjs → device/export-collections.mjs} +1 -1
- package/lib/{migrate.mjs → device/migrate.mjs} +5 -19
- package/lib/device/remote.mjs +54 -0
- package/lib/{setup.mjs → device/setup.mjs} +3 -3
- package/lib/{update.mjs → device/update.mjs} +1 -1
- package/lib/{directory.mjs → gateway/directory.mjs} +1 -1
- package/lib/{front-core.mjs → gateway/front-core.mjs} +272 -49
- package/lib/{gateway-core.mjs → gateway/gateway-core.mjs} +3 -7
- package/lib/{httpsig.mjs → gateway/httpsig.mjs} +34 -3
- package/lib/pod/README.md +61 -0
- package/lib/pod/actor.mjs +96 -0
- package/lib/pod/collection.mjs +77 -0
- package/lib/pod/containers.mjs +136 -0
- package/lib/pod/discovery.mjs +46 -0
- package/lib/pod/featured.mjs +18 -0
- package/lib/pod/followers.mjs +24 -0
- package/lib/pod/following.mjs +10 -0
- package/lib/pod/http.mjs +74 -0
- package/lib/pod/inbox.mjs +144 -0
- package/lib/{links.mjs → pod/links.mjs} +1 -1
- package/lib/pod/media.mjs +27 -0
- package/lib/pod/notes.mjs +80 -0
- package/lib/pod/notifications.mjs +89 -0
- package/lib/pod/outbox.mjs +18 -0
- package/lib/pod/package.json +19 -0
- package/lib/pod/policy.mjs +58 -0
- package/lib/pod/private.mjs +19 -0
- package/lib/pod/root.mjs +63 -0
- package/lib/pod/state.mjs +39 -0
- package/lib/{remote.mjs → pod/transport.mjs} +217 -159
- package/lib/pod/urls.mjs +71 -0
- package/lib/{embed.mjs → server/embed.mjs} +149 -22
- package/lib/shared/links.mjs +9 -0
- package/lib/{ua.mjs → shared/ua.mjs} +1 -1
- package/package.json +1 -1
- package/run-agent.mjs +47 -64
- package/web/admin/actors.js +145 -0
- package/web/admin/bar.css +9 -13
- package/web/admin/bar.js +2 -2
- package/web/admin/client/client.js +1 -1
- package/web/admin/client/index.html +5 -6
- package/web/admin/common.js +23 -0
- package/web/admin/connections.js +112 -0
- package/web/admin/gateway.js +111 -0
- package/web/admin/group.js +258 -0
- package/web/admin/index.html +60 -66
- package/web/admin/record.js +378 -0
- package/web/admin/setup/index.html +3 -1
- package/web/admin/setup/setup.js +2 -13
- package/web/admin/tokens.css +56 -0
- package/web/admin/upkeep.js +170 -0
- package/web/app/README.md +77 -0
- package/web/app/admin-facade.mjs +549 -0
- package/web/app/agent.mjs +490 -0
- package/web/app/atproto-browser.mjs +86 -0
- package/web/app/boot.mjs +340 -0
- package/web/app/deliver-relay.mjs +72 -0
- package/web/app/dist/boot.js +34067 -0
- package/web/app/dist/boot.js.map +7 -0
- package/web/app/dist/sw.js +70014 -0
- package/web/app/dist/sw.js.map +7 -0
- package/web/app/fediacct-browser.mjs +146 -0
- package/web/app/idb-kv.mjs +35 -0
- package/web/app/index.html +203 -0
- package/web/app/index.html~ +152 -0
- package/web/app/keys-browser.mjs +46 -0
- package/web/app/keystore.mjs +99 -0
- package/web/app/oidc-session.mjs +189 -0
- package/web/app/pod-auth.mjs +248 -0
- package/web/app/pod-remote.mjs +84 -0
- package/web/app/shims/_globals.mjs +2 -0
- package/web/app/shims/fedify-sig.mjs +77 -0
- package/web/app/shims/node-crypto.mjs +113 -0
- package/web/app/shims/node-fs.mjs +17 -0
- package/web/app/shims/node-path.mjs +19 -0
- package/web/app/shims/node-url.mjs +7 -0
- package/web/app/shims/prelude.js +58 -0
- package/web/app/shims/safefetch.mjs +58 -0
- package/web/app/shims/shapes-text.mjs +8 -0
- package/web/app/shims/web-push.mjs +14 -0
- package/web/app/signup.mjs +248 -0
- package/web/app/site/_headers +9 -0
- package/web/app/site/_redirects +21 -0
- package/web/app/site/admin/actors.js +145 -0
- package/web/app/site/admin/bar.css +60 -0
- package/web/app/site/admin/bar.js +61 -0
- package/web/app/site/admin/client/client.js +66 -0
- package/web/app/site/admin/client/index.html +43 -0
- package/web/app/site/admin/common.js +23 -0
- package/web/app/site/admin/connections.js +112 -0
- package/web/app/site/admin/gateway.js +111 -0
- package/web/app/site/admin/group.js +258 -0
- package/web/app/site/admin/index.html +562 -0
- package/web/app/site/admin/record.js +378 -0
- package/web/app/site/admin/setup/index.html +192 -0
- package/web/app/site/admin/setup/setup.js +297 -0
- package/web/app/site/admin/tokens.css +56 -0
- package/web/app/site/admin/upkeep.js +170 -0
- package/web/app/site/admin/window.css +55 -0
- package/web/app/site/admin/window.js +136 -0
- package/web/app/site/app/404.html +40 -0
- package/web/app/site/app/_headers +2 -0
- package/web/app/site/app/apple-touch-icon.png +0 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js +2 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +1 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +183 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
- package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +2 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +1 -0
- package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
- package/web/app/site/app/assets/chunk-aKtaBQYM.js +1 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js +2 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js.map +1 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js +35 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +1 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js +52 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +1 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js +2 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +1 -0
- package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +1 -0
- package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +1 -0
- package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
- package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
- package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
- package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
- package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
- package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
- package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +1 -0
- package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +1 -0
- package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
- package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +1 -0
- package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
- package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +1 -0
- package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
- package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
- package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +1 -0
- package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
- package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +1 -0
- package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
- package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
- package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
- package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
- package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
- package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +1 -0
- package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +1 -0
- package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +1 -0
- package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +1 -0
- package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +1 -0
- package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
- package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +1 -0
- package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
- package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +1 -0
- package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +1 -0
- package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +1 -0
- package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
- package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
- package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +1 -0
- package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +1 -0
- package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
- package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
- package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +1 -0
- package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +1 -0
- package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +1 -0
- package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
- package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +1 -0
- package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +1 -0
- package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +1 -0
- package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
- package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
- package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
- package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +1 -0
- package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +1 -0
- package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +1 -0
- package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +1 -0
- package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +1 -0
- package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
- package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
- package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +1 -0
- package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +1 -0
- package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +1 -0
- package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +1 -0
- package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +1 -0
- package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +1 -0
- package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +1 -0
- package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +1 -0
- package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
- package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +1 -0
- package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +1 -0
- package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +1 -0
- package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
- package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +1 -0
- package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +1 -0
- package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
- package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
- package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
- package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +1 -0
- package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +1 -0
- package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
- package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +1 -0
- package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +1 -0
- package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +1 -0
- package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +1 -0
- package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +1 -0
- package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +1 -0
- package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +1 -0
- package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +1 -0
- package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +1 -0
- package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
- package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +1 -0
- package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
- package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +1 -0
- package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
- package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +1 -0
- package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +1 -0
- package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
- package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +1 -0
- package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
- package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +1 -0
- package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
- package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +1 -0
- package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +1 -0
- package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
- package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +1 -0
- package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +1 -0
- package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
- package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +1 -0
- package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +1 -0
- package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +1 -0
- package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
- package/web/app/site/app/assets/instances-BNPptnn-.json +333 -0
- package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +1 -0
- package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
- package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +1 -0
- package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +1 -0
- package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +1 -0
- package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +1 -0
- package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +1 -0
- package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +1 -0
- package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +1 -0
- package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +1 -0
- package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +1 -0
- package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +1 -0
- package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +1 -0
- package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +1 -0
- package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
- package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
- package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +1 -0
- package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
- package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +1 -0
- package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +1 -0
- package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
- package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tok-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +1 -0
- package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +1 -0
- package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js +26 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js.map +1 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js +2 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js.map +1 -0
- package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
- package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +2 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +1 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +20 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +1 -0
- package/web/app/site/app/assets/style-B1iLPTPR.css +2 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js +97 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js.map +1 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +2 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
- package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js +3 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +1 -0
- package/web/app/site/app/compose/index.html +20 -0
- package/web/app/site/app/favicon.ico +0 -0
- package/web/app/site/app/index.html +73 -0
- package/web/app/site/app/logo-192.png +0 -0
- package/web/app/site/app/logo-512.png +0 -0
- package/web/app/site/app/logo-badge-72.png +0 -0
- package/web/app/site/app/logo-maskable-512.png +0 -0
- package/web/app/site/app/logo-monochrome-512.png +0 -0
- package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
- package/web/app/site/app/manifest.webmanifest +1 -0
- package/web/app/site/app/og-image-2.jpg +0 -0
- package/web/app/site/app/og-image.png +0 -0
- package/web/app/site/app/robots.txt +2 -0
- package/web/app/site/app/version.json +1 -0
- package/web/app/site/boot.js +34067 -0
- package/web/app/site/index.html +203 -0
- package/web/app/site/sw.js +70014 -0
- package/web/app/sw-src.mjs +220 -0
- package/web/front/#new-account.html# +43 -0
- package/web/front/admin.html +1 -81
- package/web/front/admin.js +85 -0
- package/web/front/new-account.html +1 -9
- package/web/front/new-account.js +13 -0
- package/web/front/run.html +1 -92
- package/web/front/run.js +107 -0
- package/lib/admin.mjs +0 -1913
- package/lib/intake.mjs +0 -1764
- package/lib/mastoapi.mjs +0 -1947
- package/lib/podrdf.mjs +0 -132
- package/lib/publisher.mjs +0 -1263
- package/web/admin/admin.js +0 -1114
- /package/lib/{oidc-auth.mjs → client/oidc-auth.mjs} +0 -0
- /package/lib/{webpush.mjs → client/webpush.mjs} +0 -0
- /package/lib/{bskyfeed.mjs → connections/bskyfeed.mjs} +0 -0
- /package/lib/{lease.mjs → core/lease.mjs} +0 -0
- /package/lib/{polls.mjs → core/polls.mjs} +0 -0
- /package/lib/{proof.mjs → core/proof.mjs} +0 -0
- /package/lib/{account.mjs → device/account.mjs} +0 -0
- /package/lib/{certs.mjs → device/certs.mjs} +0 -0
- /package/lib/{home.mjs → device/home.mjs} +0 -0
- /package/lib/{ports.mjs → device/ports.mjs} +0 -0
- /package/lib/{guard.mjs → shared/guard.mjs} +0 -0
- /package/lib/{safefetch.mjs → shared/safefetch.mjs} +0 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// render.mjs — Mastodon's JSON shapes out of the agent's records: accounts,
|
|
2
|
+
// statuses and the carry that wraps one, notifications, polls, media,
|
|
3
|
+
// relationships, filters, and the cursor paging with its Link header. Also
|
|
4
|
+
// the three actions that answer as a connected account rather than the pod
|
|
5
|
+
// actor (a Bluesky reply, a connected-account reply, a like or boost there).
|
|
6
|
+
|
|
7
|
+
import * as social from '../../core/social.mjs';
|
|
8
|
+
import { sanitizeHtml, followsNeedApproval, publicHandle } from '../../core/wire.mjs';
|
|
9
|
+
import { profileUrl, postUrl } from '../../connections/bskyfeed.mjs';
|
|
10
|
+
import { htmlToText } from './body.mjs';
|
|
11
|
+
|
|
12
|
+
// 1x1 transparent PNG — placeholder avatar/header for accounts without icons.
|
|
13
|
+
export const TRANSPARENT_PNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
|
|
14
|
+
|
|
15
|
+
const selfIcon = (api, self, cached) =>
|
|
16
|
+
(self ? api.store.getConfig()?.icon : null) || cached.icon;
|
|
17
|
+
|
|
18
|
+
export function selfAccount(api) {
|
|
19
|
+
// No fallback handle. `account()` already reads config for self, so a
|
|
20
|
+
// literal here only ever supplies a name that is not this actor's — and it
|
|
21
|
+
// was a real person's, so every install with no handle called itself jeff.
|
|
22
|
+
return api.account(api.urls.actor, { selfAcct: publicHandle(api.store.getConfig()) });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function account(api, actorUrl, { selfAcct } = {}) {
|
|
26
|
+
const cached = api.store.getActors()[actorUrl] || {};
|
|
27
|
+
let host = '', user = cached.preferredUsername || '';
|
|
28
|
+
try { host = new URL(actorUrl).host; if (!user) user = new URL(actorUrl).pathname.split('/').pop(); } catch {}
|
|
29
|
+
const self = actorUrl === api.urls?.actor;
|
|
30
|
+
if (self) user = selfAcct || publicHandle(api.store.getConfig()) || user;
|
|
31
|
+
return {
|
|
32
|
+
id: api.store.idFor(actorUrl),
|
|
33
|
+
username: user,
|
|
34
|
+
// Self gets the FULL acct (Mastodon proper returns the bare local part
|
|
35
|
+
// here): the client's login domain is the loopback agent, so the bare
|
|
36
|
+
// form would display as user@127.0.0.1 — the full form shows the real
|
|
37
|
+
// fediverse identity, and every client renders @-containing accts as-is.
|
|
38
|
+
acct: `${self ? (selfAcct || user) : user}@${host}`,
|
|
39
|
+
// Our own profile is not in the actor cache — the cache is for other
|
|
40
|
+
// people — so read it from config, or the editor opens empty and saving
|
|
41
|
+
// wipes what was there.
|
|
42
|
+
display_name: (self ? api.store.getConfig()?.name : cached.name) || cached.name || user,
|
|
43
|
+
locked: self ? followsNeedApproval(api.store.getConfig() || {}) : false,
|
|
44
|
+
// Read from config for self, like the fields above it: our own actor is
|
|
45
|
+
// not in the actor cache — the cache is for other people — so a group
|
|
46
|
+
// asking about itself would be told it was a person.
|
|
47
|
+
bot: false, discoverable: true,
|
|
48
|
+
group: self ? api.store.getConfig()?.kind === 'group' : cached.type === 'Group',
|
|
49
|
+
created_at: '2026-01-01T00:00:00.000Z',
|
|
50
|
+
note: (self ? api.store.getConfig()?.summary : cached.summary) || '',
|
|
51
|
+
url: actorUrl, uri: actorUrl,
|
|
52
|
+
avatar: selfIcon(api, self, cached) || TRANSPARENT_PNG,
|
|
53
|
+
avatar_static: selfIcon(api, self, cached) || TRANSPARENT_PNG,
|
|
54
|
+
header: (self ? api.store.getConfig()?.image : null) || TRANSPARENT_PNG,
|
|
55
|
+
header_static: (self ? api.store.getConfig()?.image : null) || TRANSPARENT_PNG,
|
|
56
|
+
// A remote actor's counts are whatever its own collections said when we
|
|
57
|
+
// last asked; unknown stays 0 because the API has no way to say "unknown".
|
|
58
|
+
followers_count: self ? api.store.getContacts().followers.length : (cached.counts?.followers ?? 0),
|
|
59
|
+
// Accepted only, matching both the published `following` collection and
|
|
60
|
+
// the list this number opens — a pending Follow is not yet a following.
|
|
61
|
+
following_count: self ? api.store.getContacts().following.filter(f => f.accepted).length : (cached.counts?.following ?? 0),
|
|
62
|
+
statuses_count: self ? api.store.getStatuses().filter(s => s.kind === 'post').length : 0,
|
|
63
|
+
last_status_at: null, emojis: [],
|
|
64
|
+
fields: (self ? api.store.getConfig()?.fields : cached.fields) || [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// `all` is not an optimisation, it is the difference between one clone and
|
|
69
|
+
// one per status: store.getStatuses() structuredClones the entire array, so
|
|
70
|
+
// rendering a 40-status timeline without it cloned a 1000-entry array 40
|
|
71
|
+
// times purely to count replies. Every caller that renders more than one
|
|
72
|
+
// status passes it; this fallback is for the single-status paths.
|
|
73
|
+
// Mastodon's cursor paging, over an array already in newest-first order.
|
|
74
|
+
// Clients do not read a `next` out of the body — they follow the Link header,
|
|
75
|
+
// which nothing here emitted, so a client could only ever see the first page.
|
|
76
|
+
// `idOf` is what a cursor names. Statuses are cursored by note, account lists
|
|
77
|
+
// by actor, so the caller says which field carries the id.
|
|
78
|
+
export function page(api, items, url, { limit = 20, max = 40, idOf = (s) => api.store.idFor(s.noteId) } = {}) {
|
|
79
|
+
const n = Math.min(Number(url.searchParams.get('limit')) || limit, max);
|
|
80
|
+
// The cursor scan is linear over every status, and `idOf` defaults to
|
|
81
|
+
// store.idFor, which structuredClones the whole uncapped ids.json on every
|
|
82
|
+
// call. Scrolling a 5000-status timeline was therefore millions of object
|
|
83
|
+
// copies of synchronous, event-loop-blocking work to serve one page of 20,
|
|
84
|
+
// and it ran again for since_id and min_id. The id is a pure function of
|
|
85
|
+
// the URL, so remembering it per item within one call is free and exact.
|
|
86
|
+
const ids = new Map();
|
|
87
|
+
const idAt = (s) => {
|
|
88
|
+
if (!ids.has(s)) ids.set(s, idOf(s));
|
|
89
|
+
return ids.get(s);
|
|
90
|
+
};
|
|
91
|
+
const cut = (param) => {
|
|
92
|
+
const v = url.searchParams.get(param);
|
|
93
|
+
if (!v) return null;
|
|
94
|
+
const i = items.findIndex(s => idAt(s) === v);
|
|
95
|
+
return i < 0 ? null : i;
|
|
96
|
+
};
|
|
97
|
+
const maxAt = cut('max_id');
|
|
98
|
+
if (maxAt != null) items = items.slice(maxAt + 1); // older than this one
|
|
99
|
+
for (const p of ['since_id', 'min_id']) {
|
|
100
|
+
const at = cut(p);
|
|
101
|
+
if (at != null) items = items.slice(0, at); // newer than this one
|
|
102
|
+
}
|
|
103
|
+
const pageItems = items.slice(0, n);
|
|
104
|
+
if (!pageItems.length) return { items: pageItems, headers: {} };
|
|
105
|
+
const base = `http://${api.host}${url.pathname}`;
|
|
106
|
+
const q = (extra) => {
|
|
107
|
+
const u = new URL(base);
|
|
108
|
+
for (const [k, v] of url.searchParams) if (!['max_id', 'since_id', 'min_id'].includes(k)) u.searchParams.set(k, v);
|
|
109
|
+
for (const [k, v] of Object.entries(extra)) u.searchParams.set(k, v);
|
|
110
|
+
return u.href;
|
|
111
|
+
};
|
|
112
|
+
const links = [`<${q({ max_id: idOf(pageItems[pageItems.length - 1]) })}>; rel="next"`];
|
|
113
|
+
if (pageItems.length) links.push(`<${q({ min_id: idOf(pageItems[0]) })}>; rel="prev"`);
|
|
114
|
+
return { items: pageItems, headers: { link: links.join(', ') } };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// A reply to a mirrored Bluesky post: a native reply from the connected
|
|
118
|
+
// account, threaded under the original. It exists only on Bluesky, so the
|
|
119
|
+
// row added here is its one local copy.
|
|
120
|
+
export async function bskyReply(api, send, body, parent, visibility) {
|
|
121
|
+
const at = api.agent.atproto;
|
|
122
|
+
if (!at?.connected()) return send(422, { error: 'this is a Bluesky post — no Bluesky account is connected to reply from' });
|
|
123
|
+
if (visibility !== 'public' && visibility !== 'unlisted') {
|
|
124
|
+
return send(422, { error: 'a Bluesky reply is public — pick public visibility' });
|
|
125
|
+
}
|
|
126
|
+
if (body.scheduled_at) return send(422, { error: 'a Bluesky reply cannot be scheduled' });
|
|
127
|
+
if ([].concat(body.media_ids || body['media_ids[]'] || []).filter(Boolean).length) {
|
|
128
|
+
return send(422, { error: 'images on a Bluesky reply are not supported' });
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const out = await at.reply(body.status, parent.noteId);
|
|
132
|
+
const rec = at.read();
|
|
133
|
+
const actor = profileUrl(rec.did);
|
|
134
|
+
if (!api.store.getActors()[actor]) {
|
|
135
|
+
api.store.cacheActor(actor, { name: rec.handle, preferredUsername: rec.handle, type: 'Person' });
|
|
136
|
+
}
|
|
137
|
+
const text = String(body.status).trim();
|
|
138
|
+
api.store.addStatus({
|
|
139
|
+
noteId: out.uri, actor, inReplyTo: parent.noteId,
|
|
140
|
+
content: `<p>${text.replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' }[c]))}</p>`,
|
|
141
|
+
published: new Date().toISOString(), kind: 'bsky',
|
|
142
|
+
...(out.cid ? { cid: out.cid } : {}), link: postUrl(out.uri),
|
|
143
|
+
});
|
|
144
|
+
return send(200, api.status(api.store.getStatuses().find(x => x.noteId === out.uri)));
|
|
145
|
+
} catch (e) { return send(422, { error: e.message }); }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Which account acts: the one that saw the post, and the first of them when
|
|
149
|
+
// several did. Undo inverts that — a like fans IN to one account, an unlike
|
|
150
|
+
// fans OUT to every account holding one, because a stray like left behind
|
|
151
|
+
// after the owner asked for it to go is the worse failure.
|
|
152
|
+
export async function acctAction(api, send, s, verb) {
|
|
153
|
+
const accounts = api.agent.fediaccts;
|
|
154
|
+
const held = s.sourceAccts || [];
|
|
155
|
+
if (!accounts || !held.length) {
|
|
156
|
+
return send(422, { error: 'this post came from a connected account, and none is connected now' });
|
|
157
|
+
}
|
|
158
|
+
const flag = verb.endsWith('favourite') ? 'favourited' : 'reblogged';
|
|
159
|
+
const undo = verb.startsWith('un');
|
|
160
|
+
const targets = (undo ? held.filter(v => v[flag]) : [held[0]]).filter(v => v?.remoteId);
|
|
161
|
+
if (!targets.length) return send(200, api.status(s));
|
|
162
|
+
try {
|
|
163
|
+
const acted = new Set();
|
|
164
|
+
for (const v of targets) {
|
|
165
|
+
await accounts.api(v.acct,
|
|
166
|
+
`/api/v1/statuses/${encodeURIComponent(v.remoteId)}/${verb}`, { method: 'POST' });
|
|
167
|
+
acted.add(v.acct);
|
|
168
|
+
}
|
|
169
|
+
const next = held.map(v => (acted.has(v.acct) ? { ...v, [flag]: !undo } : v));
|
|
170
|
+
return send(200, api.status(api.store.updateStatus(s.noteId, { sourceAccts: next }) || s));
|
|
171
|
+
} catch (e) { return send(e.status === 401 ? 401 : 422, { error: e.message }); }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// The reply exists only on that account's server, so the row added here is
|
|
175
|
+
// its one local copy — the same shape bskyReply uses for the same reason.
|
|
176
|
+
export async function acctReply(api, send, body, parent, visibility) {
|
|
177
|
+
const accounts = api.agent.fediaccts;
|
|
178
|
+
const held = (parent.sourceAccts || [])[0];
|
|
179
|
+
if (!accounts || !held?.remoteId) {
|
|
180
|
+
return send(422, { error: 'this post came from a connected account, and none is connected now' });
|
|
181
|
+
}
|
|
182
|
+
if (visibility !== 'public' && visibility !== 'unlisted') {
|
|
183
|
+
return send(422, { error: 'a reply from a connected account is public — pick public or unlisted' });
|
|
184
|
+
}
|
|
185
|
+
if (body.scheduled_at) return send(422, { error: 'a reply from a connected account cannot be scheduled' });
|
|
186
|
+
if ([].concat(body.media_ids || body['media_ids[]'] || []).filter(Boolean).length) {
|
|
187
|
+
return send(422, { error: 'images on a reply from a connected account are not supported' });
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const out = await accounts.apiJson(held.acct, '/api/v1/statuses', {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
headers: { 'content-type': 'application/json' },
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
status: body.status, in_reply_to_id: held.remoteId, visibility,
|
|
195
|
+
...(body.spoiler_text ? { spoiler_text: String(body.spoiler_text) } : {}),
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
if (!out?.uri) return send(502, { error: 'that server accepted the reply but did not say where it is' });
|
|
199
|
+
const rec = accounts.read(held.acct);
|
|
200
|
+
const actor = out.account?.uri || out.account?.url || rec?.actorUrl;
|
|
201
|
+
if (actor && !api.store.getActors()[actor]) {
|
|
202
|
+
api.store.cacheActor(actor, {
|
|
203
|
+
name: out.account?.display_name || rec?.name,
|
|
204
|
+
preferredUsername: out.account?.username || rec?.acct, type: 'Person',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
api.store.addStatus({
|
|
208
|
+
noteId: out.uri, actor, inReplyTo: parent.noteId,
|
|
209
|
+
content: sanitizeHtml(out.content || ''),
|
|
210
|
+
published: out.created_at || new Date().toISOString(), kind: 'acct',
|
|
211
|
+
sourceAccts: [{ acct: held.acct, remoteId: String(out.id) }],
|
|
212
|
+
...(out.url && out.url !== out.uri ? { link: out.url } : {}),
|
|
213
|
+
});
|
|
214
|
+
return send(200, api.status(api.store.getStatuses().find(x => x.noteId === out.uri)));
|
|
215
|
+
} catch (e) { return send(422, { error: e.message }); }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function status(api, s, { all } = {}) {
|
|
219
|
+
const replies = (all || api.store.getStatuses()).filter(x => x.inReplyTo === s.noteId).length;
|
|
220
|
+
return {
|
|
221
|
+
id: api.store.idFor(s.noteId),
|
|
222
|
+
created_at: s.published || new Date().toISOString(),
|
|
223
|
+
in_reply_to_id: s.inReplyTo ? api.store.idFor(s.inReplyTo) : null,
|
|
224
|
+
in_reply_to_account_id: null,
|
|
225
|
+
sensitive: !!s.spoiler, spoiler_text: s.spoiler || '',
|
|
226
|
+
visibility: s.visibility || 'public', language: null,
|
|
227
|
+
edited_at: s.editedAt || null,
|
|
228
|
+
uri: s.noteId, url: s.link || s.noteId,
|
|
229
|
+
replies_count: replies, reblogs_count: 0, favourites_count: 0,
|
|
230
|
+
// True when ANY of the owner's accounts holds it. The flag is really
|
|
231
|
+
// what the next tap will do: an empty star on a post one account has
|
|
232
|
+
// already liked invites a second outward like from a second identity.
|
|
233
|
+
favourited: !!s.favourited || (s.sourceAccts || []).some(v => v.favourited),
|
|
234
|
+
reblogged: !!s.reblogged || (s.sourceAccts || []).some(v => v.reblogged),
|
|
235
|
+
muted: false, bookmarked: !!s.bookmarked, pinned: !!s.pinned,
|
|
236
|
+
content: s.content || '',
|
|
237
|
+
reblog: null, application: null,
|
|
238
|
+
account: api.account(s.actor),
|
|
239
|
+
media_attachments: (s.attachments || []).map(a => api.mediaJson(a)),
|
|
240
|
+
// The mention entities are how a client knows a link is an ACCOUNT —
|
|
241
|
+
// without them, clicking a mentioned group lands on the raw actor doc.
|
|
242
|
+
mentions: (s.mentions || []).map((m) => {
|
|
243
|
+
const bare = String(m.name || '').replace(/^@/, '');
|
|
244
|
+
const user = bare.split('@')[0];
|
|
245
|
+
let host = '';
|
|
246
|
+
try { host = new URL(m.href).host; } catch { /* keep bare */ }
|
|
247
|
+
return {
|
|
248
|
+
id: api.store.idFor(m.href),
|
|
249
|
+
username: user || bare,
|
|
250
|
+
url: m.href,
|
|
251
|
+
acct: bare.includes('@') ? bare : (host ? `${user}@${host}` : bare),
|
|
252
|
+
};
|
|
253
|
+
}),
|
|
254
|
+
tags: [],
|
|
255
|
+
emojis: (s.emojis || []).map(e => ({
|
|
256
|
+
shortcode: e.shortcode, url: e.url, static_url: e.url, visible_in_picker: false,
|
|
257
|
+
})),
|
|
258
|
+
card: null, poll: s.poll ? api.pollJson(s) : null,
|
|
259
|
+
// What a filter matched, if any. Mastodon's clients read this and do the
|
|
260
|
+
// hiding or warning; they do NOT match keywords themselves — Phanpy does
|
|
261
|
+
// not — so filters that were stored and served but never applied were a
|
|
262
|
+
// setting that did nothing. The README named them as a feature.
|
|
263
|
+
filtered: api.filtersFor(s),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// v2 filters against one status. `context` is the timeline the client is
|
|
268
|
+
// showing, which we do not know here, so every non-expired filter is offered
|
|
269
|
+
// and the client drops the ones whose context does not match — the same
|
|
270
|
+
// information it uses to decide anyway.
|
|
271
|
+
export function filtersFor(api, s) {
|
|
272
|
+
const now = Date.now();
|
|
273
|
+
const hay = `${s.content || ''} ${s.spoiler || ''}`
|
|
274
|
+
.replace(/<[^>]*>/gu, ' ') // the text, not the markup around it
|
|
275
|
+
.toLowerCase();
|
|
276
|
+
if (!hay.trim()) return [];
|
|
277
|
+
const out = [];
|
|
278
|
+
// `status()` renders on every timeline read, including from the Bluesky and
|
|
279
|
+
// connected-account paths whose stores are narrower than the pod's. A
|
|
280
|
+
// missing filter list means no filters, never a thrown render.
|
|
281
|
+
for (const f of api.store.getFilters?.() || []) {
|
|
282
|
+
if (f.expiresAt && Date.parse(f.expiresAt) <= now) continue;
|
|
283
|
+
const hit = (f.keywords || []).filter((k) => {
|
|
284
|
+
const word = String(k.keyword || '').toLowerCase().trim();
|
|
285
|
+
if (!word) return false;
|
|
286
|
+
if (!k.wholeWord) return hay.includes(word);
|
|
287
|
+
// A whole word, by the same rule Mastodon uses: a boundary that is not
|
|
288
|
+
// itself a word character, at both ends.
|
|
289
|
+
const esc = word.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
290
|
+
return new RegExp(`(?:^|[^\\p{L}\\p{N}_])${esc}(?:[^\\p{L}\\p{N}_]|$)`, 'u').test(hay);
|
|
291
|
+
}).map((k) => k.keyword);
|
|
292
|
+
if (!hit.length) continue;
|
|
293
|
+
out.push({
|
|
294
|
+
filter: {
|
|
295
|
+
id: f.id, title: f.title, context: f.context || ['home'],
|
|
296
|
+
expires_at: f.expiresAt || null, filter_action: f.action || 'warn',
|
|
297
|
+
},
|
|
298
|
+
keyword_matches: hit,
|
|
299
|
+
status_matches: [],
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// A timeline row's id may name the CARRY rather than the post — statusOrBoost
|
|
306
|
+
// mints `via:<noteId>` for the envelope. A client that asks about a row it
|
|
307
|
+
// was served has to get an answer: a 404 there makes it drop the row, which
|
|
308
|
+
// is how carried posts vanished from the home column while every other view
|
|
309
|
+
// still had them.
|
|
310
|
+
export function lookup(api, id) {
|
|
311
|
+
const raw = api.store.urlFor(id);
|
|
312
|
+
if (typeof raw !== 'string') return { s: null, wrapped: false };
|
|
313
|
+
const wrapped = raw.startsWith('via:');
|
|
314
|
+
const noteId = wrapped ? raw.slice(4) : raw;
|
|
315
|
+
return { s: api.store.getStatuses().find(x => x.noteId === noteId) || null, wrapped };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// A carried post, the way clients expect to see one: the carrier "boosts"
|
|
319
|
+
// the inner post, so the feed says who brought it. Timeline views only —
|
|
320
|
+
// fetching the post by its own id still returns the post itself.
|
|
321
|
+
export function statusOrBoost(api, s, opts = {}) {
|
|
322
|
+
if (!s.via || s.via === s.actor) return api.status(s, opts);
|
|
323
|
+
const inner = api.status(s, opts);
|
|
324
|
+
return {
|
|
325
|
+
id: api.store.idFor('via:' + s.noteId),
|
|
326
|
+
created_at: s.announcedAt || s.published || inner.created_at,
|
|
327
|
+
in_reply_to_id: null, in_reply_to_account_id: null,
|
|
328
|
+
sensitive: false, spoiler_text: '', visibility: inner.visibility, language: null,
|
|
329
|
+
edited_at: null,
|
|
330
|
+
uri: s.announceActivity?.id || s.noteId + '#announce',
|
|
331
|
+
url: inner.url,
|
|
332
|
+
replies_count: 0, reblogs_count: 0, favourites_count: 0,
|
|
333
|
+
favourited: false, reblogged: false, muted: false, bookmarked: false, pinned: false,
|
|
334
|
+
content: '', reblog: inner, application: null,
|
|
335
|
+
account: api.account(s.via),
|
|
336
|
+
media_attachments: [], mentions: [], tags: [], emojis: [], card: null, poll: null,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// A notification, pushed. Fire-and-forget from the store's event hook.
|
|
341
|
+
export function pushNotify(api, n) {
|
|
342
|
+
const acct = api.account(n.actor);
|
|
343
|
+
const verbs = {
|
|
344
|
+
mention: 'mentioned you', favourite: 'favourited your post',
|
|
345
|
+
reblog: 'boosted your post', follow: 'followed you',
|
|
346
|
+
'follow-request': 'asked to follow you', move: 'moved account',
|
|
347
|
+
};
|
|
348
|
+
const s = n.noteId && api.store.getStatuses().find(x => x.noteId === n.noteId);
|
|
349
|
+
return api.push.notify(n, {
|
|
350
|
+
notification_id: n.id, notification_type: n.type, preferred_locale: 'en',
|
|
351
|
+
title: `${acct.display_name || acct.acct} ${verbs[n.type] || n.type}`,
|
|
352
|
+
body: s ? htmlToText(s.content || '').slice(0, 140) : '',
|
|
353
|
+
icon: acct.avatar || '',
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function scheduledJson(api, e) {
|
|
358
|
+
return {
|
|
359
|
+
id: e.id, scheduled_at: e.scheduledAt,
|
|
360
|
+
params: {
|
|
361
|
+
text: e.params.status, visibility: e.params.visibility || 'public',
|
|
362
|
+
spoiler_text: e.params.spoilerText || null, sensitive: !!e.params.spoilerText,
|
|
363
|
+
in_reply_to_id: e.params.inReplyTo ? api.store.idFor(e.params.inReplyTo) : null,
|
|
364
|
+
media_ids: (e.params.attachments || []).map(a => a.id),
|
|
365
|
+
poll: null, idempotency: null, scheduled_at: e.scheduledAt, application_id: null,
|
|
366
|
+
},
|
|
367
|
+
media_attachments: (e.params.attachments || []).map(a => api.mediaJson(a)),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function pollJson(api, s) {
|
|
372
|
+
const opts = s.poll.options || [];
|
|
373
|
+
const votes = opts.reduce((n, o) => n + (o.votes || 0), 0);
|
|
374
|
+
return {
|
|
375
|
+
id: api.store.idFor(s.noteId),
|
|
376
|
+
expires_at: s.poll.expiresAt || null,
|
|
377
|
+
expired: !!s.poll.closed || (!!s.poll.expiresAt && Date.parse(s.poll.expiresAt) < Date.now()),
|
|
378
|
+
multiple: !!s.poll.multiple,
|
|
379
|
+
votes_count: votes, voters_count: s.poll.votersCount ?? null,
|
|
380
|
+
options: opts.map(o => ({ title: o.title, votes_count: o.votes || 0 })),
|
|
381
|
+
voted: !!s.poll.voted, own_votes: s.poll.ownVotes || [],
|
|
382
|
+
emojis: [],
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function mediaJson(api, a) {
|
|
387
|
+
const kind = /^video\//.test(a.mediaType) ? 'video'
|
|
388
|
+
: /^audio\//.test(a.mediaType) ? 'audio'
|
|
389
|
+
// NOT 'gifv'. Mastodon uses gifv for a silent MP4 it transcoded a GIF
|
|
390
|
+
// into, and a client renders one with <video src>; handed the raw .gif
|
|
391
|
+
// URL, every GIF came out blank. Nothing here transcodes, so what we
|
|
392
|
+
// have is an image and saying so is what makes it show.
|
|
393
|
+
: 'image';
|
|
394
|
+
return {
|
|
395
|
+
id: a.id || api.store.idFor(a.url),
|
|
396
|
+
type: kind, url: a.url, preview_url: a.url, remote_url: null,
|
|
397
|
+
description: a.description || null, blurhash: null, meta: {},
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function relationship(api, actorUrl) {
|
|
402
|
+
const c = api.store.getContacts();
|
|
403
|
+
const fol = c.following.find(f => f.actor === actorUrl);
|
|
404
|
+
return {
|
|
405
|
+
id: api.store.idFor(actorUrl),
|
|
406
|
+
following: !!fol?.accepted, requested: !!fol && !fol.accepted,
|
|
407
|
+
followed_by: c.followers.some(f => f.actor === actorUrl),
|
|
408
|
+
showing_reblogs: true, notifying: false, languages: null,
|
|
409
|
+
blocking: api.store.getBlocklist().actors.includes(actorUrl),
|
|
410
|
+
blocked_by: false, domain_blocking: false,
|
|
411
|
+
muting: api.store.getMuted().actors.includes(actorUrl),
|
|
412
|
+
muting_notifications: false, endorsed: false, note: '',
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Ours is stored with a hyphen; Mastodon's API spells it with an underscore,
|
|
417
|
+
// and a client that does not know the type shows "Unknown notification type"
|
|
418
|
+
// — which is what every Mastodon client did with a follow request here. The
|
|
419
|
+
// stored spelling is left alone so existing state keeps reading.
|
|
420
|
+
export function notificationType(api, t) { return t === 'follow-request' ? 'follow_request' : t; }
|
|
421
|
+
|
|
422
|
+
export function notification(api, n) {
|
|
423
|
+
const out = { id: n.id, type: api.notificationType(n.type), created_at: n.at, account: api.account(n.actor) };
|
|
424
|
+
if (n.noteId) {
|
|
425
|
+
const s = api.store.getStatuses().find(x => x.noteId === n.noteId);
|
|
426
|
+
if (s) out.status = api.status(s);
|
|
427
|
+
}
|
|
428
|
+
return out;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Every account this instance knows whose handle or name matches: self,
|
|
432
|
+
// contacts, cached actor docs. Handle-shaped queries resolve via webfinger.
|
|
433
|
+
export async function accountSearch(api, q) {
|
|
434
|
+
const needle = String(q || '').replace(/^@/, '').toLowerCase().trim();
|
|
435
|
+
if (!needle) return [];
|
|
436
|
+
if (/^[^@\s]+@[^@\s.]+\.[^@\s]+$/.test(needle)) {
|
|
437
|
+
try {
|
|
438
|
+
const doc = await social.resolveHandle(api.agent, needle);
|
|
439
|
+
return [api.account(doc.id)];
|
|
440
|
+
} catch (e) { api.log(`account search resolve ${needle}: ${e.message}`); }
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
const cfg = api.store.getConfig();
|
|
444
|
+
const seen = new Set();
|
|
445
|
+
const out = [];
|
|
446
|
+
const add = (actorUrl) => {
|
|
447
|
+
if (actorUrl && !seen.has(actorUrl)) { seen.add(actorUrl); out.push(api.account(actorUrl)); }
|
|
448
|
+
};
|
|
449
|
+
if ((cfg?.handle || '').toLowerCase().includes(needle)
|
|
450
|
+
|| (cfg?.name || '').toLowerCase().includes(needle)) add(api.urls.actor);
|
|
451
|
+
const contacts = api.store.getContacts();
|
|
452
|
+
for (const rec of [...contacts.followers, ...contacts.following]) {
|
|
453
|
+
if ((rec.handle || rec.actor || '').toLowerCase().includes(needle)) add(rec.actor);
|
|
454
|
+
}
|
|
455
|
+
for (const [u, a] of Object.entries(api.store.getActors())) {
|
|
456
|
+
if ((a.preferredUsername + ' ' + a.name + ' ' + u).toLowerCase().includes(needle)) add(u);
|
|
457
|
+
}
|
|
458
|
+
return out.slice(0, 20);
|
|
459
|
+
}
|