@atproto/api 0.20.22 → 0.20.25

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 (64) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/client/lexicons.d.ts +28 -12
  3. package/dist/client/lexicons.d.ts.map +1 -1
  4. package/dist/client/lexicons.js +14 -6
  5. package/dist/client/lexicons.js.map +1 -1
  6. package/dist/client/types/app/bsky/ageassurance/defs.d.ts +2 -0
  7. package/dist/client/types/app/bsky/ageassurance/defs.d.ts.map +1 -1
  8. package/dist/client/types/app/bsky/ageassurance/defs.js.map +1 -1
  9. package/package.json +22 -16
  10. package/definitions/labels.json +0 -170
  11. package/docs/moderation.md +0 -260
  12. package/jest.config.cjs +0 -23
  13. package/jest.d.ts +0 -20
  14. package/jest.setup.ts +0 -97
  15. package/scripts/code/labels.mjs +0 -77
  16. package/scripts/generate-code.mjs +0 -4
  17. package/src/age-assurance.test.ts +0 -218
  18. package/src/age-assurance.ts +0 -137
  19. package/src/agent.ts +0 -1645
  20. package/src/atp-agent.ts +0 -570
  21. package/src/bsky-agent.ts +0 -14
  22. package/src/const.ts +0 -1
  23. package/src/index.ts +0 -44
  24. package/src/mocker.ts +0 -220
  25. package/src/moderation/decision.ts +0 -389
  26. package/src/moderation/index.ts +0 -69
  27. package/src/moderation/mutewords.ts +0 -170
  28. package/src/moderation/subjects/account.ts +0 -44
  29. package/src/moderation/subjects/feed-generator.ts +0 -24
  30. package/src/moderation/subjects/notification.ts +0 -25
  31. package/src/moderation/subjects/post.ts +0 -433
  32. package/src/moderation/subjects/profile.ts +0 -26
  33. package/src/moderation/subjects/status.ts +0 -27
  34. package/src/moderation/subjects/user-list.ts +0 -48
  35. package/src/moderation/types.ts +0 -191
  36. package/src/moderation/ui.ts +0 -21
  37. package/src/moderation/util.ts +0 -111
  38. package/src/predicate.ts +0 -52
  39. package/src/rich-text/detection.ts +0 -146
  40. package/src/rich-text/rich-text.ts +0 -418
  41. package/src/rich-text/sanitization.ts +0 -42
  42. package/src/rich-text/unicode.ts +0 -47
  43. package/src/rich-text/util.ts +0 -15
  44. package/src/session-manager.ts +0 -5
  45. package/src/types.ts +0 -165
  46. package/src/util.ts +0 -96
  47. package/tests/atp-agent.test.ts +0 -3863
  48. package/tests/dispatcher.test.ts +0 -527
  49. package/tests/errors.test.ts +0 -29
  50. package/tests/moderation-behaviors.test.ts +0 -951
  51. package/tests/moderation-custom-labels.test.ts +0 -334
  52. package/tests/moderation-mutewords.test.ts +0 -1299
  53. package/tests/moderation-prefs.test.ts +0 -402
  54. package/tests/moderation-quoteposts.test.ts +0 -277
  55. package/tests/moderation.test.ts +0 -739
  56. package/tests/rich-text-detection.test.ts +0 -456
  57. package/tests/rich-text-sanitization.test.ts +0 -216
  58. package/tests/rich-text.test.ts +0 -705
  59. package/tests/util/echo-server.ts +0 -37
  60. package/tests/util/moderation-behavior.ts +0 -268
  61. package/tsconfig.build.json +0 -9
  62. package/tsconfig.build.tsbuildinfo +0 -1
  63. package/tsconfig.json +0 -7
  64. package/tsconfig.tests.json +0 -10
