fedipod-server 0.29.0 → 0.30.0

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