fedipod-server 0.29.0 → 0.31.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 (444) hide show
  1. package/README.md +16 -6
  2. package/dist/claims.d.ts +2 -0
  3. package/dist/claims.js +13 -4
  4. package/dist/door.d.ts +4 -0
  5. package/dist/door.js +31 -0
  6. package/dist/fedipod.d.ts +15 -0
  7. package/dist/fedipod.js +13 -0
  8. package/dist/front.d.ts +3 -0
  9. package/dist/front.js +92 -0
  10. package/dist/handler.d.ts +36 -101
  11. package/dist/handler.js +93 -644
  12. package/dist/handler.jsonld +2 -30
  13. package/dist/identities.d.ts +71 -0
  14. package/dist/identities.js +236 -0
  15. package/dist/mounts.d.ts +43 -0
  16. package/dist/mounts.js +123 -0
  17. package/dist/opt-in.d.ts +46 -0
  18. package/dist/opt-in.js +176 -0
  19. package/dist/private-read.d.ts +4 -0
  20. package/dist/private-read.js +34 -0
  21. package/package.json +12 -20
  22. package/lib/client/c2s.mjs +0 -636
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -310
  25. package/lib/client/masto/body.mjs +0 -156
  26. package/lib/client/masto/index.mjs +0 -206
  27. package/lib/client/masto/instance.mjs +0 -112
  28. package/lib/client/masto/media.mjs +0 -79
  29. package/lib/client/masto/oauth.mjs +0 -656
  30. package/lib/client/masto/render.mjs +0 -518
  31. package/lib/client/masto/statuses.mjs +0 -372
  32. package/lib/client/masto/timelines.mjs +0 -438
  33. package/lib/client/oidc-auth.mjs +0 -62
  34. package/lib/client/streaming.mjs +0 -154
  35. package/lib/client/webpush.mjs +0 -98
  36. package/lib/connections/acctfeed.mjs +0 -257
  37. package/lib/connections/atproto.mjs +0 -309
  38. package/lib/connections/bskyfeed.mjs +0 -243
  39. package/lib/connections/bskygroup.mjs +0 -162
  40. package/lib/connections/fediacct.mjs +0 -253
  41. package/lib/connections/import.mjs +0 -357
  42. package/lib/connections/tagfeed.mjs +0 -212
  43. package/lib/connections/vault.mjs +0 -114
  44. package/lib/core/as2.mjs +0 -206
  45. package/lib/core/contexts/activitystreams.json +0 -379
  46. package/lib/core/contexts/anno.json +0 -126
  47. package/lib/core/contexts/did-v1.json +0 -57
  48. package/lib/core/contexts/fep-5711.json +0 -36
  49. package/lib/core/contexts/gotosocial.json +0 -86
  50. package/lib/core/contexts/identity-v1.json +0 -152
  51. package/lib/core/contexts/index.mjs +0 -63
  52. package/lib/core/contexts/join-lemmy.json +0 -33
  53. package/lib/core/contexts/joinmastodon.json +0 -28
  54. package/lib/core/contexts/litepub-0.1.json +0 -60
  55. package/lib/core/contexts/map.json +0 -19
  56. package/lib/core/contexts/miscellany.json +0 -19
  57. package/lib/core/contexts/quotes.json +0 -18
  58. package/lib/core/contexts/schemaorg.json +0 -8845
  59. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  60. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  61. package/lib/core/contexts/security-multikey-v1.json +0 -35
  62. package/lib/core/contexts/security-v1.json +0 -74
  63. package/lib/core/contexts/webfinger.json +0 -10
  64. package/lib/core/deliver.mjs +0 -387
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -649
  67. package/lib/core/intake/activity.mjs +0 -318
  68. package/lib/core/intake/channel.mjs +0 -144
  69. package/lib/core/intake/group.mjs +0 -258
  70. package/lib/core/intake/index.mjs +0 -732
  71. package/lib/core/intake/notes.mjs +0 -367
  72. package/lib/core/intake/verify.mjs +0 -142
  73. package/lib/core/keys.mjs +0 -153
  74. package/lib/core/lease.mjs +0 -206
  75. package/lib/core/place.mjs +0 -82
  76. package/lib/core/polls.mjs +0 -105
  77. package/lib/core/profile-page.mjs +0 -104
  78. package/lib/core/proof.mjs +0 -79
  79. package/lib/core/publisher/collections.mjs +0 -272
  80. package/lib/core/publisher/index.mjs +0 -522
  81. package/lib/core/publisher/notes.mjs +0 -519
  82. package/lib/core/publisher/own.mjs +0 -143
  83. package/lib/core/publisher/questions.mjs +0 -241
  84. package/lib/core/publisher/restore.mjs +0 -224
  85. package/lib/core/shapes/activitystreams.ttl +0 -157
  86. package/lib/core/shapes/index.mjs +0 -132
  87. package/lib/core/shapes/shapes-text.mjs +0 -13
  88. package/lib/core/social.mjs +0 -561
  89. package/lib/core/storage.mjs +0 -176
  90. package/lib/core/store.mjs +0 -578
  91. package/lib/core/wire-quotes.mjs +0 -71
  92. package/lib/core/wire.mjs +0 -925
  93. package/lib/device/account.mjs +0 -87
  94. package/lib/device/admin/index.mjs +0 -13
  95. package/lib/device/admin/origins.mjs +0 -35
  96. package/lib/device/admin/routes/connections.mjs +0 -144
  97. package/lib/device/admin/routes/gateway.mjs +0 -199
  98. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  99. package/lib/device/admin/routes/owner.mjs +0 -322
  100. package/lib/device/admin/routes/setup.mjs +0 -428
  101. package/lib/device/admin/routes/social.mjs +0 -188
  102. package/lib/device/admin/server.mjs +0 -95
  103. package/lib/device/admin/static.mjs +0 -247
  104. package/lib/device/admin/surface.mjs +0 -315
  105. package/lib/device/certs.mjs +0 -281
  106. package/lib/device/cli/commands/account.mjs +0 -586
  107. package/lib/device/cli/commands/run.mjs +0 -278
  108. package/lib/device/cli/commands/service.mjs +0 -221
  109. package/lib/device/cli/commands/setup.mjs +0 -489
  110. package/lib/device/cli/commands/state.mjs +0 -561
  111. package/lib/device/cli/context.mjs +0 -288
  112. package/lib/device/export-collections.mjs +0 -161
  113. package/lib/device/gateway-move.mjs +0 -58
  114. package/lib/device/home.mjs +0 -161
  115. package/lib/device/migrate.mjs +0 -115
  116. package/lib/device/ports.mjs +0 -27
  117. package/lib/device/remote.mjs +0 -54
  118. package/lib/device/setup.mjs +0 -496
  119. package/lib/device/update.mjs +0 -96
  120. package/lib/gateway/caches.mjs +0 -36
  121. package/lib/gateway/directory.mjs +0 -90
  122. package/lib/gateway/front-core.mjs +0 -966
  123. package/lib/gateway/gateway-core.mjs +0 -244
  124. package/lib/gateway/headers.mjs +0 -49
  125. package/lib/gateway/httpsig.mjs +0 -159
  126. package/lib/gateway/notices.mjs +0 -84
  127. package/lib/gateway/quiet.mjs +0 -189
  128. package/lib/gateway/relay-extras.mjs +0 -89
  129. package/lib/gateway/token-claims.mjs +0 -16
  130. package/lib/pod/README.md +0 -61
  131. package/lib/pod/actor.mjs +0 -96
  132. package/lib/pod/collection.mjs +0 -77
  133. package/lib/pod/containers.mjs +0 -140
  134. package/lib/pod/discovery.mjs +0 -46
  135. package/lib/pod/featured.mjs +0 -18
  136. package/lib/pod/followers.mjs +0 -24
  137. package/lib/pod/following.mjs +0 -10
  138. package/lib/pod/http.mjs +0 -74
  139. package/lib/pod/inbox.mjs +0 -166
  140. package/lib/pod/links.mjs +0 -35
  141. package/lib/pod/location.mjs +0 -52
  142. package/lib/pod/media.mjs +0 -27
  143. package/lib/pod/notes.mjs +0 -78
  144. package/lib/pod/notifications.mjs +0 -89
  145. package/lib/pod/outbox.mjs +0 -18
  146. package/lib/pod/package.json +0 -19
  147. package/lib/pod/policy.mjs +0 -58
  148. package/lib/pod/private.mjs +0 -19
  149. package/lib/pod/root.mjs +0 -104
  150. package/lib/pod/state.mjs +0 -40
  151. package/lib/pod/transport.mjs +0 -674
  152. package/lib/pod/type-index.mjs +0 -101
  153. package/lib/pod/urls.mjs +0 -91
  154. package/lib/server/embed.mjs +0 -424
  155. package/lib/session/README.md +0 -137
  156. package/lib/session/demo.html +0 -94
  157. package/lib/session/fedi-account.mjs +0 -417
  158. package/lib/session/fedi-login.mjs +0 -123
  159. package/lib/session/oidc-session.mjs +0 -233
  160. package/lib/session/package.json +0 -49
  161. package/lib/shared/guard.mjs +0 -213
  162. package/lib/shared/links.mjs +0 -9
  163. package/lib/shared/safefetch.mjs +0 -235
  164. package/lib/shared/ua.mjs +0 -29
  165. package/phanpy/dist/404.html +0 -40
  166. package/phanpy/dist/_headers +0 -2
  167. package/phanpy/dist/apple-touch-icon.png +0 -0
  168. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  169. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  170. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  171. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  172. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  173. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  174. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  175. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  176. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  177. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  178. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  179. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  180. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  181. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  182. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  183. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  184. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  185. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  186. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  187. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  188. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  189. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  190. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  191. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  192. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  193. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  197. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  198. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  199. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  200. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  201. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  202. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  203. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  204. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  205. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  206. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  207. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  208. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  209. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  210. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  211. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  212. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  213. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  214. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  215. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  216. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  217. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  218. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  219. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  220. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  221. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  222. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  223. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  224. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  225. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  226. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  227. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  228. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  229. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  230. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  231. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  232. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  233. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  234. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  235. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  236. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  237. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  238. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  239. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  240. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  241. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  242. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  243. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  244. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  245. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  246. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  247. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  248. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  249. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  250. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  251. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  252. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  253. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  254. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  255. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  256. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  257. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  258. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  259. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  260. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  261. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  262. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  263. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  264. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  265. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  266. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  267. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  268. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  269. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  270. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  271. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  272. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  273. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  274. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  275. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  276. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  277. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  278. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  279. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  280. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  281. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  282. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  283. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  284. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  285. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  286. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  287. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  288. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  289. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  290. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  291. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  292. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  293. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  294. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  295. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  296. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  297. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  298. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  299. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  300. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  301. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  302. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  303. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  304. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  305. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  306. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  307. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  308. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  309. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  310. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  311. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  312. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  313. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  314. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  315. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  316. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  317. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  318. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  319. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  321. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  322. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  323. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  324. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  325. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  326. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  327. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  329. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  330. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  331. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  332. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  333. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  334. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  336. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  337. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  338. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  339. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  340. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  341. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  342. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  343. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  344. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  345. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  346. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  347. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  348. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  349. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  350. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  351. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  352. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  353. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  354. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  355. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  356. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  357. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  358. package/phanpy/dist/compose/index.html +0 -20
  359. package/phanpy/dist/favicon.ico +0 -0
  360. package/phanpy/dist/index.html +0 -73
  361. package/phanpy/dist/logo-192.png +0 -0
  362. package/phanpy/dist/logo-512.png +0 -0
  363. package/phanpy/dist/logo-badge-72.png +0 -0
  364. package/phanpy/dist/logo-maskable-512.png +0 -0
  365. package/phanpy/dist/logo-monochrome-512.png +0 -0
  366. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  367. package/phanpy/dist/manifest.webmanifest +0 -1
  368. package/phanpy/dist/og-image-2.jpg +0 -0
  369. package/phanpy/dist/og-image.png +0 -0
  370. package/phanpy/dist/robots.txt +0 -2
  371. package/phanpy/dist/sw.js +0 -2
  372. package/phanpy/dist/sw.js.map +0 -1
  373. package/phanpy/dist/version.json +0 -1
  374. package/run-agent.mjs +0 -899
  375. package/vendor/gate.cjs +0 -138
  376. package/vendor/idp-grant.cjs +0 -394
  377. package/vendor/jfetch.cjs +0 -16
  378. package/web/admin/actors.js +0 -147
  379. package/web/admin/bar.css +0 -126
  380. package/web/admin/bar.js +0 -99
  381. package/web/admin/client/client.js +0 -66
  382. package/web/admin/client/index.html +0 -69
  383. package/web/admin/client-pick.js +0 -79
  384. package/web/admin/common.js +0 -23
  385. package/web/admin/connections.js +0 -112
  386. package/web/admin/gateway.js +0 -133
  387. package/web/admin/group.js +0 -258
  388. package/web/admin/index.html +0 -612
  389. package/web/admin/notices-bar.js +0 -90
  390. package/web/admin/oauth-signin.mjs +0 -37
  391. package/web/admin/record.js +0 -393
  392. package/web/admin/setup/index.html +0 -257
  393. package/web/admin/setup/setup.js +0 -339
  394. package/web/admin/tokens.css +0 -68
  395. package/web/admin/upkeep.js +0 -177
  396. package/web/admin/window.css +0 -55
  397. package/web/admin/window.js +0 -136
  398. package/web/app/README.md +0 -81
  399. package/web/app/admin-facade.mjs +0 -588
  400. package/web/app/agent.mjs +0 -672
  401. package/web/app/atproto-browser.mjs +0 -86
  402. package/web/app/authorize_interaction.html +0 -56
  403. package/web/app/authorize_interaction.js +0 -69
  404. package/web/app/boot.mjs +0 -616
  405. package/web/app/deliver-relay.mjs +0 -129
  406. package/web/app/dist/boot.js +0 -34562
  407. package/web/app/dist/boot.js.map +0 -7
  408. package/web/app/dist/sw.js +0 -73456
  409. package/web/app/dist/sw.js.map +0 -7
  410. package/web/app/fediacct-browser.mjs +0 -146
  411. package/web/app/gateway-move.mjs +0 -60
  412. package/web/app/idb-kv.mjs +0 -35
  413. package/web/app/index.html +0 -258
  414. package/web/app/keys-browser.mjs +0 -71
  415. package/web/app/keystore.mjs +0 -82
  416. package/web/app/oidc-session.mjs +0 -6
  417. package/web/app/pod-auth.mjs +0 -248
  418. package/web/app/pod-remote.mjs +0 -84
  419. package/web/app/shims/_globals.mjs +0 -2
  420. package/web/app/shims/fedify-sig.mjs +0 -77
  421. package/web/app/shims/node-crypto.mjs +0 -113
  422. package/web/app/shims/node-fs.mjs +0 -17
  423. package/web/app/shims/node-path.mjs +0 -19
  424. package/web/app/shims/node-url.mjs +0 -7
  425. package/web/app/shims/prelude.js +0 -58
  426. package/web/app/shims/safefetch.mjs +0 -58
  427. package/web/app/shims/shapes-text.mjs +0 -8
  428. package/web/app/shims/web-push.mjs +0 -14
  429. package/web/app/signup.mjs +0 -357
  430. package/web/app/sw-src.mjs +0 -220
  431. package/web/app/update.js +0 -70
  432. package/web/front/admin.html +0 -55
  433. package/web/front/admin.js +0 -89
  434. package/web/front/install.sh +0 -69
  435. package/web/front/new-account.html +0 -35
  436. package/web/front/new-account.js +0 -13
  437. package/web/front/notices.html +0 -69
  438. package/web/front/notices.js +0 -107
  439. package/web/front/run.html +0 -64
  440. package/web/front/run.js +0 -221
  441. package/web/front/solid-oidc-client.js +0 -6
  442. package/web/icons/forum.svg +0 -11
  443. package/web/icons/gateway.svg +0 -14
  444. 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
- }