package/jest.setup.ts DELETED
@@ -1,97 +0,0 @@
1
- import { ModerationUI } from './src/index.js'
2
- import { ModerationTestSuiteResultFlag } from './tests/util/moderation-behavior.js'
3
-
4
- expect.extend({
5
- toBeModerationResult(
6
- actual: ModerationUI,
7
- expected: ModerationTestSuiteResultFlag[] | undefined,
8
- context = '',
9
- stringifiedResult: string | undefined = undefined,
10
- _ignoreCause = false,
11
- ) {
12
- const fail = (msg: string) => ({
13
- pass: false,
14
- message: () =>
15
- `${msg}.${
16
- stringifiedResult ? ` Full result: ${stringifiedResult}` : ''
17
- }`,
18
- })
19
- // let cause = actual.causes?.type as string
20
- // if (actual.cause?.type === 'label') {
21
- // cause = `label:${actual.cause.labelDef.id}`
22
- // } else if (actual.cause?.type === 'muted') {
23
- // if (actual.cause.source.type === 'list') {
24
- // cause = 'muted-by-list'
25
- // }
26
- // } else if (actual.cause?.type === 'blocking') {
27
- // if (actual.cause.source.type === 'list') {
28
- // cause = 'blocking-by-list'
29
- // }
30
- // }
31
- if (!expected) {
32
- // if (!ignoreCause && actual.cause) {
33
- // return fail(`${context} expected to be a no-op, got ${cause}`)
34
- // }
35
- if (actual.inform) {
36
- return fail(`${context} expected to be a no-op, got inform=true`)
37
- }
38
- if (actual.alert) {
39
- return fail(`${context} expected to be a no-op, got alert=true`)
40
- }
41
- if (actual.blur) {
42
- return fail(`${context} expected to be a no-op, got blur=true`)
43
- }
44
- if (actual.filter) {
45
- return fail(`${context} expected to be a no-op, got filter=true`)
46
- }
47
- if (actual.noOverride) {
48
- return fail(`${context} expected to be a no-op, got noOverride=true`)
49
- }
50
- } else {
51
- // if (!ignoreCause && cause !== expected.cause) {
52
- // return fail(`${context} expected to be ${expected.cause}, got ${cause}`)
53
- // }
54
- const expectedInform = expected.includes('inform')
55
- if (!!actual.inform !== expectedInform) {
56
- return fail(
57
- `${context} expected to be inform=${expectedInform}, got ${
58
- actual.inform || false
59
- }`,
60
- )
61
- }
62
- const expectedAlert = expected.includes('alert')
63
- if (!!actual.alert !== expectedAlert) {
64
- return fail(
65
- `${context} expected to be alert=${expectedAlert}, got ${
66
- actual.alert || false
67
- }`,
68
- )
69
- }
70
- const expectedBlur = expected.includes('blur')
71
- if (!!actual.blur !== expectedBlur) {
72
- return fail(
73
- `${context} expected to be blur=${expectedBlur}, got ${
74
- actual.blur || false
75
- }`,
76
- )
77
- }
78
- const expectedFilter = expected.includes('filter')
79
- if (!!actual.filter !== expectedFilter) {
80
- return fail(
81
- `${context} expected to be filter=${expectedFilter}, got ${
82
- actual.filter || false
83
- }`,
84
- )
85
- }
86
- const expectedNoOverride = expected.includes('noOverride')
87
- if (!!actual.noOverride !== expectedNoOverride) {
88
- return fail(
89
- `${context} expected to be noOverride=${expectedNoOverride}, got ${
90
- actual.noOverride || false
91
- }`,
92
- )
93
- }
94
- }
95
- return { pass: true, message: () => '' }
96
- },
97
- })
@@ -1,77 +0,0 @@
1
- import { mkdir, writeFile } from 'node:fs/promises'
2
- import { dirname, join } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
- import * as prettier from 'prettier'
5
- import labelsDef from '../../definitions/labels.json' with { type: 'json' }
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url))
8
-
9
- export async function labels() {
10
- const content = await gen()
11
-
12
- const path = join(
13
- __dirname,
14
- '..',
15
- '..',
16
- 'src',
17
- 'moderation',
18
- 'const',
19
- 'labels.ts',
20
- )
21
-
22
- await mkdir(dirname(path), { recursive: true })
23
- await writeFile(path, content, 'utf8')
24
- }
25
-
26
- async function gen() {
27
- const knownValues = new Set()
28
- const flattenedLabelDefs = []
29
-
30
- for (const { alias: aliases, ...label } of labelsDef) {
31
- knownValues.add(label.identifier)
32
- flattenedLabelDefs.push([label.identifier, { ...label, locales: [] }])
33
-
34
- if (aliases) {
35
- for (const alias of aliases) {
36
- knownValues.add(alias)
37
- flattenedLabelDefs.push([
38
- alias,
39
- {
40
- ...label,
41
- identifier: alias,
42
- locales: [],
43
- comment: `@deprecated alias for \`${label.identifier}\``,
44
- },
45
- ])
46
- }
47
- }
48
- }
49
-
50
- let labelDefsStr = `{`
51
- for (const [key, { comment, ...value }] of flattenedLabelDefs) {
52
- const commentStr = comment ? `\n/** ${comment} */\n` : ''
53
- labelDefsStr += `${commentStr}'${key}': ${JSON.stringify(value, null, 2)},`
54
- }
55
- labelDefsStr += `}`
56
-
57
- return prettier.format(
58
- `/** this doc is generated by ./scripts/code/labels.mjs **/
59
- import {InterpretedLabelValueDefinition, LabelPreference} from '../types.js'
60
-
61
- export type KnownLabelValue = ${Array.from(knownValues)
62
- .map((value) => `"${value}"`)
63
- .join(' | ')}
64
-
65
- export const DEFAULT_LABEL_SETTINGS: Record<string, LabelPreference> = ${JSON.stringify(
66
- Object.fromEntries(
67
- labelsDef
68
- .filter((label) => label.configurable)
69
- .map((label) => [label.identifier, label.defaultSetting]),
70
- ),
71
- )}
72
-
73
- export const LABELS: Record<KnownLabelValue, InterpretedLabelValueDefinition> = ${labelDefsStr}
74
- `,
75
- { semi: false, parser: 'typescript', singleQuote: true },
76
- )
77
- }
@@ -1,4 +0,0 @@
1
- import { labels } from './code/labels.mjs'
2
- export { labels }
3
-
4
- labels()
@@ -1,218 +0,0 @@
1
- import { describe, expect, it } from '@jest/globals'
2
- import {
3
- ageAssuranceRuleIDs,
4
- computeAgeAssuranceRegionAccess,
5
- getAgeAssuranceRegionConfig,
6
- } from './age-assurance.js'
7
- import { AppBskyAgeassuranceDefs } from './client/index.js'
8
-
9
- describe('age-assurance', () => {
10
- describe('getAgeAssuranceRegionConfig', () => {
11
- const config: AppBskyAgeassuranceDefs.Config = {
12
- regions: [
13
- {
14
- countryCode: 'US',
15
- regionCode: 'CA',
16
- minAccessAge: 13,
17
- rules: [],
18
- },
19
- {
20
- countryCode: 'US',
21
- minAccessAge: 13,
22
- rules: [],
23
- },
24
- ],
25
- }
26
-
27
- it('should find region by country code only', () => {
28
- const result = getAgeAssuranceRegionConfig(config, {
29
- countryCode: 'US',
30
- })
31
-
32
- expect(result).toEqual({
33
- countryCode: 'US',
34
- minAccessAge: 13,
35
- rules: [],
36
- })
37
- })
38
-
39
- it('should find region by country code and region code', () => {
40
- const result = getAgeAssuranceRegionConfig(config, {
41
- countryCode: 'US',
42
- regionCode: 'CA',
43
- })
44
-
45
- expect(result).toEqual({
46
- countryCode: 'US',
47
- regionCode: 'CA',
48
- minAccessAge: 13,
49
- rules: [],
50
- })
51
- })
52
-
53
- it('should return undefined when no matching region found', () => {
54
- const result = getAgeAssuranceRegionConfig(config, {
55
- countryCode: 'GB',
56
- })
57
-
58
- expect(result).toBeUndefined()
59
- })
60
- })
61
-
62
- describe('computeAgeAssuranceRegionAccess', () => {
63
- const region: AppBskyAgeassuranceDefs.ConfigRegion = {
64
- countryCode: 'US',
65
- minAccessAge: 13,
66
- rules: [
67
- {
68
- $type: ageAssuranceRuleIDs.IfAccountNewerThan,
69
- date: '2025-12-10T00:00:00Z',
70
- access: 'none',
71
- },
72
- {
73
- $type: ageAssuranceRuleIDs.IfAssuredOverAge,
74
- age: 18,
75
- access: 'full',
76
- },
77
- {
78
- $type: ageAssuranceRuleIDs.IfAssuredOverAge,
79
- age: 16,
80
- access: 'safe',
81
- },
82
- {
83
- $type: ageAssuranceRuleIDs.IfDeclaredOverAge,
84
- age: 16,
85
- access: 'safe',
86
- },
87
- {
88
- $type: ageAssuranceRuleIDs.Default,
89
- access: 'none',
90
- },
91
- ],
92
- }
93
-
94
- it('should apply default if no data provided', () => {
95
- const result = computeAgeAssuranceRegionAccess(region, {})
96
-
97
- expect(result).toEqual({
98
- access: 'none',
99
- reason: ageAssuranceRuleIDs.Default,
100
- })
101
- })
102
-
103
- describe('IfAccountNewerThan', () => {
104
- it('should block accounts created after threshold', () => {
105
- const result = computeAgeAssuranceRegionAccess(region, {
106
- accountCreatedAt: new Date(2025, 11, 15).toISOString(),
107
- declaredAge: 18,
108
- })
109
- expect(result).toEqual({
110
- access: 'none',
111
- reason: ageAssuranceRuleIDs.IfAccountNewerThan,
112
- })
113
- })
114
-
115
- it('should allow accounts created before threshold', () => {
116
- const result = computeAgeAssuranceRegionAccess(region, {
117
- accountCreatedAt: new Date(2025, 10, 1).toISOString(),
118
- declaredAge: 18,
119
- })
120
- expect(result).toEqual({
121
- access: 'safe',
122
- reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
123
- })
124
- })
125
-
126
- it('should allow accounts created exactly at threshold', () => {
127
- const result = computeAgeAssuranceRegionAccess(region, {
128
- accountCreatedAt: new Date(2025, 11, 1).toISOString(),
129
- declaredAge: 18,
130
- })
131
- expect(result).toEqual({
132
- access: 'safe',
133
- reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
134
- })
135
- })
136
-
137
- it('should not apply rule when accountCreatedAt is not provided', () => {
138
- const result = computeAgeAssuranceRegionAccess(region, {
139
- declaredAge: 15,
140
- })
141
- expect(result).toEqual({
142
- access: 'none',
143
- reason: ageAssuranceRuleIDs.Default,
144
- })
145
- })
146
-
147
- it('should not apply rule when assuredAge is present', () => {
148
- const result = computeAgeAssuranceRegionAccess(region, {
149
- accountCreatedAt: new Date(2025, 11, 15).toISOString(),
150
- assuredAge: 20,
151
- })
152
- expect(result).toEqual({
153
- access: 'full',
154
- reason: ageAssuranceRuleIDs.IfAssuredOverAge,
155
- })
156
- })
157
- })
158
-
159
- describe('IfDeclaredOverAge rule', () => {
160
- it('should allow users at or above age threshold', () => {
161
- const result = computeAgeAssuranceRegionAccess(region, {
162
- declaredAge: 18,
163
- })
164
-
165
- expect(result).toEqual({
166
- access: 'safe',
167
- reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
168
- })
169
- })
170
-
171
- it('should allow users above age threshold', () => {
172
- const result = computeAgeAssuranceRegionAccess(region, {
173
- declaredAge: 25,
174
- })
175
-
176
- expect(result).toEqual({
177
- access: 'safe',
178
- reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
179
- })
180
- })
181
-
182
- it('should not allow users below age threshold', () => {
183
- const result = computeAgeAssuranceRegionAccess(region, {
184
- declaredAge: 17,
185
- })
186
-
187
- expect(result).toEqual({
188
- access: 'safe',
189
- reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
190
- })
191
- })
192
- })
193
-
194
- describe('IfAssuredOverAge rule', () => {
195
- it('should allow users at or above assured age threshold', () => {
196
- const result = computeAgeAssuranceRegionAccess(region, {
197
- assuredAge: 18,
198
- })
199
-
200
- expect(result).toEqual({
201
- access: 'full',
202
- reason: ageAssuranceRuleIDs.IfAssuredOverAge,
203
- })
204
- })
205
-
206
- it('should not allow users below assured age threshold', () => {
207
- const result = computeAgeAssuranceRegionAccess(region, {
208
- assuredAge: 17,
209
- })
210
-
211
- expect(result).toEqual({
212
- access: 'safe',
213
- reason: ageAssuranceRuleIDs.IfAssuredOverAge,
214
- })
215
- })
216
- })
217
- })
218
- })
@@ -1,137 +0,0 @@
1
- import { AppBskyAgeassuranceDefs } from './client/index.js'
2
- import { ids } from './client/lexicons.js'
3
-
4
- export type AgeAssuranceRuleID = Exclude<
5
- | AppBskyAgeassuranceDefs.ConfigRegionRuleDefault['$type']
6
- | AppBskyAgeassuranceDefs.ConfigRegionRuleIfDeclaredOverAge['$type']
7
- | AppBskyAgeassuranceDefs.ConfigRegionRuleIfDeclaredUnderAge['$type']
8
- | AppBskyAgeassuranceDefs.ConfigRegionRuleIfAssuredOverAge['$type']
9
- | AppBskyAgeassuranceDefs.ConfigRegionRuleIfAssuredUnderAge['$type']
10
- | AppBskyAgeassuranceDefs.ConfigRegionRuleIfAccountNewerThan['$type']
11
- | AppBskyAgeassuranceDefs.ConfigRegionRuleIfAccountOlderThan['$type'],
12
- undefined
13
- >
14
-
15
- export const ageAssuranceRuleIDs: Record<string, AgeAssuranceRuleID> = {
16
- Default: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleDefault`,
17
- IfDeclaredOverAge: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfDeclaredOverAge`,
18
- IfDeclaredUnderAge: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfDeclaredUnderAge`,
19
- IfAssuredOverAge: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfAssuredOverAge`,
20
- IfAssuredUnderAge: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfAssuredUnderAge`,
21
- IfAccountNewerThan: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfAccountNewerThan`,
22
- IfAccountOlderThan: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfAccountOlderThan`,
23
- }
24
-
25
- /**
26
- * Returns the first matched region configuration based on the provided geolocation.
27
- */
28
- export function getAgeAssuranceRegionConfig(
29
- config: AppBskyAgeassuranceDefs.Config,
30
- geolocation: {
31
- countryCode: string
32
- regionCode?: string
33
- },
34
- ): AppBskyAgeassuranceDefs.ConfigRegion | undefined {
35
- const { regions } = config
36
- return regions.find(({ countryCode, regionCode }) => {
37
- if (countryCode === geolocation.countryCode) {
38
- return !regionCode || regionCode === geolocation.regionCode
39
- }
40
- })
41
- }
42
-
43
- export function computeAgeAssuranceRegionAccess(
44
- region: AppBskyAgeassuranceDefs.ConfigRegion,
45
- data:
46
- | {
47
- /**
48
- * The account creation date in ISO 8601 format. Only checked if we
49
- * don't have an assured age, such as on the client.
50
- */
51
- accountCreatedAt?: string
52
- /**
53
- * The user's declared age
54
- */
55
- declaredAge?: number
56
- /**
57
- * The user's minimum age as assured by a trusted third party.
58
- */
59
- assuredAge?: number
60
- }
61
- | undefined,
62
- ):
63
- | {
64
- access: AppBskyAgeassuranceDefs.Access
65
- reason: AgeAssuranceRuleID
66
- }
67
- | undefined {
68
- // first match wins
69
- for (const rule of region.rules) {
70
- if (AppBskyAgeassuranceDefs.isConfigRegionRuleIfAccountNewerThan(rule)) {
71
- if (data?.accountCreatedAt && !data?.assuredAge) {
72
- const accountCreatedAt = new Date(data.accountCreatedAt)
73
- const threshold = new Date(rule.date)
74
- if (accountCreatedAt >= threshold) {
75
- return {
76
- access: rule.access,
77
- reason: rule.$type,
78
- }
79
- }
80
- }
81
- } else if (
82
- AppBskyAgeassuranceDefs.isConfigRegionRuleIfAccountOlderThan(rule)
83
- ) {
84
- if (data?.accountCreatedAt && !data?.assuredAge) {
85
- const accountCreatedAt = new Date(data.accountCreatedAt)
86
- const threshold = new Date(rule.date)
87
- if (accountCreatedAt < threshold) {
88
- return {
89
- access: rule.access,
90
- reason: rule.$type,
91
- }
92
- }
93
- }
94
- } else if (
95
- AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredOverAge(rule)
96
- ) {
97
- if (data?.declaredAge !== undefined && data.declaredAge >= rule.age) {
98
- return {
99
- access: rule.access,
100
- reason: rule.$type,
101
- }
102
- }
103
- } else if (
104
- AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredUnderAge(rule)
105
- ) {
106
- if (data?.declaredAge !== undefined && data.declaredAge < rule.age) {
107
- return {
108
- access: rule.access,
109
- reason: rule.$type,
110
- }
111
- }
112
- } else if (
113
- AppBskyAgeassuranceDefs.isConfigRegionRuleIfAssuredOverAge(rule)
114
- ) {
115
- if (data?.assuredAge && data.assuredAge >= rule.age) {
116
- return {
117
- access: rule.access,
118
- reason: rule.$type,
119
- }
120
- }
121
- } else if (
122
- AppBskyAgeassuranceDefs.isConfigRegionRuleIfAssuredUnderAge(rule)
123
- ) {
124
- if (data?.assuredAge && data.assuredAge < rule.age) {
125
- return {
126
- access: rule.access,
127
- reason: rule.$type,
128
- }
129
- }
130
- } else if (AppBskyAgeassuranceDefs.isConfigRegionRuleDefault(rule)) {
131
- return {
132
- access: rule.access,
133
- reason: rule.$type,
134
- }
135
- }
136
- }
137
- }