fedipod-server 0.28.1 → 0.30.0

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