@atproto/api 0.4.4 → 0.5.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 (290) hide show
  1. package/README.md +81 -0
  2. package/definitions/labels.json +212 -0
  3. package/definitions/locale/en/label-groups.json +38 -0
  4. package/definitions/locale/en/labels.json +366 -0
  5. package/definitions/locale/en/proposed-label-groups.json +38 -0
  6. package/definitions/locale/en/proposed-labels.json +632 -0
  7. package/definitions/moderation-behaviors.d.ts +48 -0
  8. package/definitions/post-moderation-behaviors.json +879 -0
  9. package/definitions/profile-moderation-behaviors.json +447 -0
  10. package/definitions/proposed-labels.json +326 -0
  11. package/dist/client/index.d.ts +3 -0
  12. package/dist/client/lexicons.d.ts +27 -1
  13. package/dist/client/types/app/bsky/actor/updateProfile.d.ts +3 -10
  14. package/dist/client/types/app/bsky/{feed/getBookmarkedFeeds.d.ts → graph/getListBlocks.d.ts} +2 -2
  15. package/dist/client/types/app/bsky/graph/listblock.d.ts +8 -0
  16. package/dist/client/types/app/bsky/{feed/unbookmarkFeed.d.ts → graph/subscribeMuteList.d.ts} +1 -1
  17. package/dist/client/types/app/bsky/{feed/bookmarkFeed.d.ts → graph/unsubscribeMuteList.d.ts} +1 -1
  18. package/dist/{src/client/types/app/bsky/notification/updateSeen.d.ts → client/types/app/bsky/unspecced/applyLabels.d.ts} +2 -1
  19. package/dist/helpers/bsky.d.ts +20 -0
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.js +1401 -12
  22. package/dist/index.js.map +4 -4
  23. package/dist/mixins/bsky.d.ts +23 -0
  24. package/dist/moderation/accumulator.d.ts +14 -0
  25. package/dist/moderation/const/label-groups.d.ts +2 -0
  26. package/dist/moderation/const/labels.d.ts +2 -0
  27. package/dist/moderation/index.d.ts +44 -0
  28. package/dist/moderation/subjects/account.d.ts +3 -0
  29. package/dist/moderation/subjects/feed-generator.d.ts +2 -0
  30. package/dist/moderation/subjects/post.d.ts +2 -0
  31. package/dist/moderation/subjects/profile.d.ts +3 -0
  32. package/dist/moderation/subjects/quoted-post.d.ts +6 -0
  33. package/dist/moderation/subjects/user-list.d.ts +2 -0
  34. package/dist/moderation/types.d.ts +97 -0
  35. package/dist/moderation/util.d.ts +12 -0
  36. package/dist/rich-text/sanitize.d.ts +4 -0
  37. package/docs/labels.md +522 -0
  38. package/docs/moderation-behaviors/posts.md +1919 -0
  39. package/docs/moderation-behaviors/profiles.md +907 -0
  40. package/docs/moderation.md +144 -0
  41. package/package.json +6 -4
  42. package/scripts/code/label-groups.mjs +68 -0
  43. package/scripts/code/labels.mjs +68 -0
  44. package/scripts/docs/labels.mjs +164 -0
  45. package/scripts/docs/post-moderation-behaviors.mjs +122 -0
  46. package/scripts/docs/profile-moderation-behaviors.mjs +122 -0
  47. package/scripts/generate-code.mjs +4 -0
  48. package/scripts/generate-docs.mjs +5 -0
  49. package/src/client/index.ts +13 -0
  50. package/src/client/lexicons.ts +29 -3
  51. package/src/client/types/app/bsky/unspecced/applyLabels.ts +33 -0
  52. package/src/client/types/com/atproto/admin/enableAccountInvites.ts +1 -1
  53. package/src/client/types/com/atproto/moderation/defs.ts +1 -1
  54. package/src/index.ts +4 -0
  55. package/src/moderation/accumulator.ts +181 -0
  56. package/src/moderation/const/label-groups.ts +143 -0
  57. package/src/moderation/const/labels.ts +798 -0
  58. package/src/moderation/index.ts +343 -0
  59. package/src/moderation/subjects/account.ts +40 -0
  60. package/src/moderation/subjects/feed-generator.ts +13 -0
  61. package/src/moderation/subjects/post.ts +23 -0
  62. package/src/moderation/subjects/profile.ts +31 -0
  63. package/src/moderation/subjects/quoted-post.ts +62 -0
  64. package/src/moderation/subjects/user-list.ts +13 -0
  65. package/src/moderation/types.ts +141 -0
  66. package/src/moderation/util.ts +98 -0
  67. package/tests/post-moderation.test.ts +46 -0
  68. package/tests/profile-moderation.test.ts +46 -0
  69. package/tests/util/index.ts +176 -0
  70. package/tests/util/moderation-behavior.ts +180 -0
  71. package/dist/client/types/app/bsky/actor/searchActor.d.ts +0 -22
  72. package/dist/client/types/app/bsky/actor/searchActorTypeahead.d.ts +0 -20
  73. package/dist/client/types/com/atproto/admin/getInviteCodeUsage.d.ts +0 -29
  74. package/dist/client/types/com/atproto/repo/importRepo.d.ts +0 -19
  75. package/dist/client/types/com/atproto/repo/uploadRepo.d.ts +0 -19
  76. package/dist/client/types/com/atproto/server/getUserInviteCodes.d.ts +0 -31
  77. package/dist/src/client/index.d.ts +0 -462
  78. package/dist/src/client/lexicons.d.ts +0 -2910
  79. package/dist/src/client/schemas.d.ts +0 -17
  80. package/dist/src/client/types/app/bsky/actor/createScene.d.ts +0 -32
  81. package/dist/src/client/types/app/bsky/actor/getProfile.d.ts +0 -36
  82. package/dist/src/client/types/app/bsky/actor/getSuggestions.d.ts +0 -36
  83. package/dist/src/client/types/app/bsky/actor/profile.d.ts +0 -15
  84. package/dist/src/client/types/app/bsky/actor/ref.d.ts +0 -14
  85. package/dist/src/client/types/app/bsky/actor/search.d.ts +0 -32
  86. package/dist/src/client/types/app/bsky/actor/searchTypeahead.d.ts +0 -28
  87. package/dist/src/client/types/app/bsky/actor/updateProfile.d.ts +0 -48
  88. package/dist/src/client/types/app/bsky/badge.d.ts +0 -22
  89. package/dist/src/client/types/app/bsky/badgeAccept.d.ts +0 -11
  90. package/dist/src/client/types/app/bsky/badgeOffer.d.ts +0 -11
  91. package/dist/src/client/types/app/bsky/declaration.d.ts +0 -6
  92. package/dist/src/client/types/app/bsky/embed/external.d.ts +0 -26
  93. package/dist/src/client/types/app/bsky/embed/images.d.ts +0 -23
  94. package/dist/src/client/types/app/bsky/feed/embed.d.ts +0 -36
  95. package/dist/src/client/types/app/bsky/feed/feedViewPost.d.ts +0 -26
  96. package/dist/src/client/types/app/bsky/feed/getAuthorFeed.d.ts +0 -22
  97. package/dist/src/client/types/app/bsky/feed/getPostThread.d.ts +0 -43
  98. package/dist/src/client/types/app/bsky/feed/getRepostedBy.d.ts +0 -35
  99. package/dist/src/client/types/app/bsky/feed/getTimeline.d.ts +0 -22
  100. package/dist/src/client/types/app/bsky/feed/getVotes.d.ts +0 -33
  101. package/dist/src/client/types/app/bsky/feed/mediaEmbed.d.ts +0 -18
  102. package/dist/src/client/types/app/bsky/feed/post.d.ts +0 -54
  103. package/dist/src/client/types/app/bsky/feed/repost.d.ts +0 -6
  104. package/dist/src/client/types/app/bsky/feed/setVote.d.ts +0 -25
  105. package/dist/src/client/types/app/bsky/feed/trend.d.ts +0 -6
  106. package/dist/src/client/types/app/bsky/feed/vote.d.ts +0 -7
  107. package/dist/src/client/types/app/bsky/follow.d.ts +0 -9
  108. package/dist/src/client/types/app/bsky/getAuthorFeed.d.ts +0 -56
  109. package/dist/src/client/types/app/bsky/getBadgeMembers.d.ts +0 -29
  110. package/dist/src/client/types/app/bsky/getHomeFeed.d.ts +0 -56
  111. package/dist/src/client/types/app/bsky/getLikedBy.d.ts +0 -29
  112. package/dist/src/client/types/app/bsky/getNotificationCount.d.ts +0 -16
  113. package/dist/src/client/types/app/bsky/getNotifications.d.ts +0 -33
  114. package/dist/src/client/types/app/bsky/getPostThread.d.ts +0 -55
  115. package/dist/src/client/types/app/bsky/getProfile.d.ts +0 -26
  116. package/dist/src/client/types/app/bsky/getRepostedBy.d.ts +0 -29
  117. package/dist/src/client/types/app/bsky/getTimeline.d.ts +0 -56
  118. package/dist/src/client/types/app/bsky/getUserFollowers.d.ts +0 -31
  119. package/dist/src/client/types/app/bsky/getUserFollows.d.ts +0 -31
  120. package/dist/src/client/types/app/bsky/getUsersSearch.d.ts +0 -26
  121. package/dist/src/client/types/app/bsky/getUsersTypeahead.d.ts +0 -22
  122. package/dist/src/client/types/app/bsky/graph/assertCreator.d.ts +0 -1
  123. package/dist/src/client/types/app/bsky/graph/assertMember.d.ts +0 -1
  124. package/dist/src/client/types/app/bsky/graph/assertion.d.ts +0 -7
  125. package/dist/src/client/types/app/bsky/graph/confirmation.d.ts +0 -8
  126. package/dist/src/client/types/app/bsky/graph/follow.d.ts +0 -6
  127. package/dist/src/client/types/app/bsky/graph/getAssertions.d.ts +0 -43
  128. package/dist/src/client/types/app/bsky/graph/getFollowers.d.ts +0 -34
  129. package/dist/src/client/types/app/bsky/graph/getFollows.d.ts +0 -33
  130. package/dist/src/client/types/app/bsky/graph/getMembers.d.ts +0 -33
  131. package/dist/src/client/types/app/bsky/graph/getMemberships.d.ts +0 -33
  132. package/dist/src/client/types/app/bsky/invite.d.ts +0 -10
  133. package/dist/src/client/types/app/bsky/inviteAccept.d.ts +0 -14
  134. package/dist/src/client/types/app/bsky/like.d.ts +0 -10
  135. package/dist/src/client/types/app/bsky/mediaEmbed.d.ts +0 -15
  136. package/dist/src/client/types/app/bsky/notification/getCount.d.ts +0 -17
  137. package/dist/src/client/types/app/bsky/notification/list.d.ts +0 -32
  138. package/dist/src/client/types/app/bsky/post.d.ts +0 -23
  139. package/dist/src/client/types/app/bsky/postNotificationsSeen.d.ts +0 -20
  140. package/dist/src/client/types/app/bsky/profile.d.ts +0 -5
  141. package/dist/src/client/types/app/bsky/repost.d.ts +0 -10
  142. package/dist/src/client/types/app/bsky/system/actorScene.d.ts +0 -1
  143. package/dist/src/client/types/app/bsky/system/actorUser.d.ts +0 -1
  144. package/dist/src/client/types/app/bsky/system/declRef.d.ts +0 -5
  145. package/dist/src/client/types/app/bsky/system/declaration.d.ts +0 -4
  146. package/dist/src/client/types/app/bsky/updateProfile.d.ts +0 -23
  147. package/dist/src/client/types/com/atproto/account/create.d.ts +0 -41
  148. package/dist/src/client/types/com/atproto/account/createInviteCode.d.ts +0 -22
  149. package/dist/src/client/types/com/atproto/account/delete.d.ts +0 -13
  150. package/dist/src/client/types/com/atproto/account/get.d.ts +0 -12
  151. package/dist/src/client/types/com/atproto/account/requestPasswordReset.d.ts +0 -17
  152. package/dist/src/client/types/com/atproto/account/resetPassword.d.ts +0 -24
  153. package/dist/src/client/types/com/atproto/blob/upload.d.ts +0 -19
  154. package/dist/src/client/types/com/atproto/createAccount.d.ts +0 -40
  155. package/dist/src/client/types/com/atproto/createInviteCode.d.ts +0 -20
  156. package/dist/src/client/types/com/atproto/createSession.d.ts +0 -24
  157. package/dist/src/client/types/com/atproto/data/uploadFile.d.ts +0 -19
  158. package/dist/src/client/types/com/atproto/deleteAccount.d.ts +0 -20
  159. package/dist/src/client/types/com/atproto/deleteSession.d.ts +0 -17
  160. package/dist/src/client/types/com/atproto/getAccount.d.ts +0 -16
  161. package/dist/src/client/types/com/atproto/getAccountsConfig.d.ts +0 -17
  162. package/dist/src/client/types/com/atproto/getSession.d.ts +0 -17
  163. package/dist/src/client/types/com/atproto/handle/resolve.d.ts +0 -18
  164. package/dist/src/client/types/com/atproto/refreshSession.d.ts +0 -20
  165. package/dist/src/client/types/com/atproto/repo/batchWrite.d.ts +0 -39
  166. package/dist/src/client/types/com/atproto/repo/createRecord.d.ts +0 -26
  167. package/dist/src/client/types/com/atproto/repo/deleteRecord.d.ts +0 -19
  168. package/dist/src/client/types/com/atproto/repo/describe.d.ts +0 -22
  169. package/dist/src/client/types/com/atproto/repo/getRecord.d.ts +0 -23
  170. package/dist/src/client/types/com/atproto/repo/listRecords.d.ts +0 -30
  171. package/dist/src/client/types/com/atproto/repo/putRecord.d.ts +0 -27
  172. package/dist/src/client/types/com/atproto/repo/strongRef.d.ts +0 -5
  173. package/dist/src/client/types/com/atproto/repoBatchWrite.d.ts +0 -36
  174. package/dist/src/client/types/com/atproto/repoCreateRecord.d.ts +0 -24
  175. package/dist/src/client/types/com/atproto/repoDeleteRecord.d.ts +0 -18
  176. package/dist/src/client/types/com/atproto/repoDescribe.d.ts +0 -21
  177. package/dist/src/client/types/com/atproto/repoGetRecord.d.ts +0 -22
  178. package/dist/src/client/types/com/atproto/repoListRecords.d.ts +0 -27
  179. package/dist/src/client/types/com/atproto/repoPutRecord.d.ts +0 -25
  180. package/dist/src/client/types/com/atproto/requestAccountPasswordReset.d.ts +0 -19
  181. package/dist/src/client/types/com/atproto/resetAccountPassword.d.ts +0 -26
  182. package/dist/src/client/types/com/atproto/resolveHandle.d.ts +0 -17
  183. package/dist/src/client/types/com/atproto/resolveName.d.ts +0 -17
  184. package/dist/src/client/types/com/atproto/server/getAccountsConfig.d.ts +0 -24
  185. package/dist/src/client/types/com/atproto/session/create.d.ts +0 -26
  186. package/dist/src/client/types/com/atproto/session/delete.d.ts +0 -13
  187. package/dist/src/client/types/com/atproto/session/get.d.ts +0 -18
  188. package/dist/src/client/types/com/atproto/session/refresh.d.ts +0 -21
  189. package/dist/src/client/types/com/atproto/sync/getRepo.d.ts +0 -15
  190. package/dist/src/client/types/com/atproto/sync/getRoot.d.ts +0 -18
  191. package/dist/src/client/types/com/atproto/sync/updateRepo.d.ts +0 -15
  192. package/dist/src/client/types/com/atproto/syncGetRepo.d.ts +0 -15
  193. package/dist/src/client/types/com/atproto/syncGetRoot.d.ts +0 -17
  194. package/dist/src/client/types/com/atproto/syncUpdateRepo.d.ts +0 -15
  195. package/dist/src/index.d.ts +0 -4
  196. package/dist/src/schemas.d.ts +0 -19
  197. package/dist/src/session.d.ts +0 -42
  198. package/dist/src/types/app/bsky/acceptedBadge.d.ts +0 -10
  199. package/dist/src/types/app/bsky/badge.d.ts +0 -22
  200. package/dist/src/types/app/bsky/badgeAccept.d.ts +0 -11
  201. package/dist/src/types/app/bsky/badgeOffer.d.ts +0 -11
  202. package/dist/src/types/app/bsky/declaration.d.ts +0 -6
  203. package/dist/src/types/app/bsky/follow.d.ts +0 -9
  204. package/dist/src/types/app/bsky/getAuthorFeed.d.ts +0 -56
  205. package/dist/src/types/app/bsky/getBadgeMembers.d.ts +0 -29
  206. package/dist/src/types/app/bsky/getHomeFeed.d.ts +0 -56
  207. package/dist/src/types/app/bsky/getLikedBy.d.ts +0 -29
  208. package/dist/src/types/app/bsky/getNotificationCount.d.ts +0 -16
  209. package/dist/src/types/app/bsky/getNotifications.d.ts +0 -33
  210. package/dist/src/types/app/bsky/getPostThread.d.ts +0 -55
  211. package/dist/src/types/app/bsky/getProfile.d.ts +0 -42
  212. package/dist/src/types/app/bsky/getRepostedBy.d.ts +0 -29
  213. package/dist/src/types/app/bsky/getUserFollowers.d.ts +0 -31
  214. package/dist/src/types/app/bsky/getUserFollows.d.ts +0 -31
  215. package/dist/src/types/app/bsky/getUsersSearch.d.ts +0 -26
  216. package/dist/src/types/app/bsky/getUsersTypeahead.d.ts +0 -22
  217. package/dist/src/types/app/bsky/invite.d.ts +0 -10
  218. package/dist/src/types/app/bsky/inviteAccept.d.ts +0 -14
  219. package/dist/src/types/app/bsky/like.d.ts +0 -10
  220. package/dist/src/types/app/bsky/mediaEmbed.d.ts +0 -15
  221. package/dist/src/types/app/bsky/post.d.ts +0 -23
  222. package/dist/src/types/app/bsky/postNotificationsSeen.d.ts +0 -19
  223. package/dist/src/types/app/bsky/profile.d.ts +0 -11
  224. package/dist/src/types/app/bsky/repost.d.ts +0 -10
  225. package/dist/src/types/app/bsky/updateProfile.d.ts +0 -27
  226. package/dist/src/types/com/atproto/createAccount.d.ts +0 -39
  227. package/dist/src/types/com/atproto/createInviteCode.d.ts +0 -19
  228. package/dist/src/types/com/atproto/createSession.d.ts +0 -23
  229. package/dist/src/types/com/atproto/deleteAccount.d.ts +0 -19
  230. package/dist/src/types/com/atproto/deleteSession.d.ts +0 -16
  231. package/dist/src/types/com/atproto/getAccount.d.ts +0 -19
  232. package/dist/src/types/com/atproto/getAccountsConfig.d.ts +0 -17
  233. package/dist/src/types/com/atproto/getSession.d.ts +0 -17
  234. package/dist/src/types/com/atproto/refreshSession.d.ts +0 -19
  235. package/dist/src/types/com/atproto/repoBatchWrite.d.ts +0 -35
  236. package/dist/src/types/com/atproto/repoCreateRecord.d.ts +0 -23
  237. package/dist/src/types/com/atproto/repoDeleteRecord.d.ts +0 -15
  238. package/dist/src/types/com/atproto/repoDescribe.d.ts +0 -21
  239. package/dist/src/types/com/atproto/repoGetRecord.d.ts +0 -22
  240. package/dist/src/types/com/atproto/repoListRecords.d.ts +0 -27
  241. package/dist/src/types/com/atproto/repoPutRecord.d.ts +0 -24
  242. package/dist/src/types/com/atproto/requestAccountPasswordReset.d.ts +0 -18
  243. package/dist/src/types/com/atproto/resetAccountPassword.d.ts +0 -25
  244. package/dist/src/types/com/atproto/resolveName.d.ts +0 -17
  245. package/dist/src/types/com/atproto/syncGetRepo.d.ts +0 -15
  246. package/dist/src/types/com/atproto/syncGetRoot.d.ts +0 -17
  247. package/dist/src/types/com/atproto/syncUpdateRepo.d.ts +0 -14
  248. package/dist/src/types/todo/adx/createAccount.d.ts +0 -36
  249. package/dist/src/types/todo/adx/createInviteCode.d.ts +0 -19
  250. package/dist/src/types/todo/adx/createSession.d.ts +0 -22
  251. package/dist/src/types/todo/adx/deleteAccount.d.ts +0 -19
  252. package/dist/src/types/todo/adx/deleteSession.d.ts +0 -19
  253. package/dist/src/types/todo/adx/getAccount.d.ts +0 -19
  254. package/dist/src/types/todo/adx/getAccountsConfig.d.ts +0 -17
  255. package/dist/src/types/todo/adx/getSession.d.ts +0 -17
  256. package/dist/src/types/todo/adx/repoBatchWrite.d.ts +0 -34
  257. package/dist/src/types/todo/adx/repoCreateRecord.d.ts +0 -22
  258. package/dist/src/types/todo/adx/repoDeleteRecord.d.ts +0 -15
  259. package/dist/src/types/todo/adx/repoDescribe.d.ts +0 -21
  260. package/dist/src/types/todo/adx/repoGetRecord.d.ts +0 -20
  261. package/dist/src/types/todo/adx/repoListRecords.d.ts +0 -25
  262. package/dist/src/types/todo/adx/repoPutRecord.d.ts +0 -23
  263. package/dist/src/types/todo/adx/requestAccountPasswordReset.d.ts +0 -18
  264. package/dist/src/types/todo/adx/resetAccountPassword.d.ts +0 -25
  265. package/dist/src/types/todo/adx/resolveName.d.ts +0 -17
  266. package/dist/src/types/todo/adx/syncGetRepo.d.ts +0 -15
  267. package/dist/src/types/todo/adx/syncGetRoot.d.ts +0 -17
  268. package/dist/src/types/todo/adx/syncUpdateRepo.d.ts +0 -14
  269. package/dist/src/types/todo/social/badge.d.ts +0 -23
  270. package/dist/src/types/todo/social/follow.d.ts +0 -5
  271. package/dist/src/types/todo/social/getAuthorFeed.d.ts +0 -55
  272. package/dist/src/types/todo/social/getFeed.d.ts +0 -55
  273. package/dist/src/types/todo/social/getHomeFeed.d.ts +0 -55
  274. package/dist/src/types/todo/social/getLikedBy.d.ts +0 -26
  275. package/dist/src/types/todo/social/getNotificationCount.d.ts +0 -16
  276. package/dist/src/types/todo/social/getNotifications.d.ts +0 -31
  277. package/dist/src/types/todo/social/getPostThread.d.ts +0 -54
  278. package/dist/src/types/todo/social/getProfile.d.ts +0 -40
  279. package/dist/src/types/todo/social/getRepostedBy.d.ts +0 -26
  280. package/dist/src/types/todo/social/getUserFollowers.d.ts +0 -30
  281. package/dist/src/types/todo/social/getUserFollows.d.ts +0 -30
  282. package/dist/src/types/todo/social/like.d.ts +0 -5
  283. package/dist/src/types/todo/social/mediaEmbed.d.ts +0 -15
  284. package/dist/src/types/todo/social/post.d.ts +0 -18
  285. package/dist/src/types/todo/social/postNotificationsSeen.d.ts +0 -19
  286. package/dist/src/types/todo/social/profile.d.ts +0 -10
  287. package/dist/src/types/todo/social/repost.d.ts +0 -5
  288. package/dist/tsconfig.build.tsbuildinfo +0 -1
  289. package/tests/_util.ts +0 -26
  290. package/tsconfig.build.tsbuildinfo +0 -1
