fedipod-server 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. package/README.md +11 -6
  2. package/dist/door.d.ts +4 -0
  3. package/dist/door.js +31 -0
  4. package/dist/fedipod.d.ts +16 -0
  5. package/dist/fedipod.js +13 -0
  6. package/dist/front.d.ts +3 -0
  7. package/dist/front.js +94 -0
  8. package/dist/handler.d.ts +36 -101
  9. package/dist/handler.js +85 -645
  10. package/dist/handler.jsonld +2 -30
  11. package/dist/identities.d.ts +62 -0
  12. package/dist/identities.js +236 -0
  13. package/dist/mounts.d.ts +43 -0
  14. package/dist/mounts.js +123 -0
  15. package/dist/opt-in.d.ts +46 -0
  16. package/dist/opt-in.js +176 -0
  17. package/package.json +10 -18
  18. package/lib/client/c2s.mjs +0 -636
  19. package/lib/client/localapi.mjs +0 -87
  20. package/lib/client/masto/accounts.mjs +0 -310
  21. package/lib/client/masto/body.mjs +0 -156
  22. package/lib/client/masto/index.mjs +0 -206
  23. package/lib/client/masto/instance.mjs +0 -112
  24. package/lib/client/masto/media.mjs +0 -79
  25. package/lib/client/masto/oauth.mjs +0 -656
  26. package/lib/client/masto/render.mjs +0 -518
  27. package/lib/client/masto/statuses.mjs +0 -372
  28. package/lib/client/masto/timelines.mjs +0 -438
  29. package/lib/client/oidc-auth.mjs +0 -62
  30. package/lib/client/streaming.mjs +0 -154
  31. package/lib/client/webpush.mjs +0 -98
  32. package/lib/connections/acctfeed.mjs +0 -257
  33. package/lib/connections/atproto.mjs +0 -309
  34. package/lib/connections/bskyfeed.mjs +0 -243
  35. package/lib/connections/bskygroup.mjs +0 -162
  36. package/lib/connections/fediacct.mjs +0 -253
  37. package/lib/connections/import.mjs +0 -357
  38. package/lib/connections/tagfeed.mjs +0 -212
  39. package/lib/connections/vault.mjs +0 -114
  40. package/lib/core/as2.mjs +0 -206
  41. package/lib/core/contexts/activitystreams.json +0 -379
  42. package/lib/core/contexts/anno.json +0 -126
  43. package/lib/core/contexts/did-v1.json +0 -57
  44. package/lib/core/contexts/fep-5711.json +0 -36
  45. package/lib/core/contexts/gotosocial.json +0 -86
  46. package/lib/core/contexts/identity-v1.json +0 -152
  47. package/lib/core/contexts/index.mjs +0 -63
  48. package/lib/core/contexts/join-lemmy.json +0 -33
  49. package/lib/core/contexts/joinmastodon.json +0 -28
  50. package/lib/core/contexts/litepub-0.1.json +0 -60
  51. package/lib/core/contexts/map.json +0 -19
  52. package/lib/core/contexts/miscellany.json +0 -19
  53. package/lib/core/contexts/quotes.json +0 -18
  54. package/lib/core/contexts/schemaorg.json +0 -8845
  55. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  56. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  57. package/lib/core/contexts/security-multikey-v1.json +0 -35
  58. package/lib/core/contexts/security-v1.json +0 -74
  59. package/lib/core/contexts/webfinger.json +0 -10
  60. package/lib/core/deliver.mjs +0 -387
  61. package/lib/core/graphview.mjs +0 -327
  62. package/lib/core/intake/activities.mjs +0 -649
  63. package/lib/core/intake/activity.mjs +0 -318
  64. package/lib/core/intake/channel.mjs +0 -144
  65. package/lib/core/intake/group.mjs +0 -258
  66. package/lib/core/intake/index.mjs +0 -732
  67. package/lib/core/intake/notes.mjs +0 -367
  68. package/lib/core/intake/verify.mjs +0 -142
  69. package/lib/core/keys.mjs +0 -153
  70. package/lib/core/lease.mjs +0 -206
  71. package/lib/core/place.mjs +0 -82
  72. package/lib/core/polls.mjs +0 -105
  73. package/lib/core/profile-page.mjs +0 -104
  74. package/lib/core/proof.mjs +0 -79
  75. package/lib/core/publisher/collections.mjs +0 -272
  76. package/lib/core/publisher/index.mjs +0 -522
  77. package/lib/core/publisher/notes.mjs +0 -519
  78. package/lib/core/publisher/own.mjs +0 -143
  79. package/lib/core/publisher/questions.mjs +0 -241
  80. package/lib/core/publisher/restore.mjs +0 -224
  81. package/lib/core/shapes/activitystreams.ttl +0 -157
  82. package/lib/core/shapes/index.mjs +0 -132
  83. package/lib/core/shapes/shapes-text.mjs +0 -13
  84. package/lib/core/social.mjs +0 -561
  85. package/lib/core/storage.mjs +0 -176
  86. package/lib/core/store.mjs +0 -578
  87. package/lib/core/wire-quotes.mjs +0 -71
  88. package/lib/core/wire.mjs +0 -925
  89. package/lib/device/account.mjs +0 -87
  90. package/lib/device/admin/index.mjs +0 -13
  91. package/lib/device/admin/origins.mjs +0 -35
  92. package/lib/device/admin/routes/connections.mjs +0 -144
  93. package/lib/device/admin/routes/gateway.mjs +0 -199
  94. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  95. package/lib/device/admin/routes/owner.mjs +0 -322
  96. package/lib/device/admin/routes/setup.mjs +0 -428
  97. package/lib/device/admin/routes/social.mjs +0 -188
  98. package/lib/device/admin/server.mjs +0 -95
  99. package/lib/device/admin/static.mjs +0 -247
  100. package/lib/device/admin/surface.mjs +0 -315
  101. package/lib/device/certs.mjs +0 -281
  102. package/lib/device/cli/commands/account.mjs +0 -586
  103. package/lib/device/cli/commands/run.mjs +0 -278
  104. package/lib/device/cli/commands/service.mjs +0 -221
  105. package/lib/device/cli/commands/setup.mjs +0 -489
  106. package/lib/device/cli/commands/state.mjs +0 -561
  107. package/lib/device/cli/context.mjs +0 -288
  108. package/lib/device/export-collections.mjs +0 -161
  109. package/lib/device/gateway-move.mjs +0 -58
  110. package/lib/device/home.mjs +0 -161
  111. package/lib/device/migrate.mjs +0 -115
  112. package/lib/device/ports.mjs +0 -27
  113. package/lib/device/remote.mjs +0 -54
  114. package/lib/device/setup.mjs +0 -496
  115. package/lib/device/update.mjs +0 -96
  116. package/lib/gateway/caches.mjs +0 -36
  117. package/lib/gateway/directory.mjs +0 -90
  118. package/lib/gateway/front-core.mjs +0 -966
  119. package/lib/gateway/gateway-core.mjs +0 -244
  120. package/lib/gateway/headers.mjs +0 -49
  121. package/lib/gateway/httpsig.mjs +0 -159
  122. package/lib/gateway/notices.mjs +0 -84
  123. package/lib/gateway/quiet.mjs +0 -189
  124. package/lib/gateway/relay-extras.mjs +0 -89
  125. package/lib/gateway/token-claims.mjs +0 -16
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/location.mjs +0 -52
  138. package/lib/pod/media.mjs +0 -27
  139. package/lib/pod/notes.mjs +0 -78
  140. package/lib/pod/notifications.mjs +0 -89
  141. package/lib/pod/outbox.mjs +0 -18
  142. package/lib/pod/package.json +0 -19
  143. package/lib/pod/policy.mjs +0 -58
  144. package/lib/pod/private.mjs +0 -19
  145. package/lib/pod/root.mjs +0 -104
  146. package/lib/pod/state.mjs +0 -40
  147. package/lib/pod/transport.mjs +0 -674
  148. package/lib/pod/type-index.mjs +0 -101
  149. package/lib/pod/urls.mjs +0 -91
  150. package/lib/server/embed.mjs +0 -424
  151. package/lib/session/README.md +0 -137
  152. package/lib/session/demo.html +0 -94
  153. package/lib/session/fedi-account.mjs +0 -417
  154. package/lib/session/fedi-login.mjs +0 -123
  155. package/lib/session/oidc-session.mjs +0 -233
  156. package/lib/session/package.json +0 -49
  157. package/lib/shared/guard.mjs +0 -213
  158. package/lib/shared/links.mjs +0 -9
  159. package/lib/shared/safefetch.mjs +0 -235
  160. package/lib/shared/ua.mjs +0 -29
  161. package/phanpy/dist/404.html +0 -40
  162. package/phanpy/dist/_headers +0 -2
  163. package/phanpy/dist/apple-touch-icon.png +0 -0
  164. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  165. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  166. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  167. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  168. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  169. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  170. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  171. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  172. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  173. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  174. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  175. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  176. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  177. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  178. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  179. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  180. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  181. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  182. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  183. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  184. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  185. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  186. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  187. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  188. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  189. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  197. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  198. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  199. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  200. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  201. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  202. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  203. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  204. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  206. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  207. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  208. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  209. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  210. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  211. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  212. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  213. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  214. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  215. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  216. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  217. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  218. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  219. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  220. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  221. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  222. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  223. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  224. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  225. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  226. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  227. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  228. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  229. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  230. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  231. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  232. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  233. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  234. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  235. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  236. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  237. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  238. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  239. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  240. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  241. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  242. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  243. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  244. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  245. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  246. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  247. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  248. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  249. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  250. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  251. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  252. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  253. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  254. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  255. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  256. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  257. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  258. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  259. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  260. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  261. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  262. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  263. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  264. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  265. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  266. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  267. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  268. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  269. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  270. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  271. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  272. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  273. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  274. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  275. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  276. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  277. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  278. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  279. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  280. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  281. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  282. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  283. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  284. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  285. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  286. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  287. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  288. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  289. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  290. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  291. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  292. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  293. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  294. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  295. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  296. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  297. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  298. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  299. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  300. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  301. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  302. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  303. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  304. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  305. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  306. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  307. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  308. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  309. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  310. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  311. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  312. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  313. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  314. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  315. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  316. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  317. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  318. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  319. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  320. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  321. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  323. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  324. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  325. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  326. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  327. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  328. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  329. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  331. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  332. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  333. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  334. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  336. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  337. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  338. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  339. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  340. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  341. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  342. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  343. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  344. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  345. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  346. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  347. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  348. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  349. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  350. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  351. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  352. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  353. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  354. package/phanpy/dist/compose/index.html +0 -20
  355. package/phanpy/dist/favicon.ico +0 -0
  356. package/phanpy/dist/index.html +0 -73
  357. package/phanpy/dist/logo-192.png +0 -0
  358. package/phanpy/dist/logo-512.png +0 -0
  359. package/phanpy/dist/logo-badge-72.png +0 -0
  360. package/phanpy/dist/logo-maskable-512.png +0 -0
  361. package/phanpy/dist/logo-monochrome-512.png +0 -0
  362. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  363. package/phanpy/dist/manifest.webmanifest +0 -1
  364. package/phanpy/dist/og-image-2.jpg +0 -0
  365. package/phanpy/dist/og-image.png +0 -0
  366. package/phanpy/dist/robots.txt +0 -2
  367. package/phanpy/dist/sw.js +0 -2
  368. package/phanpy/dist/sw.js.map +0 -1
  369. package/phanpy/dist/version.json +0 -1
  370. package/run-agent.mjs +0 -899
  371. package/vendor/gate.cjs +0 -138
  372. package/vendor/idp-grant.cjs +0 -394
  373. package/vendor/jfetch.cjs +0 -16
  374. package/web/admin/actors.js +0 -147
  375. package/web/admin/bar.css +0 -126
  376. package/web/admin/bar.js +0 -99
  377. package/web/admin/client/client.js +0 -66
  378. package/web/admin/client/index.html +0 -69
  379. package/web/admin/client-pick.js +0 -79
  380. package/web/admin/common.js +0 -23
  381. package/web/admin/connections.js +0 -112
  382. package/web/admin/gateway.js +0 -133
  383. package/web/admin/group.js +0 -258
  384. package/web/admin/index.html +0 -612
  385. package/web/admin/notices-bar.js +0 -90
  386. package/web/admin/oauth-signin.mjs +0 -37
  387. package/web/admin/record.js +0 -393
  388. package/web/admin/setup/index.html +0 -257
  389. package/web/admin/setup/setup.js +0 -339
  390. package/web/admin/tokens.css +0 -68
  391. package/web/admin/upkeep.js +0 -177
  392. package/web/admin/window.css +0 -55
  393. package/web/admin/window.js +0 -136
  394. package/web/app/README.md +0 -81
  395. package/web/app/admin-facade.mjs +0 -588
  396. package/web/app/agent.mjs +0 -672
  397. package/web/app/atproto-browser.mjs +0 -86
  398. package/web/app/authorize_interaction.html +0 -56
  399. package/web/app/authorize_interaction.js +0 -69
  400. package/web/app/boot.mjs +0 -616
  401. package/web/app/deliver-relay.mjs +0 -129
  402. package/web/app/dist/boot.js +0 -34562
  403. package/web/app/dist/boot.js.map +0 -7
  404. package/web/app/dist/sw.js +0 -73456
  405. package/web/app/dist/sw.js.map +0 -7
  406. package/web/app/fediacct-browser.mjs +0 -146
  407. package/web/app/gateway-move.mjs +0 -60
  408. package/web/app/idb-kv.mjs +0 -35
  409. package/web/app/index.html +0 -258
  410. package/web/app/keys-browser.mjs +0 -71
  411. package/web/app/keystore.mjs +0 -82
  412. package/web/app/oidc-session.mjs +0 -6
  413. package/web/app/pod-auth.mjs +0 -248
  414. package/web/app/pod-remote.mjs +0 -84
  415. package/web/app/shims/_globals.mjs +0 -2
  416. package/web/app/shims/fedify-sig.mjs +0 -77
  417. package/web/app/shims/node-crypto.mjs +0 -113
  418. package/web/app/shims/node-fs.mjs +0 -17
  419. package/web/app/shims/node-path.mjs +0 -19
  420. package/web/app/shims/node-url.mjs +0 -7
  421. package/web/app/shims/prelude.js +0 -58
  422. package/web/app/shims/safefetch.mjs +0 -58
  423. package/web/app/shims/shapes-text.mjs +0 -8
  424. package/web/app/shims/web-push.mjs +0 -14
  425. package/web/app/signup.mjs +0 -357
  426. package/web/app/sw-src.mjs +0 -220
  427. package/web/app/update.js +0 -70
  428. package/web/front/admin.html +0 -55
  429. package/web/front/admin.js +0 -89
  430. package/web/front/install.sh +0 -69
  431. package/web/front/new-account.html +0 -35
  432. package/web/front/new-account.js +0 -13
  433. package/web/front/notices.html +0 -69
  434. package/web/front/notices.js +0 -107
  435. package/web/front/run.html +0 -64
  436. package/web/front/run.js +0 -221
  437. package/web/front/solid-oidc-client.js +0 -6
  438. package/web/icons/forum.svg +0 -11
  439. package/web/icons/gateway.svg +0 -14
  440. package/web/icons/local.svg +0 -11
