fedipod-server 0.21.0 → 0.28.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 +5 -4
- package/dist/claims.d.ts +3 -1
- package/dist/claims.js +20 -7
- package/dist/components/context.jsonld +6 -0
- package/dist/handler.d.ts +29 -1
- package/dist/handler.js +89 -4
- package/dist/handler.jsonld +36 -1
- package/lib/client/c2s.mjs +100 -3
- package/lib/client/masto/accounts.mjs +44 -4
- package/lib/client/masto/body.mjs +90 -3
- package/lib/client/masto/index.mjs +5 -1
- package/lib/client/masto/instance.mjs +11 -3
- package/lib/client/masto/media.mjs +3 -57
- package/lib/client/masto/oauth.mjs +66 -9
- package/lib/client/masto/render.mjs +62 -5
- package/lib/client/masto/statuses.mjs +25 -1
- package/lib/client/masto/timelines.mjs +128 -6
- package/lib/connections/tagfeed.mjs +42 -5
- package/lib/core/as2.mjs +36 -5
- package/lib/core/contexts/index.mjs +14 -0
- package/lib/core/contexts/litepub-0.1.json +60 -0
- package/lib/core/contexts/map.json +3 -1
- package/lib/core/contexts/quotes.json +18 -0
- package/lib/core/graphview.mjs +41 -3
- package/lib/core/intake/activities.mjs +136 -2
- package/lib/core/intake/activity.mjs +79 -1
- package/lib/core/intake/group.mjs +37 -3
- package/lib/core/intake/index.mjs +32 -2
- package/lib/core/intake/notes.mjs +60 -3
- package/lib/core/lease.mjs +12 -2
- package/lib/core/profile-page.mjs +104 -0
- package/lib/core/publisher/collections.mjs +6 -1
- package/lib/core/publisher/index.mjs +35 -17
- package/lib/core/publisher/notes.mjs +104 -3
- package/lib/core/shapes/activitystreams.ttl +28 -0
- package/lib/core/shapes/index.mjs +26 -1
- package/lib/core/wire-quotes.mjs +71 -0
- package/lib/core/wire.mjs +59 -103
- package/lib/device/admin/routes/lifecycle.mjs +9 -0
- package/lib/device/admin/static.mjs +4 -1
- package/lib/device/admin/surface.mjs +26 -2
- package/lib/device/certs.mjs +18 -2
- package/lib/device/cli/commands/setup.mjs +6 -2
- package/lib/device/cli/commands/state.mjs +3 -1
- package/lib/device/gateway-move.mjs +58 -0
- package/lib/device/migrate.mjs +3 -1
- package/lib/device/setup.mjs +67 -7
- package/lib/gateway/front-core.mjs +307 -36
- package/lib/gateway/gateway-core.mjs +15 -6
- package/lib/gateway/notices.mjs +84 -0
- package/lib/gateway/quiet.mjs +184 -0
- package/lib/pod/root.mjs +6 -3
- package/lib/pod/state.mjs +8 -7
- package/lib/pod/transport.mjs +5 -1
- package/lib/session/README.md +123 -0
- package/lib/session/demo.html +94 -0
- package/lib/session/fedi-account.mjs +346 -0
- package/lib/session/fedi-login.mjs +123 -0
- package/lib/session/oidc-session.mjs +233 -0
- package/lib/session/package.json +41 -0
- package/package.json +1 -1
- package/run-agent.mjs +28 -0
- package/vendor/gate.cjs +6 -3
- package/web/admin/bar.css +66 -0
- package/web/admin/bar.js +38 -0
- package/web/admin/client/index.html +31 -3
- package/web/admin/client-pick.js +79 -0
- package/web/admin/gateway.js +22 -0
- package/web/admin/index.html +54 -13
- package/web/admin/notices-bar.js +90 -0
- package/web/admin/oauth-signin.mjs +37 -0
- package/web/admin/record.js +13 -1
- package/web/admin/setup/index.html +31 -3
- package/web/admin/tokens.css +15 -3
- package/web/admin/upkeep.js +4 -5
- package/web/app/README.md +14 -10
- package/web/app/admin-facade.mjs +51 -12
- package/web/app/agent.mjs +157 -19
- package/web/app/authorize_interaction.html +56 -0
- package/web/app/authorize_interaction.js +69 -0
- package/web/app/boot.mjs +233 -95
- package/web/app/dist/boot.js +477 -569
- package/web/app/dist/boot.js.map +4 -4
- package/web/app/dist/sw.js +1861 -532
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/gateway-move.mjs +60 -0
- package/web/app/index.html +40 -49
- package/web/app/keys-browser.mjs +12 -15
- package/web/app/keystore.mjs +19 -36
- package/web/app/oidc-session.mjs +5 -188
- package/web/app/signup.mjs +172 -132
- package/web/app/sw-src.mjs +1 -1
- package/web/app/update.js +66 -0
- package/web/front/admin.html +5 -3
- package/web/front/new-account.html +2 -2
- package/web/front/notices.html +69 -0
- package/web/front/notices.js +107 -0
- package/web/front/run.html +15 -8
- package/web/front/run.js +124 -36
- package/web/icons/forum.svg +11 -0
- package/web/icons/gateway.svg +14 -0
- package/web/icons/local.svg +11 -0
- package/web/app/index.html~ +0 -152
- package/web/app/site/_headers +0 -9
- package/web/app/site/_redirects +0 -22
- package/web/app/site/admin/actors.js +0 -145
- package/web/app/site/admin/bar.css +0 -60
- package/web/app/site/admin/bar.js +0 -61
- package/web/app/site/admin/client/client.js +0 -66
- package/web/app/site/admin/client/index.html +0 -43
- package/web/app/site/admin/common.js +0 -23
- package/web/app/site/admin/connections.js +0 -112
- package/web/app/site/admin/gateway.js +0 -111
- package/web/app/site/admin/group.js +0 -258
- package/web/app/site/admin/index.html +0 -564
- package/web/app/site/admin/record.js +0 -378
- package/web/app/site/admin/setup/index.html +0 -215
- package/web/app/site/admin/setup/setup.js +0 -326
- package/web/app/site/admin/tokens.css +0 -56
- package/web/app/site/admin/upkeep.js +0 -178
- package/web/app/site/admin/window.css +0 -55
- package/web/app/site/admin/window.js +0 -136
- package/web/app/site/app/404.html +0 -40
- package/web/app/site/app/_headers +0 -2
- package/web/app/site/app/apple-touch-icon.png +0 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js +0 -2
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +0 -1
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +0 -183
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
- package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +0 -2
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +0 -1
- 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 +0 -1
- package/web/app/site/app/assets/compose-CFEx7wb4.js +0 -2
- package/web/app/site/app/assets/compose-CFEx7wb4.js.map +0 -1
- package/web/app/site/app/assets/compose-ZJN5xSb4.js +0 -35
- package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +0 -1
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js +0 -52
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +0 -1
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js +0 -2
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +0 -1
- 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 +0 -1
- package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +0 -1
- package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
- package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +0 -1
- package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
- package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
- package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
- package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
- package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
- package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
- package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
- package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
- package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +0 -1
- package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +0 -1
- package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
- package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +0 -1
- package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
- package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +0 -1
- package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
- package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
- package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +0 -1
- package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
- package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +0 -1
- package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
- package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
- package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
- package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
- package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
- package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +0 -1
- package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +0 -1
- package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +0 -1
- package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +0 -1
- package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +0 -1
- package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
- package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +0 -1
- package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
- package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +0 -1
- package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +0 -1
- package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +0 -1
- package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
- package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
- package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +0 -1
- package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +0 -1
- package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
- package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
- package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +0 -1
- package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +0 -1
- package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +0 -1
- package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
- package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +0 -1
- package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +0 -1
- package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +0 -1
- package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
- package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
- package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
- package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +0 -1
- package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +0 -1
- package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +0 -1
- package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +0 -1
- package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +0 -1
- package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
- package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
- package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +0 -1
- package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +0 -1
- package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +0 -1
- package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +0 -1
- package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +0 -1
- package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +0 -1
- package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +0 -1
- package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +0 -1
- package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
- package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +0 -1
- package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +0 -1
- package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +0 -1
- package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
- package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +0 -1
- package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +0 -1
- package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
- package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
- package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
- package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +0 -1
- package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +0 -1
- package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
- package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +0 -1
- package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +0 -1
- package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +0 -1
- package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +0 -1
- package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +0 -1
- package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +0 -1
- package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +0 -1
- package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +0 -1
- package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +0 -1
- package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
- package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +0 -1
- package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
- package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
- package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
- package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +0 -1
- package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
- package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +0 -1
- package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +0 -1
- package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
- package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +0 -1
- package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
- package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +0 -1
- package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
- package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +0 -1
- package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +0 -1
- package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
- package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +0 -1
- package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +0 -1
- package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
- package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +0 -1
- package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +0 -1
- package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +0 -1
- package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
- package/web/app/site/app/assets/instances-BNPptnn-.json +0 -333
- package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +0 -1
- package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
- package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +0 -1
- package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +0 -1
- package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +0 -1
- package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +0 -1
- package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +0 -1
- package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +0 -1
- package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +0 -1
- package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +0 -1
- package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +0 -1
- package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +0 -1
- package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +0 -1
- package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +0 -1
- package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
- package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
- package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +0 -1
- package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
- package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +0 -1
- package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +0 -1
- package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
- package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/tok-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +0 -1
- package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +0 -1
- package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/main-BdqNbG-a.js +0 -26
- package/web/app/site/app/assets/main-BdqNbG-a.js.map +0 -1
- package/web/app/site/app/assets/mock-home-DGhR__He.js +0 -2
- package/web/app/site/app/assets/mock-home-DGhR__He.js.map +0 -1
- 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 +0 -2
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +0 -1
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +0 -20
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +0 -1
- package/web/app/site/app/assets/style-B1iLPTPR.css +0 -2
- package/web/app/site/app/assets/temml-DuyIQMei.js +0 -97
- package/web/app/site/app/assets/temml-DuyIQMei.js.map +0 -1
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +0 -2
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
- 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 +0 -3
- package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +0 -1
- package/web/app/site/app/compose/index.html +0 -20
- package/web/app/site/app/favicon.ico +0 -0
- package/web/app/site/app/index.html +0 -73
- 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 +0 -1
- 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 +0 -2
- package/web/app/site/app/version.json +0 -1
- package/web/app/site/boot.js +0 -34277
- package/web/app/site/index.html +0 -251
- package/web/app/site/sw.js +0 -71331
|
@@ -62,6 +62,49 @@ export async function handle(api, ctx) {
|
|
|
62
62
|
const following = (api.agent.tagfeed?.config().tags || []).includes(name);
|
|
63
63
|
return send(200, api.tagObject(name, following, req));
|
|
64
64
|
}
|
|
65
|
+
// --- trending tags: counted off the notes already mirrored ---
|
|
66
|
+
//
|
|
67
|
+
// A single-actor instance has no firehose to measure, so "trending" here can
|
|
68
|
+
// only mean what the tag feed has actually brought in — which is the honest
|
|
69
|
+
// answer, and the one that makes a client's Explore surface show something
|
|
70
|
+
// instead of an empty list. `/api/v1/trends` is Mastodon's older name for the
|
|
71
|
+
// same thing.
|
|
72
|
+
if (pathname === '/api/v1/trends/tags' || pathname === '/api/v1/trends') {
|
|
73
|
+
const DAY = 86400000;
|
|
74
|
+
const today = Math.floor(Date.now() / DAY) * DAY;
|
|
75
|
+
// tag -> day -> { uses, accounts }. Accounts are counted distinctly, which
|
|
76
|
+
// is what the number means in Mastodon: one person posting nine times is
|
|
77
|
+
// nine uses by one account, and a client shows the second number.
|
|
78
|
+
const seen = new Map();
|
|
79
|
+
for (const st of api.store.getStatuses()) {
|
|
80
|
+
if (st.kind !== 'tag' || !st.tag) continue;
|
|
81
|
+
const at = Date.parse(st.published || '');
|
|
82
|
+
if (!Number.isFinite(at) || at < today - 6 * DAY) continue;
|
|
83
|
+
const day = Math.floor(at / DAY) * DAY;
|
|
84
|
+
if (!seen.has(st.tag)) seen.set(st.tag, new Map());
|
|
85
|
+
const days = seen.get(st.tag);
|
|
86
|
+
if (!days.has(day)) days.set(day, { uses: 0, accounts: new Set() });
|
|
87
|
+
const rec = days.get(day);
|
|
88
|
+
rec.uses++;
|
|
89
|
+
if (st.actor) rec.accounts.add(st.actor);
|
|
90
|
+
}
|
|
91
|
+
const followed = new Set(api.agent.tagfeed?.config().tags || []);
|
|
92
|
+
const ranked = [...seen.entries()].map(([name, days]) => {
|
|
93
|
+
// Seven entries, newest first, gaps filled with zeroes — a client draws
|
|
94
|
+
// the sparkline straight off this and a short array draws a short line.
|
|
95
|
+
const history = [];
|
|
96
|
+
for (let i = 0; i < 7; i++) {
|
|
97
|
+
const rec = days.get(today - i * DAY);
|
|
98
|
+
history.push({ day: String(Math.floor((today - i * DAY) / 1000)),
|
|
99
|
+
uses: String(rec ? rec.uses : 0), accounts: String(rec ? rec.accounts.size : 0) });
|
|
100
|
+
}
|
|
101
|
+
return { total: history.reduce((n, h) => n + Number(h.uses), 0),
|
|
102
|
+
tag: api.tagObject(name, followed.has(name), req, history) };
|
|
103
|
+
}).sort((a, b) => b.total - a.total);
|
|
104
|
+
const limit = Math.min(Math.max(Number(url.searchParams.get('limit')) || 10, 1), 40);
|
|
105
|
+
return send(200, ranked.slice(0, limit).map(x => x.tag));
|
|
106
|
+
}
|
|
107
|
+
|
|
65
108
|
const mTagTl = pathname.match(/^\/api\/v1\/timelines\/tag\/([^/]+)$/);
|
|
66
109
|
if (mTagTl && req.method === 'GET') {
|
|
67
110
|
const name = decodeURIComponent(mTagTl[1]).replace(/^#/, '').toLowerCase();
|
|
@@ -208,7 +251,83 @@ export async function handle(api, ctx) {
|
|
|
208
251
|
})));
|
|
209
252
|
}
|
|
210
253
|
|
|
254
|
+
// The direct timeline: every direct post, newest first, paged like the rest.
|
|
255
|
+
if (pathname === '/api/v1/timelines/direct' && req.method === 'GET') {
|
|
256
|
+
const all = api.store.getStatuses();
|
|
257
|
+
const items = all.filter(s => s.direct || s.visibility === 'direct');
|
|
258
|
+
const { items: page, headers } = api.page(items, url);
|
|
259
|
+
return send(200, page.map(s => api.status(s, { all })), headers);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// One notification by id — what a push notification tap opens — and the
|
|
263
|
+
// two ways of being rid of them.
|
|
264
|
+
if (pathname === '/api/v1/notifications/clear' && req.method === 'POST') {
|
|
265
|
+
api.store.removeNotifications(() => true);
|
|
266
|
+
return send(200, {});
|
|
267
|
+
}
|
|
268
|
+
const mNotif = /^\/api\/v1\/notifications\/([A-Za-z0-9_-]+)(\/dismiss)?$/.exec(pathname);
|
|
269
|
+
if (mNotif) {
|
|
270
|
+
const n = api.store.getNotifications().find(x => x.id === mNotif[1]);
|
|
271
|
+
if (!n) return send(404, { error: 'Record not found' });
|
|
272
|
+
if (mNotif[2] && req.method === 'POST') { api.store.removeNotifications(x => x.id === n.id); return send(200, {}); }
|
|
273
|
+
if (!mNotif[2] && req.method === 'GET') return send(200, api.notification(n));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Grouped notifications, the shape a client reads from API version 2 on.
|
|
277
|
+
// One group per notification: the client does its own gathering of
|
|
278
|
+
// favourites and boosts on a post, and every field it joins on — the
|
|
279
|
+
// account and status ids — is here.
|
|
280
|
+
const mGroup = /^\/api\/v2\/notifications\/([A-Za-z0-9_-]+)(\/accounts|\/dismiss)?$/.exec(pathname);
|
|
281
|
+
if (mGroup) {
|
|
282
|
+
const n = api.store.getNotifications().find(x => x.id === mGroup[1]);
|
|
283
|
+
if (!n) return send(404, { error: 'Record not found' });
|
|
284
|
+
if (mGroup[2] === '/dismiss' && req.method === 'POST') { api.store.removeNotifications(x => x.id === n.id); return send(200, {}); }
|
|
285
|
+
if (mGroup[2] === '/accounts' && req.method === 'GET') return send(200, [api.account(n.actor)]);
|
|
286
|
+
if (!mGroup[2] && req.method === 'GET') return send(200, groupedNotifications(api, [n]));
|
|
287
|
+
}
|
|
288
|
+
if (pathname === '/api/v2/notifications') {
|
|
289
|
+
const { page, headers } = notificationPage(api, ctx);
|
|
290
|
+
return send(200, groupedNotifications(api, page), headers);
|
|
291
|
+
}
|
|
292
|
+
|
|
211
293
|
if (pathname === '/api/v1/notifications') {
|
|
294
|
+
const { page, headers } = notificationPage(api, ctx);
|
|
295
|
+
return send(200, page.map((n) => api.notification(n)), headers);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Search: @user@host → webfinger resolve; URL → actor or note ingest;
|
|
299
|
+
// plain text → local mirror + actor-cache scan.
|
|
300
|
+
if (pathname === '/api/v2/search' || pathname === '/api/v1/search') {
|
|
301
|
+
return searchHandler(api, ctx);
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function groupedNotifications(api, page) {
|
|
307
|
+
const accounts = new Map();
|
|
308
|
+
const statuses = new Map();
|
|
309
|
+
const groups = page.map((n) => {
|
|
310
|
+
const acct = api.account(n.actor);
|
|
311
|
+
accounts.set(acct.id, acct);
|
|
312
|
+
const s = n.noteId && api.store.getStatuses().find(x => x.noteId === n.noteId);
|
|
313
|
+
let statusId = null;
|
|
314
|
+
if (s) { const j = api.status(s); statuses.set(j.id, j); statusId = j.id; }
|
|
315
|
+
return {
|
|
316
|
+
group_key: n.id, notifications_count: 1, type: api.notificationType(n.type),
|
|
317
|
+
most_recent_notification_id: n.id, page_min_id: n.id, page_max_id: n.id,
|
|
318
|
+
latest_page_notification_at: n.at, sample_account_ids: [acct.id],
|
|
319
|
+
...(statusId ? { status_id: statusId } : {}),
|
|
320
|
+
...(n.type === 'reaction' ? { emoji: n.emoji, ...(n.url ? { emoji_url: n.url } : {}) } : {}),
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
return { accounts: [...accounts.values()], statuses: [...statuses.values()], notification_groups: groups };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// The slice of notifications a request asks for, and the Link header that
|
|
327
|
+
// pages it. Shared by the flat list and the grouped one.
|
|
328
|
+
function notificationPage(api, ctx) {
|
|
329
|
+
const { req, pathname, url } = ctx;
|
|
330
|
+
{
|
|
212
331
|
const limit = Math.min(Number(url.searchParams.get('limit')) || 30, 60);
|
|
213
332
|
const q = url.searchParams;
|
|
214
333
|
// Newest first, which is what every id rule below assumes.
|
|
@@ -261,17 +380,20 @@ export async function handle(api, ctx) {
|
|
|
261
380
|
for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);
|
|
262
381
|
return u.href;
|
|
263
382
|
};
|
|
264
|
-
return
|
|
383
|
+
return { page, headers: {
|
|
265
384
|
link: `<${link({ max_id: page[page.length - 1].id })}>; rel="next", `
|
|
266
385
|
+ `<${link({ min_id: page[0].id })}>; rel="prev"`,
|
|
267
|
-
}
|
|
386
|
+
} };
|
|
268
387
|
}
|
|
269
|
-
return
|
|
388
|
+
return { page: [], headers: undefined };
|
|
270
389
|
}
|
|
390
|
+
}
|
|
271
391
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
392
|
+
// Search: @user@host → webfinger resolve; URL → actor or note ingest;
|
|
393
|
+
// plain text → local mirror + actor-cache scan.
|
|
394
|
+
async function searchHandler(api, ctx) {
|
|
395
|
+
const { req, res, pathname, url, send } = ctx; // eslint-disable-line no-unused-vars
|
|
396
|
+
{
|
|
275
397
|
const q = String(url.searchParams.get('q') || '').trim();
|
|
276
398
|
const type = url.searchParams.get('type');
|
|
277
399
|
const out = { accounts: [], statuses: [], hashtags: [] };
|
|
@@ -12,13 +12,36 @@ import { isContentType, authorOf } from '../core/intake/index.mjs';
|
|
|
12
12
|
// and a bound where there was none.
|
|
13
13
|
const MAX_TIMELINE_BYTES = 2 * 1024 * 1024;
|
|
14
14
|
|
|
15
|
+
// Two tags from each of six subjects, so a new account reads something on the
|
|
16
|
+
// first morning without having followed anybody. Deliberately even: a busy
|
|
17
|
+
// subject and a quiet one get the same number of places, and the sweep below
|
|
18
|
+
// makes sure the busy ones cannot take the quiet ones' turns as well.
|
|
15
19
|
const DEFAULTS = {
|
|
16
20
|
instance: 'https://mastodon.social',
|
|
17
|
-
tags: [
|
|
21
|
+
tags: [
|
|
22
|
+
'fediverse', 'activitypub', // the network this account is on
|
|
23
|
+
'opensource', 'foss', // how the software gets made
|
|
24
|
+
'solidproject', 'linkeddata', // where the data lives
|
|
25
|
+
'privacy', 'digitalrights', // what is at stake
|
|
26
|
+
'indieweb', 'smallweb', // publishing for yourself
|
|
27
|
+
'commons', 'platformcooperativism', // owning it together
|
|
28
|
+
],
|
|
18
29
|
intervalMin: 15,
|
|
19
30
|
};
|
|
20
31
|
const PER_TAG = 20; // statuses requested per tag per sweep
|
|
21
|
-
const MAX_NEW_PER_SWEEP =
|
|
32
|
+
const MAX_NEW_PER_SWEEP = 8; // dereference budget per sweep — stay light
|
|
33
|
+
// …and no one tag may take more than this much of it. Without a per-tag share
|
|
34
|
+
// the budget was spent in list order: the first two or three busy tags used it
|
|
35
|
+
// all and everything below them never appeared once, however long you waited.
|
|
36
|
+
const MAX_NEW_PER_TAG = 2;
|
|
37
|
+
// Only this many tags are polled in any one sweep, taken in turn. A sweep holds
|
|
38
|
+
// its writes until it finishes — so on the browser build, where the agent lives
|
|
39
|
+
// in a service worker the browser is free to kill whenever it likes, a sweep
|
|
40
|
+
// that runs long enough to be killed contributes NOTHING. Twelve tags was long
|
|
41
|
+
// enough: a new account could open the app repeatedly and still see an empty
|
|
42
|
+
// timeline. Four finishes. The list is still covered, three sweeps at a time,
|
|
43
|
+
// and a sweep now costs the gateway less than the three-tag one it replaced.
|
|
44
|
+
const TAGS_PER_SWEEP = 4;
|
|
22
45
|
const MAX_TAG_ENTRIES = 200; // oldest tag entries pruned beyond this
|
|
23
46
|
// The instance here is a stranger's server we poll on our own schedule, and it
|
|
24
47
|
// has no other way to tell us to stop.
|
|
@@ -31,6 +54,9 @@ export class TagFeed {
|
|
|
31
54
|
Object.assign(this, { store, intake, log, fetcher });
|
|
32
55
|
this.lastSweep = null;
|
|
33
56
|
this.lastAdded = 0;
|
|
57
|
+
// Where this sweep starts in the tag list. It moves on by one each time, so
|
|
58
|
+
// when the budget does run out it is a different tag that goes without.
|
|
59
|
+
this.cursor = 0;
|
|
34
60
|
}
|
|
35
61
|
|
|
36
62
|
config() { return { ...DEFAULTS, ...this.store.read('tagfeed.json', {}) }; }
|
|
@@ -91,6 +117,11 @@ export class TagFeed {
|
|
|
91
117
|
const known = new Set(this.store.getStatuses().map(s => s.noteId));
|
|
92
118
|
let budget = MAX_NEW_PER_SWEEP;
|
|
93
119
|
let added = 0;
|
|
120
|
+
// A different handful every time, taken in turn, so no tag is permanently
|
|
121
|
+
// first and none is permanently last.
|
|
122
|
+
const start = this.cursor % tags.length;
|
|
123
|
+
this.cursor = (start + TAGS_PER_SWEEP) % tags.length;
|
|
124
|
+
const order = [...tags, ...tags].slice(start, start + Math.min(TAGS_PER_SWEEP, tags.length));
|
|
94
125
|
// The store's 300ms debounce cannot coalesce a sweep that awaits between
|
|
95
126
|
// items — every note costs a fetch to somebody else's server, which outlives
|
|
96
127
|
// the timer — so statuses.json and actors.json, the two largest documents,
|
|
@@ -99,7 +130,8 @@ export class TagFeed {
|
|
|
99
130
|
// acctfeed has done it this way since 2026-08-03; this is the same fix.
|
|
100
131
|
this.store.hold?.();
|
|
101
132
|
try {
|
|
102
|
-
for (const tag of
|
|
133
|
+
for (const tag of order) {
|
|
134
|
+
let perTag = MAX_NEW_PER_TAG;
|
|
103
135
|
let list;
|
|
104
136
|
try {
|
|
105
137
|
const { safeFetch, retryAfterMs, readCapped } = await import('../shared/safefetch.mjs');
|
|
@@ -129,7 +161,8 @@ export class TagFeed {
|
|
|
129
161
|
for (const st of Array.isArray(list) ? list : []) {
|
|
130
162
|
const noteId = st?.uri;
|
|
131
163
|
if (!noteId || known.has(noteId) || this.store.isBlocked(noteId)) continue;
|
|
132
|
-
if (budget
|
|
164
|
+
if (perTag <= 0 || budget <= 0) break;
|
|
165
|
+
perTag--; budget--;
|
|
133
166
|
const note = await this.intake.fetchAP(noteId).catch(() => null);
|
|
134
167
|
if (!note || note.id !== noteId || !isContentType(note.type)) continue;
|
|
135
168
|
// The author is only known once the note is dereferenced, and this
|
|
@@ -159,6 +192,10 @@ export class TagFeed {
|
|
|
159
192
|
known.add(noteId);
|
|
160
193
|
added++;
|
|
161
194
|
}
|
|
195
|
+
// Nothing left to take, so stop asking. Every remaining tag would be a
|
|
196
|
+
// request whose answer is thrown away — and in the browser build every
|
|
197
|
+
// one of those goes out through the gateway.
|
|
198
|
+
if (budget <= 0) break;
|
|
162
199
|
}
|
|
163
200
|
const all = this.store.getStatuses();
|
|
164
201
|
const tagged = all.filter(s => s.kind === 'tag');
|
|
@@ -170,6 +207,6 @@ export class TagFeed {
|
|
|
170
207
|
this.store.release?.();
|
|
171
208
|
}
|
|
172
209
|
this.lastAdded = added;
|
|
173
|
-
if (added) this.log(`tagfeed: +${added} from #${
|
|
210
|
+
if (added) this.log(`tagfeed: +${added} from #${order.join(' #')}`);
|
|
174
211
|
}
|
|
175
212
|
}
|
package/lib/core/as2.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// common shape: one that skipped it would not be read as JSON-LD, and would
|
|
20
20
|
// produce no graph, so nothing could be validated against it.
|
|
21
21
|
//
|
|
22
|
-
// CONTEXTS ARE NEVER FETCHED. The loader serves the
|
|
22
|
+
// CONTEXTS ARE NEVER FETCHED. The loader serves the ones the fediverse
|
|
23
23
|
// actually uses and refuses every other URL. That refusal is the point: this
|
|
24
24
|
// code runs on documents a stranger wrote, in the same path the SSRF guards
|
|
25
25
|
// protect, and expanding one would otherwise mean dereferencing a URL of their
|
|
@@ -50,8 +50,14 @@ export class AS2Error extends Error {
|
|
|
50
50
|
* the map throws rather than being fetched, so a document cannot make this
|
|
51
51
|
* process reach out to an address its sender picked.
|
|
52
52
|
*/
|
|
53
|
+
// Pleroma and Akkoma name their own copy of the litepub schema, one URL per
|
|
54
|
+
// instance. Every copy is the same document, held here under the litepub
|
|
55
|
+
// namespace, so a per-instance URL names that entry.
|
|
56
|
+
const LITEPUB = 'http://litepub.social/ns';
|
|
57
|
+
export const heldKey = (url) => (typeof url === 'string' && /\/schemas\/litepub-0\.1\.jsonld$/u.test(url) ? LITEPUB : url);
|
|
58
|
+
|
|
53
59
|
export function contextLoader(url) {
|
|
54
|
-
const doc = CONTEXTS[url];
|
|
60
|
+
const doc = CONTEXTS[heldKey(url)];
|
|
55
61
|
if (!doc) {
|
|
56
62
|
return Promise.reject(new AS2Error(`context not held here: ${url}`, { context: url }));
|
|
57
63
|
}
|
|
@@ -120,7 +126,7 @@ export async function parseAS2(raw) {
|
|
|
120
126
|
*/
|
|
121
127
|
export function groundContext(input) {
|
|
122
128
|
const ctx = input['@context'];
|
|
123
|
-
const held = (c) => typeof c === 'string' ? Object.hasOwn(CONTEXTS, c) : (c && typeof c === 'object');
|
|
129
|
+
const held = (c) => typeof c === 'string' ? Object.hasOwn(CONTEXTS, heldKey(c)) : (c && typeof c === 'object');
|
|
124
130
|
let kept;
|
|
125
131
|
if (ctx === undefined || ctx === null) kept = [];
|
|
126
132
|
else if (Array.isArray(ctx)) kept = ctx.filter(held);
|
|
@@ -143,6 +149,31 @@ export function groundContext(input) {
|
|
|
143
149
|
* `doc` and `graph` are null only when the bytes are not JSON at all, which is
|
|
144
150
|
* what the plain read would have concluded too.
|
|
145
151
|
*/
|
|
152
|
+
/**
|
|
153
|
+
* What a `null` in the document meant, carried onto the reading of it.
|
|
154
|
+
*
|
|
155
|
+
* ActivityPub §6.3.1: in an Update, a key set to `null` says REMOVE that
|
|
156
|
+
* property. JSON-LD has no way to carry that — expansion drops a null outright
|
|
157
|
+
* — so a partial update that takes something away arrives through the graph
|
|
158
|
+
* saying nothing at all, and is indistinguishable from an Update that never
|
|
159
|
+
* mentioned the property. The document plainly said it, so it is put back.
|
|
160
|
+
*
|
|
161
|
+
* Only nulls, only on the activity and its object, and only where the reading
|
|
162
|
+
* does not already have the key: nothing here overrides what the graph carried.
|
|
163
|
+
*/
|
|
164
|
+
function carryRemovals(input, view) {
|
|
165
|
+
if (!input || typeof input !== 'object' || !view || typeof view !== 'object') return view;
|
|
166
|
+
for (const [key, value] of Object.entries(input)) {
|
|
167
|
+
if (value === null) {
|
|
168
|
+
if (!(key in view)) view[key] = null;
|
|
169
|
+
} else if (key === 'object' && typeof value === 'object' && !Array.isArray(value)
|
|
170
|
+
&& view.object && typeof view.object === 'object' && !Array.isArray(view.object)) {
|
|
171
|
+
carryRemovals(value, view.object);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return view;
|
|
175
|
+
}
|
|
176
|
+
|
|
146
177
|
export async function readLenient(raw) {
|
|
147
178
|
// The document's own node is the one its `id` names. Handed to the view
|
|
148
179
|
// as the root, so a document that points at itself — every actor does,
|
|
@@ -153,7 +184,7 @@ export async function readLenient(raw) {
|
|
|
153
184
|
const root = typeof own === 'string' && /^https?:\/\//u.test(own) ? own : null;
|
|
154
185
|
try {
|
|
155
186
|
const { doc, graph } = await parseAS2(raw);
|
|
156
|
-
return { doc, graph, view: graphView(graph, { root }), degraded: null };
|
|
187
|
+
return { doc, graph, view: carryRemovals(input, graphView(graph, { root })), degraded: null };
|
|
157
188
|
} catch (e) {
|
|
158
189
|
if (!input || typeof input !== 'object') return { doc: null, graph: null, view: null, degraded: e.message };
|
|
159
190
|
// Grounded, so a graph exists for every document that is JSON at all.
|
|
@@ -164,7 +195,7 @@ export async function readLenient(raw) {
|
|
|
164
195
|
// what they read, so rewriting it here silently changed what they posted.
|
|
165
196
|
try {
|
|
166
197
|
const { graph } = await parseAS2(groundContext(input));
|
|
167
|
-
return { doc: input, graph, view: graphView(graph, { root }), degraded: e.message };
|
|
198
|
+
return { doc: input, graph, view: carryRemovals(input, graphView(graph, { root })), degraded: e.message };
|
|
168
199
|
} catch (inner) {
|
|
169
200
|
// Grounding names only held contexts, so reaching here means the document
|
|
170
201
|
// is malformed JSON-LD rather than unfetchable. Hand back what a plain
|
|
@@ -28,6 +28,18 @@ import ctx13 from './miscellany.json' with { type: 'json' };
|
|
|
28
28
|
// Not from fedify: the W3C Web Annotation context, which a client such as
|
|
29
29
|
// dokieli names on the annotations it posts to the outbox.
|
|
30
30
|
import ctx14 from './anno.json' with { type: 'json' };
|
|
31
|
+
// Not from fedify either. Pleroma and Akkoma name a copy of the litepub schema
|
|
32
|
+
// served by each instance itself, at /schemas/litepub-0.1.jsonld; every copy
|
|
33
|
+
// is the same document, and this is one of them. It is what declares
|
|
34
|
+
// EmojiReact, and the older quote spellings quoteUrl and quoteUri. Held under
|
|
35
|
+
// the litepub namespace, since no one URL names it; groundContext maps a
|
|
36
|
+
// per-instance URL onto this entry.
|
|
37
|
+
import ctx15 from './litepub-0.1.json' with { type: 'json' };
|
|
38
|
+
// The quote terms as Mastodon declares them inline on a post: FEP-044f's
|
|
39
|
+
// quote, quoteAuthorization, QuoteRequest and QuoteAuthorization, and
|
|
40
|
+
// Misskey's _misskey_quote. No document at the FEP's URL serves a context;
|
|
41
|
+
// the key is what lets the terms be named when reading.
|
|
42
|
+
import ctx16 from './quotes.json' with { type: 'json' };
|
|
31
43
|
|
|
32
44
|
/** URL → the context document itself. Nothing outside this map is ever resolved. */
|
|
33
45
|
export const CONTEXTS = {
|
|
@@ -46,4 +58,6 @@ export const CONTEXTS = {
|
|
|
46
58
|
'http://joinmastodon.org/ns': ctx12,
|
|
47
59
|
'https://purl.archive.org/miscellany': ctx13,
|
|
48
60
|
'http://www.w3.org/ns/anno.jsonld': ctx14,
|
|
61
|
+
'http://litepub.social/ns': ctx15,
|
|
62
|
+
'https://w3id.org/fep/044f': ctx16,
|
|
49
63
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": [
|
|
3
|
+
"https://www.w3.org/ns/activitystreams",
|
|
4
|
+
"https://w3id.org/security/v1",
|
|
5
|
+
{
|
|
6
|
+
"Emoji": "toot:Emoji",
|
|
7
|
+
"Hashtag": "as:Hashtag",
|
|
8
|
+
"PropertyValue": "schema:PropertyValue",
|
|
9
|
+
"atomUri": "ostatus:atomUri",
|
|
10
|
+
"conversation": {
|
|
11
|
+
"@id": "ostatus:conversation",
|
|
12
|
+
"@type": "@id"
|
|
13
|
+
},
|
|
14
|
+
"discoverable": "toot:discoverable",
|
|
15
|
+
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
|
16
|
+
"capabilities": "litepub:capabilities",
|
|
17
|
+
"ostatus": "http://ostatus.org#",
|
|
18
|
+
"schema": "http://schema.org#",
|
|
19
|
+
"toot": "http://joinmastodon.org/ns#",
|
|
20
|
+
"misskey": "https://misskey-hub.net/ns#",
|
|
21
|
+
"fedibird": "http://fedibird.com/ns#",
|
|
22
|
+
"sharkey": "https://joinsharkey.org/ns#",
|
|
23
|
+
"value": "schema:value",
|
|
24
|
+
"sensitive": "as:sensitive",
|
|
25
|
+
"litepub": "http://litepub.social/ns#",
|
|
26
|
+
"invisible": "litepub:invisible",
|
|
27
|
+
"directMessage": "litepub:directMessage",
|
|
28
|
+
"listMessage": {
|
|
29
|
+
"@id": "litepub:listMessage",
|
|
30
|
+
"@type": "@id"
|
|
31
|
+
},
|
|
32
|
+
"quoteUrl": "as:quoteUrl",
|
|
33
|
+
"quoteUri": "fedibird:quoteUri",
|
|
34
|
+
"oauthRegistrationEndpoint": {
|
|
35
|
+
"@id": "litepub:oauthRegistrationEndpoint",
|
|
36
|
+
"@type": "@id"
|
|
37
|
+
},
|
|
38
|
+
"EmojiReact": "litepub:EmojiReact",
|
|
39
|
+
"ChatMessage": "litepub:ChatMessage",
|
|
40
|
+
"alsoKnownAs": {
|
|
41
|
+
"@id": "as:alsoKnownAs",
|
|
42
|
+
"@type": "@id"
|
|
43
|
+
},
|
|
44
|
+
"vcard": "http://www.w3.org/2006/vcard/ns#",
|
|
45
|
+
"formerRepresentations": "litepub:formerRepresentations",
|
|
46
|
+
"contentMap": {
|
|
47
|
+
"@id": "as:content",
|
|
48
|
+
"@container": "@language"
|
|
49
|
+
},
|
|
50
|
+
"featured": {
|
|
51
|
+
"@id": "toot:featured",
|
|
52
|
+
"@type": "@id"
|
|
53
|
+
},
|
|
54
|
+
"backgroundUrl": {
|
|
55
|
+
"@id": "sharkey:backgroundUrl",
|
|
56
|
+
"@type": "@id"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
@@ -13,5 +13,7 @@
|
|
|
13
13
|
"https://join-lemmy.org/context.json": "join-lemmy.json",
|
|
14
14
|
"http://joinmastodon.org/ns": "joinmastodon.json",
|
|
15
15
|
"https://purl.archive.org/miscellany": "miscellany.json",
|
|
16
|
-
"http://www.w3.org/ns/anno.jsonld": "anno.json"
|
|
16
|
+
"http://www.w3.org/ns/anno.jsonld": "anno.json",
|
|
17
|
+
"http://litepub.social/ns": "litepub-0.1.json",
|
|
18
|
+
"https://w3id.org/fep/044f": "quotes.json"
|
|
17
19
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"quote": {
|
|
4
|
+
"@id": "https://w3id.org/fep/044f#quote",
|
|
5
|
+
"@type": "@id"
|
|
6
|
+
},
|
|
7
|
+
"quoteAuthorization": {
|
|
8
|
+
"@id": "https://w3id.org/fep/044f#quoteAuthorization",
|
|
9
|
+
"@type": "@id"
|
|
10
|
+
},
|
|
11
|
+
"QuoteRequest": "https://w3id.org/fep/044f#QuoteRequest",
|
|
12
|
+
"QuoteAuthorization": "https://w3id.org/fep/044f#QuoteAuthorization",
|
|
13
|
+
"_misskey_quote": {
|
|
14
|
+
"@id": "https://misskey-hub.net/ns#_misskey_quote",
|
|
15
|
+
"@type": "@id"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/lib/core/graphview.mjs
CHANGED
|
@@ -55,8 +55,13 @@ function buildTerms() {
|
|
|
55
55
|
const terms = new Map();
|
|
56
56
|
const order = [AS_CTX, ...Object.keys(CONTEXTS).filter(u => u !== AS_CTX)];
|
|
57
57
|
for (const url of order) {
|
|
58
|
-
const
|
|
59
|
-
|
|
58
|
+
const body = CONTEXTS[url]?.['@context'];
|
|
59
|
+
// A context document's body is one term map, or a list mixing URLs of
|
|
60
|
+
// other contexts with term maps — the litepub schema is the second shape.
|
|
61
|
+
// The URLs are held on their own; only the maps carry terms.
|
|
62
|
+
const maps = (Array.isArray(body) ? body : [body]).filter(c => c && typeof c === 'object');
|
|
63
|
+
if (!maps.length) continue;
|
|
64
|
+
const ctx = Object.assign({}, ...maps);
|
|
60
65
|
const prefixes = {};
|
|
61
66
|
for (const [k, v] of Object.entries(ctx)) {
|
|
62
67
|
if (typeof v === 'string' && /^https?:\/\//.test(v)) prefixes[k] = v;
|
|
@@ -189,8 +194,41 @@ function isListHead(term, ctx) {
|
|
|
189
194
|
* IRI, which is what a bare reference was. Handlers already test which they
|
|
190
195
|
* got (`typeof activity.object === 'object'`), so both stay readable.
|
|
191
196
|
*/
|
|
197
|
+
// A literal's `.value` is its LEXICAL form, which is a string whatever the
|
|
198
|
+
// literal meant: `false` written in JSON-LD arrives here as the four characters
|
|
199
|
+
// "false", and `42` as two digits. Every handler downstream then reads a string
|
|
200
|
+
// where the document said a boolean or a number — and `!!"false"` is true, which
|
|
201
|
+
// is how a moderator's reopen was applied as another close.
|
|
202
|
+
//
|
|
203
|
+
// So the two datatypes whose lexical form lies about the value are turned back.
|
|
204
|
+
// Everything else is left exactly as it was, dates above all: a timestamp is
|
|
205
|
+
// carried, compared and republished as its string everywhere in this project,
|
|
206
|
+
// and "helpfully" turning it into a Date would break all of it.
|
|
207
|
+
const XSD = 'http://www.w3.org/2001/XMLSchema#';
|
|
208
|
+
const NUMERIC = new Set(['integer', 'decimal', 'double', 'float', 'long', 'int', 'short', 'byte',
|
|
209
|
+
'nonNegativeInteger', 'positiveInteger', 'nonPositiveInteger', 'negativeInteger',
|
|
210
|
+
'unsignedLong', 'unsignedInt', 'unsignedShort', 'unsignedByte'].map(t => XSD + t));
|
|
211
|
+
|
|
212
|
+
function fromLiteral(term) {
|
|
213
|
+
const type = term.datatype?.value || '';
|
|
214
|
+
if (type === `${XSD}boolean`) {
|
|
215
|
+
// The lexical space of xsd:boolean is exactly these four. Anything else is
|
|
216
|
+
// not a boolean the document can have meant, so it stays as it was written.
|
|
217
|
+
if (term.value === 'true' || term.value === '1') return true;
|
|
218
|
+
if (term.value === 'false' || term.value === '0') return false;
|
|
219
|
+
return term.value;
|
|
220
|
+
}
|
|
221
|
+
if (NUMERIC.has(type)) {
|
|
222
|
+
const n = Number(term.value);
|
|
223
|
+
// A numeric literal that will not parse is malformed; handing back NaN
|
|
224
|
+
// would lose what the document actually said.
|
|
225
|
+
return Number.isFinite(n) ? n : term.value;
|
|
226
|
+
}
|
|
227
|
+
return term.value;
|
|
228
|
+
}
|
|
229
|
+
|
|
192
230
|
function toValue(term, ctx, depth, path) {
|
|
193
|
-
if (term.termType === 'Literal') return term
|
|
231
|
+
if (term.termType === 'Literal') return fromLiteral(term);
|
|
194
232
|
if (isListHead(term, ctx)) return readList(term.value, ctx, depth, path);
|
|
195
233
|
const preds = ctx.bySubject.get(term.value);
|
|
196
234
|
if (preds && preds.size) return makeView(term.value, ctx, depth + 1, path);
|