@@ -0,0 +1,98 @@
1
+ import {
2
+ AppBskyEmbedRecord,
3
+ AppBskyEmbedRecordWithMedia,
4
+ AppBskyFeedPost,
5
+ } from '../client'
6
+ import { ModerationDecision, ModerationUI } from './types'
7
+
8
+ export function takeHighestPriorityDecision(
9
+ ...decisions: (ModerationDecision | undefined)[]
10
+ ): ModerationDecision {
11
+ // remove undefined decisions
12
+ const filtered = decisions.filter((d) => !!d) as ModerationDecision[]
13
+ if (filtered.length === 0) {
14
+ return ModerationDecision.noop()
15
+ }
16
+
17
+ // sort by highest priority
18
+ filtered.sort((a, b) => {
19
+ if (a.cause && b.cause) {
20
+ return a.cause.priority - b.cause.priority
21
+ }
22
+ if (a.cause) {
23
+ return -1
24
+ }
25
+ if (b.cause) {
26
+ return 1
27
+ }
28
+ return 0
29
+ })
30
+
31
+ // use the top priority
32
+ return filtered[0]
33
+ }
34
+
35
+ export function downgradeDecision(
36
+ decision: ModerationDecision,
37
+ { alert }: { alert: boolean },
38
+ ) {
39
+ decision.blur = false
40
+ decision.blurMedia = false
41
+ decision.filter = false
42
+ decision.noOverride = false
43
+ decision.alert = alert
44
+ if (!alert) {
45
+ delete decision.cause
46
+ }
47
+ }
48
+
49
+ export function isModerationDecisionNoop(
50
+ decision: ModerationDecision | undefined,
51
+ { ignoreFilter }: { ignoreFilter: boolean } = { ignoreFilter: false },
52
+ ): boolean {
53
+ if (!decision) {
54
+ return true
55
+ }
56
+ if (decision.alert) {
57
+ return false
58
+ }
59
+ if (decision.blur) {
60
+ return false
61
+ }
62
+ if (decision.filter && !ignoreFilter) {
63
+ return false
64
+ }
65
+ return true
66
+ }
67
+
68
+ export function isQuotedPost(embed: unknown): embed is AppBskyEmbedRecord.View {
69
+ return Boolean(
70
+ embed &&
71
+ AppBskyEmbedRecord.isView(embed) &&
72
+ AppBskyEmbedRecord.isViewRecord(embed.record) &&
73
+ AppBskyFeedPost.isRecord(embed.record.value) &&
74
+ AppBskyFeedPost.validateRecord(embed.record.value).success,
75
+ )
76
+ }
77
+
78
+ export function isQuotedPostWithMedia(
79
+ embed: unknown,
80
+ ): embed is AppBskyEmbedRecordWithMedia.View {
81
+ return Boolean(
82
+ embed &&
83
+ AppBskyEmbedRecordWithMedia.isView(embed) &&
84
+ AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
85
+ AppBskyFeedPost.isRecord(embed.record.record.value) &&
86
+ AppBskyFeedPost.validateRecord(embed.record.record.value).success,
87
+ )
88
+ }
89
+
90
+ export function toModerationUI(decision: ModerationDecision): ModerationUI {
91
+ return {
92
+ cause: decision.cause,
93
+ filter: decision.filter,
94
+ blur: decision.blur,
95
+ alert: decision.alert,
96
+ noOverride: decision.noOverride,
97
+ }
98
+ }
@@ -0,0 +1,46 @@
1
+ import { moderatePost } from '../src'
2
+ import type {
3
+ ModerationBehaviors,
4
+ ModerationBehaviorScenario,
5
+ } from '../definitions/moderation-behaviors'
6
+ import { ModerationBehaviorSuiteRunner } from './util/moderation-behavior'
7
+ import { readFileSync } from 'fs'
8
+ import { join } from 'path'
9
+
10
+ const suite: ModerationBehaviors = JSON.parse(
11
+ readFileSync(
12
+ join(__dirname, '..', 'definitions', 'post-moderation-behaviors.json'),
13
+ 'utf8',
14
+ ),
15
+ )
16
+
17
+ const suiteRunner = new ModerationBehaviorSuiteRunner(suite)
18
+
19
+ describe('Post moderation behaviors', () => {
20
+ const scenarios = Array.from(Object.entries(suite.scenarios))
21
+ it.each(scenarios)(
22
+ '%s',
23
+ (_name: string, scenario: ModerationBehaviorScenario) => {
24
+ const res = moderatePost(
25
+ suiteRunner.postScenario(scenario),
26
+ suiteRunner.moderationOpts(scenario),
27
+ )
28
+ expect(res.content).toBeModerationResult(
29
+ scenario.behaviors.content,
30
+ 'post content',
31
+ JSON.stringify(res, null, 2),
32
+ )
33
+ expect(res.avatar).toBeModerationResult(
34
+ scenario.behaviors.avatar,
35
+ 'post avatar',
36
+ JSON.stringify(res, null, 2),
37
+ true,
38
+ )
39
+ expect(res.embed).toBeModerationResult(
40
+ scenario.behaviors.embed,
41
+ 'post embed',
42
+ JSON.stringify(res, null, 2),
43
+ )
44
+ },
45
+ )
46
+ })
@@ -0,0 +1,46 @@
1
+ import { moderateProfile } from '../src'
2
+ import type {
3
+ ModerationBehaviors,
4
+ ModerationBehaviorScenario,
5
+ } from '../definitions/moderation-behaviors'
6
+ import { ModerationBehaviorSuiteRunner } from './util/moderation-behavior'
7
+ import { readFileSync } from 'fs'
8
+ import { join } from 'path'
9
+
10
+ const suite: ModerationBehaviors = JSON.parse(
11
+ readFileSync(
12
+ join(__dirname, '..', 'definitions', 'profile-moderation-behaviors.json'),
13
+ 'utf8',
14
+ ),
15
+ )
16
+
17
+ const suiteRunner = new ModerationBehaviorSuiteRunner(suite)
18
+
19
+ describe('Post moderation behaviors', () => {
20
+ const scenarios = Array.from(Object.entries(suite.scenarios))
21
+ it.each(scenarios)(
22
+ '%s',
23
+ (_name: string, scenario: ModerationBehaviorScenario) => {
24
+ const res = moderateProfile(
25
+ suiteRunner.profileScenario(scenario),
26
+ suiteRunner.moderationOpts(scenario),
27
+ )
28
+ expect(res.account).toBeModerationResult(
29
+ scenario.behaviors.account,
30
+ 'account',
31
+ JSON.stringify(res, null, 2),
32
+ )
33
+ expect(res.profile).toBeModerationResult(
34
+ scenario.behaviors.profile,
35
+ 'profile content',
36
+ JSON.stringify(res, null, 2),
37
+ )
38
+ expect(res.avatar).toBeModerationResult(
39
+ scenario.behaviors.avatar,
40
+ 'profile avatar',
41
+ JSON.stringify(res, null, 2),
42
+ true,
43
+ )
44
+ },
45
+ )
46
+ })
@@ -0,0 +1,176 @@
1
+ import {
2
+ AtpAgentFetchHandlerResponse,
3
+ ComAtprotoLabelDefs,
4
+ AppBskyFeedDefs,
5
+ AppBskyActorDefs,
6
+ AppBskyFeedPost,
7
+ AppBskyEmbedRecord,
8
+ AppBskyGraphDefs,
9
+ } from '../../src'
10
+
11
+ export async function fetchHandler(
12
+ httpUri: string,
13
+ httpMethod: string,
14
+ httpHeaders: Record<string, string>,
15
+ httpReqBody: unknown,
16
+ ): Promise<AtpAgentFetchHandlerResponse> {
17
+ // The duplex field is now required for streaming bodies, but not yet reflected
18
+ // anywhere in docs or types. See whatwg/fetch#1438, nodejs/node#46221.
19
+ const reqInit: RequestInit & { duplex: string } = {
20
+ method: httpMethod,
21
+ headers: httpHeaders,
22
+ body: httpReqBody
23
+ ? new TextEncoder().encode(JSON.stringify(httpReqBody))
24
+ : undefined,
25
+ duplex: 'half',
26
+ }
27
+ const res = await fetch(httpUri, reqInit)
28
+ const resBody = await res.arrayBuffer()
29
+ return {
30
+ status: res.status,
31
+ headers: Object.fromEntries(res.headers.entries()),
32
+ body: resBody ? JSON.parse(new TextDecoder().decode(resBody)) : undefined,
33
+ }
34
+ }
35
+
36
+ export const mock = {
37
+ post({
38
+ text,
39
+ reply,
40
+ embed,
41
+ }: {
42
+ text: string
43
+ reply?: AppBskyFeedPost.ReplyRef
44
+ embed?: AppBskyFeedPost.Record['embed']
45
+ }): AppBskyFeedPost.Record {
46
+ return {
47
+ $type: 'app.bsky.feed.post',
48
+ text,
49
+ reply,
50
+ embed,
51
+ langs: ['en'],
52
+ createdAt: new Date().toISOString(),
53
+ }
54
+ },
55
+
56
+ postView({
57
+ record,
58
+ author,
59
+ embed,
60
+ replyCount,
61
+ repostCount,
62
+ likeCount,
63
+ viewer,
64
+ labels,
65
+ }: {
66
+ record: AppBskyFeedPost.Record
67
+ author: AppBskyActorDefs.ProfileViewBasic
68
+ embed?: AppBskyFeedDefs.PostView['embed']
69
+ replyCount?: number
70
+ repostCount?: number
71
+ likeCount?: number
72
+ viewer?: AppBskyFeedDefs.ViewerState
73
+ labels?: ComAtprotoLabelDefs.Label[]
74
+ }): AppBskyFeedDefs.PostView {
75
+ return {
76
+ uri: `at://${author.did}/app.bsky.post/fake`,
77
+ cid: 'fake',
78
+ author,
79
+ record,
80
+ embed,
81
+ replyCount,
82
+ repostCount,
83
+ likeCount,
84
+ indexedAt: new Date().toISOString(),
85
+ viewer,
86
+ labels,
87
+ }
88
+ },
89
+
90
+ embedRecordView({
91
+ record,
92
+ author,
93
+ labels,
94
+ }: {
95
+ record: AppBskyFeedPost.Record
96
+ author: AppBskyActorDefs.ProfileViewBasic
97
+ labels?: ComAtprotoLabelDefs.Label[]
98
+ }): AppBskyEmbedRecord.View {
99
+ return {
100
+ $type: 'app.bsky.embed.record#view',
101
+ record: {
102
+ $type: 'app.bsky.embed.record#viewRecord',
103
+ uri: `at://${author.did}/app.bsky.post/fake`,
104
+ cid: 'fake',
105
+ author,
106
+ value: record,
107
+ labels,
108
+ indexedAt: new Date().toISOString(),
109
+ },
110
+ }
111
+ },
112
+
113
+ profileViewBasic({
114
+ handle,
115
+ displayName,
116
+ viewer,
117
+ labels,
118
+ }: {
119
+ handle: string
120
+ displayName?: string
121
+ viewer?: AppBskyActorDefs.ViewerState
122
+ labels?: ComAtprotoLabelDefs.Label[]
123
+ }): AppBskyActorDefs.ProfileViewBasic {
124
+ return {
125
+ did: `did:web:${handle}`,
126
+ handle,
127
+ displayName,
128
+ viewer,
129
+ labels,
130
+ }
131
+ },
132
+
133
+ actorViewerState({
134
+ muted,
135
+ mutedByList,
136
+ blockedBy,
137
+ blocking,
138
+ following,
139
+ followedBy,
140
+ }: {
141
+ muted?: boolean
142
+ mutedByList?: AppBskyGraphDefs.ListViewBasic
143
+ blockedBy?: boolean
144
+ blocking?: string
145
+ following?: string
146
+ followedBy?: string
147
+ }): AppBskyActorDefs.ViewerState {
148
+ return {
149
+ muted,
150
+ mutedByList,
151
+ blockedBy,
152
+ blocking,
153
+ following,
154
+ followedBy,
155
+ }
156
+ },
157
+
158
+ listViewBasic({ name }: { name: string }): AppBskyGraphDefs.ListViewBasic {
159
+ return {
160
+ uri: 'at://did:plc:fake/app.bsky.graph.list/fake',
161
+ cid: 'fake',
162
+ name,
163
+ purpose: 'app.bsky.graph.defs#modlist',
164
+ indexedAt: new Date().toISOString(),
165
+ }
166
+ },
167
+
168
+ label({ val, uri }: { val: string; uri: string }): ComAtprotoLabelDefs.Label {
169
+ return {
170
+ src: 'did:plc:fake-labeler',
171
+ uri,
172
+ val,
173
+ cts: new Date().toISOString(),
174
+ }
175
+ },
176
+ }
@@ -0,0 +1,180 @@
1
+ import { ModerationUI, ModerationOpts, ComAtprotoLabelDefs } from '../../src'
2
+ import type {
3
+ ModerationBehaviors,
4
+ ModerationBehaviorScenario,
5
+ ModerationBehaviorResult,
6
+ } from '../../definitions/moderation-behaviors'
7
+ import { mock as m } from './index'
8
+
9
+ expect.extend({
10
+ toBeModerationResult(
11
+ actual: ModerationUI,
12
+ expected: ModerationBehaviorResult | undefined,
13
+ context: string,
14
+ stringifiedResult: string,
15
+ ignoreCause = false,
16
+ ) {
17
+ const fail = (msg: string) => ({
18
+ pass: false,
19
+ message: () => `${msg}. Full result: ${stringifiedResult}`,
20
+ })
21
+ let cause = actual.cause?.type as string
22
+ if (actual.cause?.type === 'label') {
23
+ cause = `label:${actual.cause.labelDef.id}`
24
+ } else if (actual.cause?.type === 'muted') {
25
+ if (actual.cause.source.type === 'list') {
26
+ cause = 'muted-by-list'
27
+ }
28
+ }
29
+ if (!expected) {
30
+ if (!ignoreCause && actual.cause) {
31
+ return fail(`${context} expected to be a no-op, got ${cause}`)
32
+ }
33
+ if (actual.alert) {
34
+ return fail(`${context} expected to be a no-op, got alert=true`)
35
+ }
36
+ if (actual.blur) {
37
+ return fail(`${context} expected to be a no-op, got blur=true`)
38
+ }
39
+ if (actual.filter) {
40
+ return fail(`${context} expected to be a no-op, got filter=true`)
41
+ }
42
+ if (actual.noOverride) {
43
+ return fail(`${context} expected to be a no-op, got noOverride=true`)
44
+ }
45
+ } else {
46
+ if (!ignoreCause && cause !== expected.cause) {
47
+ return fail(`${context} expected to be ${expected.cause}, got ${cause}`)
48
+ }
49
+ if (!!actual.alert !== !!expected.alert) {
50
+ return fail(
51
+ `${context} expected to be alert=${expected.alert || false}, got ${
52
+ actual.alert || false
53
+ }`,
54
+ )
55
+ }
56
+ if (!!actual.blur !== !!expected.blur) {
57
+ return fail(
58
+ `${context} expected to be blur=${expected.blur || false}, got ${
59
+ actual.blur || false
60
+ }`,
61
+ )
62
+ }
63
+ if (!!actual.filter !== !!expected.filter) {
64
+ return fail(
65
+ `${context} expected to be filter=${expected.filter || false}, got ${
66
+ actual.filter || false
67
+ }`,
68
+ )
69
+ }
70
+ if (!!actual.noOverride !== !!expected.noOverride) {
71
+ return fail(
72
+ `${context} expected to be noOverride=${
73
+ expected.noOverride || false
74
+ }, got ${actual.noOverride || false}`,
75
+ )
76
+ }
77
+ }
78
+ return { pass: true, message: () => '' }
79
+ },
80
+ })
81
+
82
+ export class ModerationBehaviorSuiteRunner {
83
+ constructor(public suite: ModerationBehaviors) {}
84
+
85
+ postScenario(scenario: ModerationBehaviorScenario) {
86
+ if (scenario.subject !== 'post') {
87
+ throw new Error('Scenario subject must be "post"')
88
+ }
89
+ const author = this.profileViewBasic(scenario.author, scenario.labels)
90
+ return m.postView({
91
+ record: m.post({
92
+ text: 'Post text',
93
+ }),
94
+ author,
95
+ labels: (scenario.labels.post || []).map((val) =>
96
+ m.label({ val, uri: `at://${author.did}/app.bsky.feed.post/fake` }),
97
+ ),
98
+ embed: scenario.quoteAuthor
99
+ ? m.embedRecordView({
100
+ record: m.post({
101
+ text: 'Quoted post text',
102
+ }),
103
+ labels: (scenario.labels.quotedPost || []).map((val) =>
104
+ m.label({
105
+ val,
106
+ uri: `at://${author.did}/app.bsky.feed.post/fake`,
107
+ }),
108
+ ),
109
+ author: this.profileViewBasic(scenario.quoteAuthor, {
110
+ account: scenario.labels.quotedAccount,
111
+ }),
112
+ })
113
+ : undefined,
114
+ })
115
+ }
116
+
117
+ profileScenario(scenario: ModerationBehaviorScenario) {
118
+ if (scenario.subject !== 'profile') {
119
+ throw new Error('Scenario subject must be "profile"')
120
+ }
121
+ return this.profileViewBasic(scenario.author, scenario.labels)
122
+ }
123
+
124
+ profileViewBasic(
125
+ name: string,
126
+ scenarioLabels: ModerationBehaviorScenario['labels'],
127
+ ) {
128
+ const def = this.suite.users[name]
129
+
130
+ const labels: ComAtprotoLabelDefs.Label[] = []
131
+ if (scenarioLabels.account) {
132
+ for (const l of scenarioLabels.account) {
133
+ labels.push(m.label({ val: l, uri: `did:web:${name}` }))
134
+ }
135
+ }
136
+ if (scenarioLabels.profile) {
137
+ for (const l of scenarioLabels.profile) {
138
+ labels.push(
139
+ m.label({
140
+ val: l,
141
+ uri: `at://did:web:${name}/app.bsky.actor.profile/self`,
142
+ }),
143
+ )
144
+ }
145
+ }
146
+
147
+ return m.profileViewBasic({
148
+ handle: `${name}.test`,
149
+ labels,
150
+ viewer: m.actorViewerState({
151
+ muted: def.muted || def.mutedByList,
152
+ mutedByList: def.mutedByList
153
+ ? m.listViewBasic({ name: 'Fake List' })
154
+ : undefined,
155
+ blockedBy: def.blockedBy,
156
+ blocking: def.blocking
157
+ ? 'at://did:web:self.test/app.bsky.graph.block/fake'
158
+ : undefined,
159
+ }),
160
+ })
161
+ }
162
+
163
+ moderationOpts(scenario: ModerationBehaviorScenario): ModerationOpts {
164
+ return {
165
+ userDid: 'did:web:self.test',
166
+ adultContentEnabled: Boolean(
167
+ this.suite.configurations[scenario.cfg].adultContentEnabled,
168
+ ),
169
+ labelerSettings: [
170
+ {
171
+ labeler: {
172
+ did: 'did:plc:fake-labeler',
173
+ displayName: 'Fake Labeler',
174
+ },
175
+ settings: this.suite.configurations[scenario.cfg].settings,
176
+ },
177
+ ],
178
+ }
179
+ }
180
+ }
@@ -1,22 +0,0 @@
1
- import { Headers } from '@atproto/xrpc';
2
- import * as AppBskyActorDefs from './defs';
3
- export interface QueryParams {
4
- term?: string;
5
- limit?: number;
6
- cursor?: string;
7
- }
8
- export declare type InputSchema = undefined;
9
- export interface OutputSchema {
10
- cursor?: string;
11
- actors: AppBskyActorDefs.ProfileViewBasic[];
12
- [k: string]: unknown;
13
- }
14
- export interface CallOptions {
15
- headers?: Headers;
16
- }
17
- export interface Response {
18
- success: boolean;
19
- headers: Headers;
20
- data: OutputSchema;
21
- }
22
- export declare function toKnownErr(e: any): any;
@@ -1,20 +0,0 @@
1
- import { Headers } from '@atproto/xrpc';
2
- import * as AppBskyActorDefs from './defs';
3
- export interface QueryParams {
4
- term?: string;
5
- limit?: number;
6
- }
7
- export declare type InputSchema = undefined;
8
- export interface OutputSchema {
9
- actors: AppBskyActorDefs.WithInfo[];
10
- [k: string]: unknown;
11
- }
12
- export interface CallOptions {
13
- headers?: Headers;
14
- }
15
- export interface Response {
16
- success: boolean;
17
- headers: Headers;
18
- data: OutputSchema;
19
- }
20
- export declare function toKnownErr(e: any): any;
@@ -1,29 +0,0 @@
1
- import { Headers } from '@atproto/xrpc';
2
- import { ValidationResult } from '@atproto/lexicon';
3
- export interface QueryParams {
4
- }
5
- export declare type InputSchema = undefined;
6
- export interface OutputSchema {
7
- total: CodesDetail;
8
- user: CodesDetail;
9
- admin: CodesDetail;
10
- [k: string]: unknown;
11
- }
12
- export interface CallOptions {
13
- headers?: Headers;
14
- }
15
- export interface Response {
16
- success: boolean;
17
- headers: Headers;
18
- data: OutputSchema;
19
- }
20
- export declare function toKnownErr(e: any): any;
21
- export interface CodesDetail {
22
- count: number;
23
- available: number;
24
- used: number;
25
- disabled: number;
26
- [k: string]: unknown;
27
- }
28
- export declare function isCodesDetail(v: unknown): v is CodesDetail;
29
- export declare function validateCodesDetail(v: unknown): ValidationResult;
@@ -1,19 +0,0 @@
1
- import { Headers } from '@atproto/xrpc';
2
- export interface QueryParams {
3
- }
4
- export declare type InputSchema = string | Uint8Array;
5
- export interface OutputSchema {
6
- head: string;
7
- [k: string]: unknown;
8
- }
9
- export interface CallOptions {
10
- headers?: Headers;
11
- qp?: QueryParams;
12
- encoding: 'application/vnd.ipld.car';
13
- }
14
- export interface Response {
15
- success: boolean;
16
- headers: Headers;
17
- data: OutputSchema;
18
- }
19
- export declare function toKnownErr(e: any): any;
@@ -1,19 +0,0 @@
1
- import { Headers } from '@atproto/xrpc';
2
- export interface QueryParams {
3
- }
4
- export declare type InputSchema = string | Uint8Array;
5
- export interface OutputSchema {
6
- head: string;
7
- [k: string]: unknown;
8
- }
9
- export interface CallOptions {
10
- headers?: Headers;
11
- qp?: QueryParams;
12
- encoding: 'application/vnd.ipld.car';
13
- }
14
- export interface Response {
15
- success: boolean;
16
- headers: Headers;
17
- data: OutputSchema;
18
- }
19
- export declare function toKnownErr(e: any): any;
@@ -1,31 +0,0 @@
1
- import { Headers, XRPCError } from '@atproto/xrpc';
2
- import { ValidationResult } from '@atproto/lexicon';
3
- export interface QueryParams {
4
- includeUsed?: boolean;
5
- createAvailable?: boolean;
6
- }
7
- export declare type InputSchema = undefined;
8
- export interface OutputSchema {
9
- codes: Invite[];
10
- [k: string]: unknown;
11
- }
12
- export interface CallOptions {
13
- headers?: Headers;
14
- }
15
- export interface Response {
16
- success: boolean;
17
- headers: Headers;
18
- data: OutputSchema;
19
- }
20
- export declare class DuplicateCreateError extends XRPCError {
21
- constructor(src: XRPCError);
22
- }
23
- export declare function toKnownErr(e: any): any;
24
- export interface Invite {
25
- code: string;
26
- available: number;
27
- uses: number;
28
- [k: string]: unknown;
29
- }
30
- export declare function isInvite(v: unknown): v is Invite;
31
- export declare function validateInvite(v: unknown): ValidationResult;