@@ -1,561 +0,0 @@
1
- // social.mjs — outbound social actions (follow, like, announce, delete)
2
- // shared by the admin API and the Mastodon-API facade. Each action delivers
3
- // the activity and mirrors state into the store; the store keeps the
4
- // activity a later Undo must name.
5
-
6
- import crypto from 'node:crypto';
7
- import * as wire from './wire.mjs';
8
- import * as podNotes from '../pod/notes.mjs';
9
- import { dropFollower } from './store.mjs';
10
-
11
- // WebFinger, ours rather than fedify's — v2 dropped lookupWebFinger.
12
- //
13
- // Better here anyway: the host comes out of a handle, and a handle can arrive
14
- // in a MENTION inside a post somebody else wrote, so it is remote input and
15
- // belongs behind the same SSRF guard as every other outbound request. fedify's
16
- // lookup did not go through ours. https only, because a handle resolved over
17
- // plaintext is a handle anyone on the path can answer for.
18
- //
19
- // `fetchImpl`, when given, makes the request instead of safeFetch: the agent's
20
- // own remote read, which in the browser build is the relay — a server-side
21
- // fetch, so the lookup does not depend on the far pod host answering a
22
- // browser's cross-origin request. Every other remote read there goes the
23
- // same way; WebFinger went direct and was the one lookup that could fail
24
- // while the rest worked.
25
- export async function lookupWebFinger(acct, { fetchImpl = null } = {}) {
26
- const clean = String(acct || '').replace(/^acct:/, '');
27
- const at = clean.lastIndexOf('@');
28
- if (at < 1) return null;
29
- const [user, host] = [clean.slice(0, at), clean.slice(at + 1)];
30
- if (!host || /[/\\?#]/.test(host)) return null;
31
- const { safeFetch, readCapped } = await import('../shared/safefetch.mjs');
32
- const url = `https://${host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${clean}`)}`;
33
- const headers = { accept: 'application/jrd+json, application/json' };
34
- const res = await (fetchImpl ? fetchImpl(url, { headers }) : safeFetch(url, { headers })).catch(() => null);
35
- if (!res || res.status >= 400) return null;
36
- let jrd;
37
- try { jrd = JSON.parse(await readCapped(res, 256 * 1024)); } catch { return null; }
38
- // The JRD says who it is about. Nothing checked it, so a host answering every
39
- // query with one account's document made `@anyone@that.host` resolve to that
40
- // account — and it was then stored under the handle that was asked for, not
41
- // the one it turned out to be.
42
- const subject = String(jrd?.subject || '').replace(/^acct:/, '').toLowerCase();
43
- if (subject && subject !== clean.toLowerCase()) {
44
- return null;
45
- }
46
- return jrd;
47
- }
48
-
49
- export const selfLink = (jrd) => jrd?.links?.find(l => l.rel === 'self' &&
50
- /application\/(activity\+json|ld\+json)/.test(l.type || ''));
51
-
52
- // A WebFinger server can name an actor on ANY host, and until this ran we
53
- // believed it. The handle is remote input — it arrives in the Mention tags of a
54
- // note somebody else wrote — so evil.example could answer for `@x@evil.example`
55
- // with a real, busy actor elsewhere, and a reply that carried that mention was
56
- // addressed, tagged and DELIVERED to a third party who had nothing to do with
57
- // the thread, over our signature.
58
- //
59
- // The host cannot simply be required to match: `@user@example.com` served by
60
- // mastodon.example is the ordinary delegated setup and the whole point of
61
- // WebFinger. So ask the ACTOR's own host who it says that actor is, and require
62
- // it to come back to the same document. A server may name anyone; it cannot
63
- // make that person's server agree. This is the round trip Mastodon does.
64
- //
65
- // Only when the two hosts differ — same-host is self-consistent by
66
- // construction, so the common case costs no extra request.
67
- export async function confirmDelegation(asked, doc, lookup = lookupWebFinger) {
68
- let home;
69
- try { home = new URL(doc.id).host.toLowerCase(); }
70
- catch { throw new Error(`actor id is not a URL (${doc.id})`); }
71
- if (home === asked.toLowerCase()) return;
72
- const user = doc.preferredUsername;
73
- if (!user) {
74
- throw new Error(`${asked} points at ${doc.id} on another host, which names no `
75
- + 'username to confirm it by');
76
- }
77
- const back = await lookup(`acct:${user}@${home}`);
78
- if (selfLink(back)?.href !== doc.id) {
79
- throw new Error(`${asked} claims ${doc.id}, but ${home} does not agree that is `
80
- + `@${user}@${home} — refusing to take one host's word for another's actor`);
81
- }
82
- }
83
-
84
- // acct:user@host → verified actor doc (cached by fetchAP), or throws.
85
- export async function resolveHandle(agent, handle) {
86
- const clean = String(handle || '').replace(/^@/, '');
87
- if (!/^[^@]+@[^@]+$/.test(clean)) throw new Error('handle must look like user@host');
88
- if (agent.store.isBlocked('https://' + clean.split('@')[1] + '/')) throw new Error('domain is blocked');
89
- // Through the agent's own remote read where it has one — the relay, in the
90
- // browser build — and directly otherwise.
91
- const remote = agent.intake?.deliverer?.signedFetch
92
- ? (u, i) => agent.intake.deliverer.signedFetch(u, i) : null;
93
- const lookup = (a) => lookupWebFinger(a, { fetchImpl: remote });
94
- const jrd = await lookup('acct:' + clean);
95
- const self = selfLink(jrd);
96
- if (!self?.href) throw new Error(`webfinger found no actor for ${clean}`);
97
- const doc = await agent.intake.fetchAP(self.href);
98
- if (!doc?.id) throw new Error(`actor document unusable for ${clean}`);
99
- if (agent.store.isBlocked(doc.id)) throw new Error('actor is blocked');
100
- await confirmDelegation(clean.slice(clean.lastIndexOf('@') + 1), doc, lookup);
101
- // Counts live in the collections, not the actor document, so a client asking
102
- // "who is this" would otherwise be told everyone has none. Two GETs, and only
103
- // when someone asked by name — never on the drain path.
104
- await cacheCounts(agent, doc);
105
- return doc;
106
- }
107
-
108
- // totalItems off the followers/following collections, stored beside the cached
109
- // actor. Best effort: a collection that will not answer leaves the count unset
110
- // rather than reported as zero, which is a different claim.
111
- async function cacheCounts(agent, doc) {
112
- const total = async (url) => {
113
- if (!url) return null;
114
- try {
115
- const res = await agent.intake.fetchAP(url);
116
- return Number.isInteger(res?.totalItems) ? res.totalItems : null;
117
- } catch { return null; }
118
- };
119
- const [followers, following] = await Promise.all([total(doc.followers), total(doc.following)]);
120
- if (followers === null && following === null) return;
121
- const actors = agent.store.getActors();
122
- if (!actors[doc.id]) return;
123
- actors[doc.id] = { ...actors[doc.id], counts: { followers, following } };
124
- agent.store.write('actors.json', actors);
125
- }
126
-
127
- // `doc` skips the fetch when the caller already resolved the actor; a bulk
128
- // import passes publish:false and republishes the collections once at the end.
129
- export async function followActor(agent, actorUrl, { publish = true, doc: fetched = null, serial = Date.now() } = {}) {
130
- const doc = fetched || await agent.intake.fetchAP(actorUrl);
131
- if (!doc?.inbox) throw new Error(`actor document unusable (${actorUrl})`);
132
- if (agent.store.isBlocked(doc.id)) throw new Error('actor is blocked');
133
- const contacts = agent.store.getContacts();
134
- let rec = contacts.following.find(f => f.actor === doc.id);
135
- if (!rec) {
136
- rec = { actor: doc.id, inbox: doc.inbox, accepted: false };
137
- contacts.following.push(rec);
138
- }
139
- const follow = wire.followActivity({ urls: agent.publisher.urls, targetActor: doc.id, serial });
140
- rec.followActivity = follow; // kept for a later Undo
141
- agent.store.setContacts(contacts);
142
- await agent.deliverer.deliver(doc.inbox, follow);
143
- if (publish) await agent.publisher.publishCollections({ following: true, pending: true });
144
- return doc;
145
- }
146
-
147
- export async function followHandle(agent, handle, { serial = Date.now() } = {}) {
148
- const doc = await resolveHandle(agent, handle);
149
- await followActor(agent, doc.id, { serial });
150
- const clean = String(handle || '').replace(/^@/, '');
151
- const contacts = agent.store.getContacts();
152
- const rec = contacts.following.find(f => f.actor === doc.id);
153
- if (rec && !rec.handle) { rec.handle = clean; agent.store.setContacts(contacts); }
154
- return { ok: true, actor: doc.id };
155
- }
156
-
157
- export async function unfollowActor(agent, actor, { serial = Date.now() } = {}) {
158
- const contacts = agent.store.getContacts();
159
- const rec = contacts.following.find(f => f.actor === actor);
160
- if (!rec) throw new Error('not following that actor');
161
- if (rec.followActivity) {
162
- await agent.deliverer.deliver(rec.inbox,
163
- wire.undoActivity({ urls: agent.publisher.urls, activity: rec.followActivity, serial }));
164
- }
165
- contacts.following = contacts.following.filter(f => f.actor !== actor);
166
- agent.store.setContacts(contacts);
167
- await agent.publisher.publishCollections({ following: true, pending: true });
168
- return { ok: true };
169
- }
170
-
171
- export async function favourite(agent, s, { serial = Date.now() } = {}) {
172
- if (s.favourited) return s;
173
- const doc = await agent.intake.fetchAP(s.actor);
174
- if (!doc?.inbox) throw new Error('author inbox unavailable');
175
- const act = wire.likeActivity({ urls: agent.publisher.urls, noteId: s.noteId, serial, to: s.actor });
176
- await agent.deliverer.deliver(doc.endpoints?.sharedInbox || doc.inbox, act);
177
- return agent.store.updateStatus(s.noteId, { favourited: true, likeActivity: act });
178
- }
179
-
180
- export async function unfavourite(agent, s, { serial = Date.now() } = {}) {
181
- if (s.likeActivity) {
182
- const doc = await agent.intake.fetchAP(s.actor).catch(() => null);
183
- if (doc?.inbox) {
184
- await agent.deliverer.deliver(doc.endpoints?.sharedInbox || doc.inbox,
185
- wire.undoActivity({ urls: agent.publisher.urls, activity: s.likeActivity, serial }));
186
- }
187
- }
188
- return agent.store.updateStatus(s.noteId, { favourited: false, likeActivity: undefined });
189
- }
190
-
191
- // Announce goes to our followers (that's what boosting is) and to the author.
192
- export async function reblog(agent, s, { serial = Date.now() } = {}) {
193
- if (s.reblogged) return s;
194
- const { urls } = agent.publisher;
195
- const act = wire.announceActivity({ urls, object: s.noteId, serial });
196
- const inboxes = new Set(agent.store.getContacts().followers
197
- .map(f => f.sharedInbox || f.inbox).filter(Boolean));
198
- if (s.actor !== urls.actor) {
199
- const doc = await agent.intake.fetchAP(s.actor).catch(() => null);
200
- if (doc?.inbox) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
201
- }
202
- await agent.deliverer.deliverToAll([...inboxes], act);
203
- // Mark it boosted before recording it: a failed outbox write costs one
204
- // missing entry, a failed status write would let a retry announce twice.
205
- const updated = agent.store.updateStatus(s.noteId, { reblogged: true, announceActivity: act });
206
- await agent.publisher.recordOutbox(act);
207
- if (await agent.store.commit?.() === false) agent.log(`boost sent but its timeline row was refused: ${s.noteId}`);
208
- return updated;
209
- }
210
-
211
- export async function unreblog(agent, s, { serial = Date.now() } = {}) {
212
- if (s.announceActivity) {
213
- const undo = wire.undoActivity({ urls: agent.publisher.urls, activity: s.announceActivity, serial });
214
- const inboxes = new Set(agent.store.getContacts().followers
215
- .map(f => f.sharedInbox || f.inbox).filter(Boolean));
216
- const doc = await agent.intake.fetchAP(s.actor).catch(() => null);
217
- if (doc?.inbox && s.actor !== agent.publisher.urls.actor) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
218
- await agent.deliverer.deliverToAll([...inboxes], undo);
219
- await agent.publisher.unrecordOutbox(i => i?.id === s.announceActivity.id, { record: () => undo });
220
- }
221
- return agent.store.updateStatus(s.noteId, { reblogged: false, announceActivity: undefined });
222
- }
223
-
224
- // Group: end a following we will no longer carry. Forgetting them locally is
225
- // not enough — without the Reject their server keeps the relationship and they
226
- // keep receiving everything the group announces. Muting them too, because an
227
- // ejection anyone can undo by re-following is not an ejection; a re-follow is
228
- // still auto-accepted, but nothing of theirs gets carried.
229
- export async function ejectFollower(agent, actor) {
230
- const { urls } = agent.publisher;
231
- const contacts = agent.store.getContacts();
232
- const rec = contacts.followers.find(f => f.actor === actor);
233
- if (!rec) throw new Error('not a member');
234
- dropFollower(contacts, actor, 'ejected');
235
- agent.store.setContacts(contacts);
236
- const muted = agent.store.getMuted();
237
- muted.actors = [...new Set([...muted.actors, actor])];
238
- agent.store.setMuted(muted);
239
- await agent.publisher.publishCollections({ followers: true });
240
- // The ban is broadcast to the membership (FEP-1b12), so their servers can
241
- // mirror it. Never sent to the ejected member — they get the Reject below.
242
- await announceModeration(agent,
243
- wire.blockActivity({ urls, targetActor: actor, serial: Date.now() }))
244
- .catch(e => agent.log?.(`moderation announce failed: ${e.message}`));
245
- // A Bluesky-side member's departure sticks by blocking the DID.
246
- if (rec.bsky) {
247
- await agent.bskygroup?.blockDid(rec.bsky.did)
248
- .catch(e => agent.log?.(`bluesky block failed for ${rec.bsky.handle}: ${e.message}`));
249
- return { ok: true, actor, told: true };
250
- }
251
- if (rec.inbox) {
252
- await agent.deliverer.deliver(rec.inbox, wire.rejectActivity({
253
- urls,
254
- followActivity: { id: rec.followId, type: 'Follow', actor, object: urls.actor },
255
- serial: Date.now(),
256
- }));
257
- }
258
- return { ok: true, actor, told: !!rec.inbox };
259
- }
260
-
261
- // Group: answer a held join request. Admitting is the Accept onFollow would
262
- // have sent immediately had the group not been gated.
263
- export async function admitRequest(agent, actor, { publish = true, serial = Date.now() } = {}) {
264
- const reqs = agent.store.getRequests();
265
- const req = reqs.find(r => r.actor === actor);
266
- if (!req) throw new Error('no such request');
267
- const contacts = agent.store.getContacts();
268
- if (!contacts.followers.some(f => f.actor === actor)) {
269
- // Admitting a bridged Bluesky member retires their Bluesky-only record,
270
- // the same way an ungated group's follow does: one person, one membership.
271
- const bridgedDid = /^https:\/\/bsky\.brid\.gy\/ap\/(did:[^/]+)$/.exec(actor)?.[1];
272
- if (bridgedDid) contacts.followers = contacts.followers.filter(f => f.bsky?.did !== bridgedDid);
273
- contacts.followers.push(req.bsky
274
- ? { actor, bsky: req.bsky }
275
- : { actor, inbox: req.inbox, sharedInbox: req.sharedInbox, followId: req.activity?.id });
276
- agent.store.setContacts(contacts);
277
- agent.store.addNotification({ type: 'follow', actor, ...(req.bsky ? { bsky: true } : {}) });
278
- }
279
- agent.store.setRequests(reqs.filter(r => r.actor !== actor));
280
- // A Bluesky-only member has no inbox and no Accept to receive; the follow
281
- // already stands on their side. The one-time bridge nudge goes out instead.
282
- if (req.bsky) {
283
- await agent.bskygroup?.nudgeOnce(contacts.followers.find(f => f.actor === actor));
284
- return { ok: true, actor };
285
- }
286
- // A bulk admit passes publish:false and republishes once for the lot.
287
- if (publish) await agent.publisher.publishCollections({ followers: true, pending: true });
288
- await agent.deliverer.deliver(req.inbox, wire.acceptActivity({
289
- urls: agent.publisher.urls, followActivity: req.activity, serial,
290
- }));
291
- return { ok: true, actor };
292
- }
293
-
294
- // Refusing is not a ban: they may ask again, and the queue is where you would
295
- // see that. Eject is the sticky one, because it mutes.
296
- export async function refuseRequest(agent, actor, { serial = Date.now() } = {}) {
297
- const reqs = agent.store.getRequests();
298
- const req = reqs.find(r => r.actor === actor);
299
- if (!req) throw new Error('no such request');
300
- agent.store.setRequests(reqs.filter(r => r.actor !== actor));
301
- await agent.publisher?.publishCollections({ pending: true }).catch(() => {});
302
- // No Reject can reach a Bluesky-only requester, so the refusal is enforced
303
- // where it can be: their DID is blocked.
304
- if (req.bsky) {
305
- await agent.bskygroup?.blockDid(req.bsky.did)
306
- .catch(e => agent.log?.(`bluesky block failed for ${req.bsky.handle}: ${e.message}`));
307
- return { ok: true, actor };
308
- }
309
- await agent.deliverer.deliver(req.inbox, wire.rejectActivity({
310
- urls: agent.publisher.urls, followActivity: req.activity, serial,
311
- }));
312
- return { ok: true, actor };
313
- }
314
-
315
- // Group: unsay an Announce. The work is in Intake because an upstream Delete
316
- // has to do the same thing, and both must use the recipient set amplify used.
317
- export async function retractAnnouncement(agent, noteId) {
318
- return agent.intake.retract(noteId);
319
- }
320
-
321
- // Remove an own note everywhere: Delete→followers, remote note + outbox,
322
- // local pod RDF, statuses mirror.
323
- //
324
- // Two of the three pod deletes have to land, and both are checked. While the
325
- // note document answers, anyone holding its id can still read a post its author
326
- // deleted — and the `-create` beside it EMBEDS the whole note, so leaving that
327
- // one behind publishes the same content under another name. A failure keeps the
328
- // post here and says so, rather than reporting a deletion that did not happen:
329
- // removing it locally would leave nothing to try again with, and nothing to see
330
- // that it is still up.
331
- // A vote is the fediverse convention: one bare Note per chosen option, whose
332
- // name IS the option, inReplyTo the question, addressed to the question's
333
- // author alone. The documents are stored for dereference but never listed —
334
- // not in the outbox, not in the local index.
335
- export async function votePoll(agent, s, choices) {
336
- const { urls } = agent.publisher;
337
- const poll = s.poll || {};
338
- const opts = poll.options || [];
339
- if (poll.closed || (poll.expiresAt && Date.parse(poll.expiresAt) < Date.now())) {
340
- return { ok: false, error: 'this poll has closed' };
341
- }
342
- if (poll.voted) return { ok: false, error: 'already voted' };
343
- // Your own poll is the one you are counting, not one you are answering.
344
- if (s.actor === urls.actor) return { ok: false, error: 'this is your own poll' };
345
- const picks = [...new Set(choices)].filter(i => i >= 0 && i < opts.length);
346
- if (!picks.length) return { ok: false, error: 'invalid choice' };
347
- if (!poll.multiple && picks.length > 1) return { ok: false, error: 'this poll takes one choice' };
348
- const author = await agent.intake.fetchAP(s.actor).catch(() => null);
349
- const inbox = author?.endpoints?.sharedInbox || author?.inbox;
350
- if (!inbox) return { ok: false, error: 'could not reach the poll author' };
351
- const published = new Date().toISOString();
352
- for (const i of picks) {
353
- const id = urls.notes + published.slice(0, 10) + '-vote-' + crypto.randomBytes(4).toString('hex');
354
- const note = {
355
- '@context': wire.AS_CTX, id, type: 'Note',
356
- attributedTo: urls.actor, name: opts[i].title,
357
- inReplyTo: s.noteId, published, to: [s.actor],
358
- };
359
- const create = {
360
- '@context': wire.AS_CTX, id: wire.createActivityId(id), type: 'Create',
361
- actor: urls.actor, published, to: [s.actor], object: note,
362
- };
363
- await podNotes.write(agent.remote, id, note);
364
- await podNotes.writeCreate(agent.remote, create.id, create);
365
- await agent.deliverer.deliverToAll([inbox], create);
366
- }
367
- agent.store.updateStatus(s.noteId, {
368
- poll: {
369
- ...poll, voted: true, ownVotes: picks,
370
- options: opts.map((o, j) => (picks.includes(j) ? { ...o, votes: (o.votes || 0) + 1 } : o)),
371
- },
372
- });
373
- return { ok: true };
374
- }
375
-
376
- export async function deleteNote(agent, s) {
377
- const { urls } = agent.publisher;
378
- // The deletion goes where the post went: followers unless it was direct, and
379
- // everyone it named or was blind-copied to. Addressed as the post was, read
380
- // from the post itself while it is still there.
381
- const doc = await Promise.resolve().then(() => podNotes.read(agent.remote, s.noteId)).catch(() => null);
382
- const shape = wire.addressing(urls, s.visibility || 'public', (s.mentions || []).map(m => m.href).filter(Boolean));
383
- const to = Array.isArray(doc?.to) ? doc.to : shape.to;
384
- const cc = Array.isArray(doc?.cc) ? doc.cc : shape.cc;
385
- const named = [...to, ...cc, ...(s.addressed || []), ...(s.replyActor ? [s.replyActor] : [])]
386
- .filter(a => typeof a === 'string' && a !== wire.PUBLIC && a !== urls.followers && a !== urls.actor);
387
- const inboxes = [
388
- ...(s.visibility === 'direct' ? [] : agent.store.getContacts().followers.map(f => f.sharedInbox || f.inbox)),
389
- ...(agent.publisher.inboxesFor ? await agent.publisher.inboxesFor(named).catch(() => []) : []),
390
- ].filter(Boolean);
391
- const del = wire.deleteActivity({ urls, noteId: s.noteId, to, cc });
392
- await agent.deliverer.deliverToAll(inboxes, del);
393
-
394
- // The note is REPLACED with a Tombstone rather than removed, so a peer that
395
- // dereferences it learns the post was deleted instead of a bare 404 (§7.4).
396
- // The Tombstone must stay readable; the container above it is owner-only, so
397
- // the ACL cannot be inherited. The Create activity is the withdrawn action,
398
- // not the object, so it is deleted outright.
399
- const stuck = [];
400
- const deletedAt = new Date().toISOString();
401
- // Only inside the posts folders: an object that lives elsewhere on the pod
402
- // (an annotation a client keeps in its own place, or anything it named) is
403
- // that client's document, and a deletion here must not overwrite it — the
404
- // profile, say — with a Tombstone. Its Create and outbox entry still go.
405
- const ours = [urls.notes, urls.privateNotes].some(c => String(s.noteId).startsWith(c));
406
- try {
407
- if (ours) await podNotes.writeTombstone(agent.remote, s.noteId, wire.noteTombstone(s.noteId, deletedAt));
408
- } catch { stuck.push(s.noteId); }
409
- // The Create becomes a Tombstone too, not a gap: it embedded the post, so
410
- // its words go, and whoever asks for it is told it is gone (410 through the
411
- // Gateway) rather than that it never was.
412
- // An object kept outside the posts folders has its Create inside them, under
413
- // the name it was given (publishObject).
414
- const createId = ours ? wire.createActivityId(s.noteId) : (s.slug ? wire.createActivityId(urls.notes + s.slug) : null);
415
- try {
416
- if (createId) await podNotes.writeTombstone(agent.remote, createId, wire.noteTombstone(createId, deletedAt, 'Create'));
417
- } catch { stuck.push(createId); }
418
- if (stuck.length) {
419
- agent.log?.(`delete ${s.noteId}: the pod kept ${stuck.length} document(s) — the post is STILL PUBLISHED`);
420
- return {
421
- ok: false, stillPublished: stuck,
422
- error: 'the pod would not remove the post — it is still published, and has been kept here so you can try again',
423
- };
424
- }
425
- // Empty, and referenced by nothing once the note is gone.
426
- await podNotes.dropReplies(agent.remote, wire.repliesId(s.noteId));
427
-
428
- // The Bluesky mirror goes with the post. Best-effort: a mirror the far side
429
- // already lost is not a reason to keep the post here.
430
- if (s.atproto?.uri && agent.atproto?.connected()) {
431
- await agent.atproto.deleteCrossPost(s.atproto.uri)
432
- .then(() => agent.log?.(`bluesky mirror deleted: ${s.atproto.uri}`))
433
- .catch(e => agent.log?.(`bluesky mirror not deleted (${e.message}): ${s.atproto.uri}`));
434
- }
435
-
436
- // The note's outbox entry, and our own boost of it if there was one. The
437
- // Delete takes the post's place — only for a post the outbox listed, so a
438
- // private post's deletion stays as private as the post was.
439
- const itsOwn = (i) => i === s.noteId || (i?.id && i.id === s.announceActivity?.id)
440
- // its edits too: an Update carries the post's words, which must not
441
- // outlive the post on the public record
442
- || (i?.type === 'Update' && (typeof i.object === 'string' ? i.object : i.object?.id) === s.noteId);
443
- await agent.publisher.unrecordOutbox(itsOwn,
444
- { record: (gone) => (gone.includes(s.noteId) ? del : null) });
445
- agent.store.removeStatus(s.noteId);
446
- return { ok: true };
447
- }
448
-
449
- // FEP-1b12: a GROUP's moderation is announced to its followers, so their
450
- // servers can mirror the action. Group moderation only — a person's blocks
451
- // never go on the wire (that is also the spec's own SHOULD NOT), which is why
452
- // the guard lives here rather than in every caller. Never delivered to the
453
- // moderated party; the announce goes to the membership.
454
- export async function announceModeration(agent, activity) {
455
- if (agent.store.getConfig()?.kind !== 'group') return null;
456
- const { urls } = agent.publisher;
457
- const act = wire.announceActivity({ urls, object: activity, serial: Date.now(), audience: urls.actor });
458
- const inboxes = [...new Set(agent.store.getContacts().followers
459
- .map(f => f.sharedInbox || f.inbox).filter(Boolean))];
460
- await agent.deliverer.deliverToAll(inboxes, act);
461
- await agent.publisher.recordOutbox(act);
462
- return act;
463
- }
464
-
465
- // A block is state, not speech: the list changes and the follow ends, and
466
- // nothing is delivered TO THE BLOCKED PARTY (§6.10). Intake already refuses a
467
- // blocked author on arrival. A group additionally announces the ban to its
468
- // members (FEP-1b12) — announceModeration keeps a person's blocks silent.
469
- export async function blockActor(agent, actorUrl, { serial = Date.now() } = {}) {
470
- if (!/^https?:\/\/\S+$/.test(String(actorUrl || ''))) throw new Error('actor must be a URL');
471
- const b = agent.store.getBlocklist();
472
- if (!b.actors.includes(actorUrl)) b.actors.push(actorUrl);
473
- agent.store.setBlocklist(b);
474
- await unfollowActor(agent, actorUrl).catch(() => {}); // not following is fine
475
- await agent.publisher?.publishCollections({ blocked: true }).catch(() => {});
476
- await announceModeration(agent,
477
- wire.blockActivity({ urls: agent.publisher.urls, targetActor: actorUrl, serial }))
478
- .catch(e => agent.log?.(`moderation announce failed: ${e.message}`));
479
- return { ok: true, actor: actorUrl };
480
- }
481
-
482
- export async function unblockActor(agent, actorUrl, { serial = Date.now() } = {}) {
483
- const b = agent.store.getBlocklist();
484
- b.actors = b.actors.filter(a => a !== actorUrl);
485
- agent.store.setBlocklist(b);
486
- await agent.publisher?.publishCollections({ blocked: true }).catch(() => {});
487
- // The unban is announced as the Undo of the ban, the ban re-stated inside it
488
- // — its id was a fragment nobody stored, so receivers match on the shape.
489
- await announceModeration(agent, wire.undoActivity({
490
- urls: agent.publisher.urls,
491
- activity: { type: 'Block', actor: agent.publisher.urls.actor, object: actorUrl },
492
- serial,
493
- })).catch(e => agent.log?.(`moderation announce failed: ${e.message}`));
494
- return { ok: true, actor: actorUrl };
495
- }
496
-
497
- // Apply (or refuse) one queued moderation request — an action a LISTED
498
- // moderator asked for over federation. Deliveries carry nothing that proves
499
- // the sender, which is exactly why these wait in a queue for the operator
500
- // instead of running on arrival; applying one is the operator vouching for it.
501
- export async function applyModeration(agent, entry) {
502
- const { urls } = agent.publisher;
503
- const target = typeof entry.activity?.object === 'string'
504
- ? entry.activity.object : entry.activity?.object?.id;
505
- switch (entry.type) {
506
- case 'Block': {
507
- if (!target) throw new Error('the queued Block names nobody');
508
- // A member is ejected (which mutes and announces the ban); a stranger is
509
- // simply blocked. Either way the ban is broadcast once.
510
- const member = agent.store.getContacts().followers.some(f => f.actor === target);
511
- return member ? ejectFollower(agent, target) : blockActor(agent, target);
512
- }
513
- case 'Undo': {
514
- const inner = entry.activity?.object;
515
- const unbanned = typeof inner === 'object' && inner?.type === 'Block'
516
- ? (typeof inner.object === 'string' ? inner.object : inner.object?.id) : null;
517
- if (!unbanned) throw new Error('the queued Undo does not name a Block');
518
- const muted = agent.store.getMuted();
519
- muted.actors = muted.actors.filter(a => a !== unbanned);
520
- agent.store.setMuted(muted);
521
- return unblockActor(agent, unbanned);
522
- }
523
- case 'Delete': {
524
- if (!target) throw new Error('the queued Delete names nothing');
525
- // Removing a post from the group is unsaying its carry.
526
- return agent.intake.retract(target);
527
- }
528
- case 'Add':
529
- case 'Remove': {
530
- if (!target) throw new Error('the queued roster change names nobody');
531
- const cfg = { ...agent.store.getConfig() };
532
- const prev = cfg.moderators || [];
533
- cfg.moderators = entry.type === 'Add'
534
- ? [...new Set([...prev, target])]
535
- : prev.filter(m => m !== target);
536
- agent.store.setConfig(cfg);
537
- if (agent.publisher) agent.publisher.config.moderators = cfg.moderators;
538
- await agent.publisher.publishProfile();
539
- await announceModeration(agent, wire.addRemoveActivity({
540
- urls, type: entry.type, object: target, target: urls.moderators, serial: Date.now(),
541
- })).catch(e => agent.log?.(`moderation announce failed: ${e.message}`));
542
- return { ok: true, moderators: cfg.moderators };
543
- }
544
- default: throw new Error(`no way to apply a queued ${entry.type}`);
545
- }
546
- }
547
-
548
- // Pin or unpin an own post. Republishing the featured collection is what makes
549
- // the pin real to anyone else — the actor document names that collection, and
550
- // a visitor's server reads it, so the profile is republished too (it skips
551
- // itself when nothing changed).
552
- export async function pinStatus(agent, s, pinned) {
553
- if (s.actor !== agent.publisher.urls.actor) throw new Error('not your status');
554
- const updated = agent.store.updateStatus(s.noteId, { pinned: !!pinned });
555
- await agent.publisher.publishFeatured().catch(e => agent.log?.(`featured: ${e.message}`));
556
- if (agent.publisher.publishProfilePage) {
557
- await agent.publisher.publishProfilePage().catch(e => agent.log?.(`profile page: ${e.message}`));
558
- }
559
- agent.publisher.publishProfile?.().catch(() => {});
560
- return updated || s;
561
- }