@atproto/ozone 0.1.9 → 0.1.11

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 (82) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/api/moderation/emitEvent.d.ts.map +1 -1
  3. package/dist/api/moderation/emitEvent.js +4 -0
  4. package/dist/api/moderation/emitEvent.js.map +1 -1
  5. package/dist/api/moderation/queryStatuses.d.ts.map +1 -1
  6. package/dist/api/moderation/queryStatuses.js +2 -1
  7. package/dist/api/moderation/queryStatuses.js.map +1 -1
  8. package/dist/api/util.d.ts +1 -1
  9. package/dist/api/util.d.ts.map +1 -1
  10. package/dist/api/util.js +2 -0
  11. package/dist/api/util.js.map +1 -1
  12. package/dist/db/migrations/20240408T192432676Z-mute-reporting.d.ts +4 -0
  13. package/dist/db/migrations/20240408T192432676Z-mute-reporting.d.ts.map +1 -0
  14. package/dist/db/migrations/20240408T192432676Z-mute-reporting.js +18 -0
  15. package/dist/db/migrations/20240408T192432676Z-mute-reporting.js.map +1 -0
  16. package/dist/db/migrations/index.d.ts +1 -0
  17. package/dist/db/migrations/index.d.ts.map +1 -1
  18. package/dist/db/migrations/index.js +2 -1
  19. package/dist/db/migrations/index.js.map +1 -1
  20. package/dist/db/schema/moderation_event.d.ts +1 -1
  21. package/dist/db/schema/moderation_event.d.ts.map +1 -1
  22. package/dist/db/schema/moderation_subject_status.d.ts +1 -0
  23. package/dist/db/schema/moderation_subject_status.d.ts.map +1 -1
  24. package/dist/lexicon/lexicons.d.ts +68 -0
  25. package/dist/lexicon/lexicons.d.ts.map +1 -1
  26. package/dist/lexicon/lexicons.js +78 -0
  27. package/dist/lexicon/lexicons.js.map +1 -1
  28. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +16 -1
  29. package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
  30. package/dist/lexicon/types/app/bsky/actor/defs.js +21 -1
  31. package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
  32. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +22 -2
  33. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  34. package/dist/lexicon/types/tools/ozone/moderation/defs.js +22 -2
  35. package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
  36. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +1 -1
  37. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  38. package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +2 -0
  39. package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
  40. package/dist/mod-service/index.d.ts +6 -2
  41. package/dist/mod-service/index.d.ts.map +1 -1
  42. package/dist/mod-service/index.js +25 -1
  43. package/dist/mod-service/index.js.map +1 -1
  44. package/dist/mod-service/lang-data.d.ts +12 -0
  45. package/dist/mod-service/lang-data.d.ts.map +1 -0
  46. package/dist/mod-service/lang-data.js +555 -0
  47. package/dist/mod-service/lang-data.js.map +1 -0
  48. package/dist/mod-service/lang.d.ts +1 -0
  49. package/dist/mod-service/lang.d.ts.map +1 -1
  50. package/dist/mod-service/lang.js +50 -0
  51. package/dist/mod-service/lang.js.map +1 -1
  52. package/dist/mod-service/status.d.ts +21 -1
  53. package/dist/mod-service/status.d.ts.map +1 -1
  54. package/dist/mod-service/status.js +23 -0
  55. package/dist/mod-service/status.js.map +1 -1
  56. package/dist/mod-service/views.d.ts.map +1 -1
  57. package/dist/mod-service/views.js +3 -0
  58. package/dist/mod-service/views.js.map +1 -1
  59. package/jest.config.js +1 -1
  60. package/package.json +4 -3
  61. package/src/api/moderation/emitEvent.ts +9 -0
  62. package/src/api/moderation/queryStatuses.ts +2 -0
  63. package/src/api/util.ts +2 -0
  64. package/src/db/migrations/20240408T192432676Z-mute-reporting.ts +15 -0
  65. package/src/db/migrations/index.ts +1 -0
  66. package/src/db/schema/moderation_event.ts +3 -0
  67. package/src/db/schema/moderation_subject_status.ts +1 -0
  68. package/src/lexicon/lexicons.ts +80 -0
  69. package/src/lexicon/types/app/bsky/actor/defs.ts +38 -0
  70. package/src/lexicon/types/tools/ozone/moderation/defs.ts +56 -0
  71. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +3 -0
  72. package/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +2 -0
  73. package/src/mod-service/index.ts +33 -0
  74. package/src/mod-service/lang-data.ts +561 -0
  75. package/src/mod-service/lang.ts +34 -0
  76. package/src/mod-service/status.ts +26 -0
  77. package/src/mod-service/views.ts +3 -0
  78. package/tests/__snapshots__/get-record.test.ts.snap +2 -2
  79. package/tests/__snapshots__/moderation-events.test.ts.snap +6 -1
  80. package/tests/__snapshots__/moderation-statuses.test.ts.snap +1 -1
  81. package/tests/lang.test.ts +109 -0
  82. package/tests/report-muting.test.ts +100 -0
@@ -57,6 +57,15 @@ const getSubjectStatusForModerationEvent = ({
57
57
  suspendUntil: null,
58
58
  lastReviewedAt: createdAt,
59
59
  }
60
+ case 'tools.ozone.moderation.defs#modEventUnmuteReporter':
61
+ return {
62
+ lastReviewedBy: createdBy,
63
+ muteReportingUntil: null,
64
+ // It's not likely to receive an unmute event that does not already have a status row
65
+ // but if it does happen, default to unnecessary
66
+ reviewState: defaultReviewState,
67
+ lastReviewedAt: createdAt,
68
+ }
60
69
  case 'tools.ozone.moderation.defs#modEventUnmute':
61
70
  return {
62
71
  lastReviewedBy: createdBy,
@@ -76,6 +85,18 @@ const getSubjectStatusForModerationEvent = ({
76
85
  ? new Date(Date.now() + durationInHours * HOUR).toISOString()
77
86
  : null,
78
87
  }
88
+ case 'tools.ozone.moderation.defs#modEventMuteReporter':
89
+ return {
90
+ lastReviewedBy: createdBy,
91
+ lastReviewedAt: createdAt,
92
+ // By default, mute for 24hrs
93
+ muteReportingUntil: new Date(
94
+ Date.now() + (durationInHours || 24) * HOUR,
95
+ ).toISOString(),
96
+ // It's not likely to receive a mute event on a subject that does not already have a status row
97
+ // but if it does happen, default to unnecessary
98
+ reviewState: defaultReviewState,
99
+ }
79
100
  case 'tools.ozone.moderation.defs#modEventMute':
80
101
  return {
81
102
  lastReviewedBy: createdBy,
@@ -140,6 +161,11 @@ export const adjustModerationSubjectStatus = async (
140
161
  .selectAll()
141
162
  .executeTakeFirst()
142
163
 
164
+ // If reporting is muted for this reporter, we don't want to update the subject status
165
+ if (meta?.isReporterMuted) {
166
+ return currentStatus || null
167
+ }
168
+
143
169
  const isAppealEvent =
144
170
  action === 'tools.ozone.moderation.defs#modEventReport' &&
145
171
  meta?.reportType === REASONAPPEAL
@@ -108,6 +108,7 @@ export class ModerationViews {
108
108
 
109
109
  if (
110
110
  [
111
+ 'tools.ozone.moderation.defs#modEventMuteReporter',
111
112
  'tools.ozone.moderation.defs#modEventTakedown',
112
113
  'tools.ozone.moderation.defs#modEventMute',
113
114
  ].includes(event.action)
@@ -157,6 +158,7 @@ export class ModerationViews {
157
158
  eventView.event = {
158
159
  ...eventView.event,
159
160
  reportType: event.meta?.reportType ?? undefined,
161
+ isReporterMuted: !!event.meta?.isReporterMuted,
160
162
  }
161
163
  }
162
164
 
@@ -500,6 +502,7 @@ export class ModerationViews {
500
502
  lastReportedAt: status.lastReportedAt ?? undefined,
501
503
  lastAppealedAt: status.lastAppealedAt ?? undefined,
502
504
  muteUntil: status.muteUntil ?? undefined,
505
+ muteReportingUntil: status.muteReportingUntil ?? undefined,
503
506
  suspendUntil: status.suspendUntil ?? undefined,
504
507
  takendown: status.takendown ?? undefined,
505
508
  appealed: status.appealed ?? undefined,
@@ -42,7 +42,7 @@ Object {
42
42
  "subjectBlobCids": Array [],
43
43
  "subjectRepoHandle": "alice.test",
44
44
  "tags": Array [
45
- "lang:und",
45
+ "lang:en",
46
46
  ],
47
47
  "takendown": true,
48
48
  "updatedAt": "1970-01-01T00:00:00.000Z",
@@ -141,7 +141,7 @@ Object {
141
141
  "subjectBlobCids": Array [],
142
142
  "subjectRepoHandle": "alice.test",
143
143
  "tags": Array [
144
- "lang:und",
144
+ "lang:en",
145
145
  ],
146
146
  "takendown": true,
147
147
  "updatedAt": "1970-01-01T00:00:00.000Z",
@@ -7,6 +7,7 @@ Object {
7
7
  "event": Object {
8
8
  "$type": "tools.ozone.moderation.defs#modEventReport",
9
9
  "comment": "X",
10
+ "isReporterMuted": false,
10
11
  "reportType": "com.atproto.moderation.defs#reasonMisleading",
11
12
  },
12
13
  "id": 1,
@@ -77,6 +78,7 @@ Array [
77
78
  "event": Object {
78
79
  "$type": "tools.ozone.moderation.defs#modEventReport",
79
80
  "comment": "X",
81
+ "isReporterMuted": false,
80
82
  "reportType": "com.atproto.moderation.defs#reasonSpam",
81
83
  },
82
84
  "id": 11,
@@ -113,6 +115,7 @@ Array [
113
115
  "event": Object {
114
116
  "$type": "tools.ozone.moderation.defs#modEventReport",
115
117
  "comment": "X",
118
+ "isReporterMuted": false,
116
119
  "reportType": "com.atproto.moderation.defs#reasonSpam",
117
120
  },
118
121
  "id": 5,
@@ -135,6 +138,7 @@ Array [
135
138
  "event": Object {
136
139
  "$type": "tools.ozone.moderation.defs#modEventReport",
137
140
  "comment": "X",
141
+ "isReporterMuted": false,
138
142
  "reportType": "com.atproto.moderation.defs#reasonSpam",
139
143
  },
140
144
  "id": 10,
@@ -152,7 +156,7 @@ Array [
152
156
  "event": Object {
153
157
  "$type": "tools.ozone.moderation.defs#modEventTag",
154
158
  "add": Array [
155
- "lang:und",
159
+ "lang:en",
156
160
  ],
157
161
  "remove": Array [],
158
162
  },
@@ -172,6 +176,7 @@ Array [
172
176
  "event": Object {
173
177
  "$type": "tools.ozone.moderation.defs#modEventReport",
174
178
  "comment": "X",
179
+ "isReporterMuted": false,
175
180
  "reportType": "com.atproto.moderation.defs#reasonSpam",
176
181
  },
177
182
  "id": 3,
@@ -94,7 +94,7 @@ Array [
94
94
  "subjectBlobCids": Array [],
95
95
  "subjectRepoHandle": "alice.test",
96
96
  "tags": Array [
97
- "lang:und",
97
+ "lang:ha",
98
98
  ],
99
99
  "takendown": false,
100
100
  "updatedAt": "1970-01-01T00:00:00.000Z",
@@ -0,0 +1,109 @@
1
+ import {
2
+ ModeratorClient,
3
+ SeedClient,
4
+ TestNetwork,
5
+ basicSeed,
6
+ } from '@atproto/dev-env'
7
+ import AtpAgent from '@atproto/api'
8
+ import { REASONSPAM } from '../src/lexicon/types/com/atproto/moderation/defs'
9
+
10
+ describe('moderation status language tagging', () => {
11
+ let network: TestNetwork
12
+ let agent: AtpAgent
13
+ let sc: SeedClient
14
+ let modClient: ModeratorClient
15
+
16
+ beforeAll(async () => {
17
+ network = await TestNetwork.create({
18
+ dbPostgresSchema: 'ozone_blob_divert_test',
19
+ ozone: {
20
+ blobDivertUrl: `https://blob-report.com`,
21
+ blobDivertAdminPassword: 'test-auth-token',
22
+ },
23
+ })
24
+ agent = network.pds.getClient()
25
+ sc = network.getSeedClient()
26
+ modClient = network.ozone.getModClient()
27
+ await basicSeed(sc)
28
+ await network.processAll()
29
+ })
30
+
31
+ afterAll(async () => {
32
+ await network.close()
33
+ })
34
+
35
+ const getStatus = async (subject: string) => {
36
+ const { subjectStatuses } = await modClient.queryStatuses({
37
+ subject,
38
+ })
39
+
40
+ return subjectStatuses[0]
41
+ }
42
+
43
+ it('Adds language tag to post from text', async () => {
44
+ const createPostAndReport = async (text: string) => {
45
+ const post = await sc.post(sc.dids.carol, text)
46
+ await network.processAll()
47
+ const report = await sc.createReport({
48
+ reasonType: REASONSPAM,
49
+ subject: {
50
+ $type: 'com.atproto.repo.strongRef',
51
+ uri: post.ref.uriStr,
52
+ cid: post.ref.cidStr,
53
+ },
54
+ reportedBy: sc.dids.alice,
55
+ })
56
+
57
+ return { post, report }
58
+ }
59
+ const [japanesePost, greekPost] = await Promise.all([
60
+ createPostAndReport('Xで有名な人達+反AIや絵描きによくない'),
61
+ createPostAndReport(
62
+ 'Λορεμ ιπσθμ δολορ σιτ αμετ, μει θτ vιδιτ νοστρθμ προπριαε',
63
+ ),
64
+ ])
65
+
66
+ const [japanesePostStatus, greekPostStatus] = await Promise.all([
67
+ getStatus(japanesePost.post.ref.uriStr),
68
+ getStatus(greekPost.post.ref.uriStr),
69
+ ])
70
+
71
+ expect(japanesePostStatus.tags).toContain('lang:ja')
72
+ expect(greekPostStatus.tags).toContain('lang:el')
73
+ })
74
+
75
+ it('Uses name/description text for language tag for list', async () => {
76
+ const createListAndReport = async (name: string, description?: string) => {
77
+ const list = await sc.createList(sc.dids.carol, name, 'mod', {
78
+ description,
79
+ })
80
+ await network.processAll()
81
+ const report = await sc.createReport({
82
+ reasonType: REASONSPAM,
83
+ subject: {
84
+ $type: 'com.atproto.repo.strongRef',
85
+ uri: list.uriStr,
86
+ cid: list.cidStr,
87
+ },
88
+ reportedBy: sc.dids.alice,
89
+ })
90
+ return { list, report }
91
+ }
92
+
93
+ const [listWithDescription, listWithoutDescription] = await Promise.all([
94
+ createListAndReport(
95
+ 'よくない',
96
+ 'Xで有名な人達+反AIや絵描きによくない感情を持つ人達+絵描き詐称',
97
+ ),
98
+ createListAndReport('人達+反AIや絵描きによくない感情'),
99
+ ])
100
+
101
+ const [japaneseListStatus, chineseListStatus] = await Promise.all([
102
+ getStatus(listWithDescription.list.uriStr),
103
+ getStatus(listWithoutDescription.list.uriStr),
104
+ ])
105
+
106
+ expect(japaneseListStatus.tags).toContain('lang:ja')
107
+ expect(chineseListStatus.tags).toContain('lang:ja')
108
+ })
109
+ })
@@ -0,0 +1,100 @@
1
+ import {
2
+ TestNetwork,
3
+ SeedClient,
4
+ basicSeed,
5
+ ModeratorClient,
6
+ } from '@atproto/dev-env'
7
+ import {
8
+ ComAtprotoModerationDefs,
9
+ ToolsOzoneModerationDefs,
10
+ } from '@atproto/api'
11
+ import {
12
+ REVIEWNONE,
13
+ REVIEWOPEN,
14
+ } from '../src/lexicon/types/tools/ozone/moderation/defs'
15
+
16
+ describe('report-muting', () => {
17
+ let network: TestNetwork
18
+ let sc: SeedClient
19
+ let modClient: ModeratorClient
20
+
21
+ beforeAll(async () => {
22
+ network = await TestNetwork.create({
23
+ dbPostgresSchema: 'ozone_report_muting',
24
+ })
25
+ sc = network.getSeedClient()
26
+ modClient = network.ozone.getModClient()
27
+ await basicSeed(sc)
28
+ await network.processAll()
29
+ })
30
+
31
+ afterAll(async () => {
32
+ await network.close()
33
+ })
34
+
35
+ const assertSubjectStatus = async (
36
+ subject: string,
37
+ status?: string,
38
+ ): Promise<ToolsOzoneModerationDefs.SubjectStatusView | undefined> => {
39
+ const res = await modClient.queryStatuses({
40
+ subject,
41
+ })
42
+ expect(res.subjectStatuses[0]?.reviewState).toEqual(status)
43
+ return res.subjectStatuses[0]
44
+ }
45
+
46
+ it('does not change reviewState when muted reporter reports', async () => {
47
+ const bobsPostSubject = {
48
+ $type: 'com.atproto.repo.strongRef',
49
+ uri: sc.posts[sc.dids.bob][1].ref.uriStr,
50
+ cid: sc.posts[sc.dids.bob][1].ref.cidStr,
51
+ }
52
+ const carolsAccountSubject = {
53
+ $type: 'com.atproto.admin.defs#repoRef',
54
+ did: sc.dids.carol,
55
+ }
56
+
57
+ await modClient.emitEvent({
58
+ event: {
59
+ $type: 'tools.ozone.moderation.defs#modEventMuteReporter',
60
+ durationInHours: 24,
61
+ },
62
+ subject: carolsAccountSubject,
63
+ })
64
+ await sc.createReport({
65
+ reportedBy: sc.dids.carol,
66
+ reasonType: ComAtprotoModerationDefs.REASONMISLEADING,
67
+ reason: 'misleading',
68
+ subject: bobsPostSubject,
69
+ })
70
+
71
+ // Verify that a subject status was not created for bob's post since the reporter was muted
72
+ await assertSubjectStatus(bobsPostSubject.uri, undefined)
73
+ // Verify, however, that the event was logged
74
+ await modClient.queryEvents({
75
+ subject: bobsPostSubject.uri,
76
+ })
77
+
78
+ // Verify that reporting mute duration is stored for the reporter
79
+ const carolsStatus = await assertSubjectStatus(sc.dids.carol, REVIEWNONE)
80
+ expect(
81
+ new Date(`${carolsStatus?.muteReportingUntil}`).getTime(),
82
+ ).toBeGreaterThan(Date.now())
83
+
84
+ await modClient.emitEvent({
85
+ event: {
86
+ $type: 'tools.ozone.moderation.defs#modEventUnmuteReporter',
87
+ },
88
+ subject: carolsAccountSubject,
89
+ })
90
+ await sc.createReport({
91
+ reportedBy: sc.dids.carol,
92
+ reasonType: ComAtprotoModerationDefs.REASONMISLEADING,
93
+ reason: 'misleading',
94
+ subject: bobsPostSubject,
95
+ })
96
+
97
+ // Verify that a subject status was created for bob's post since the reporter was no longer muted
98
+ await assertSubjectStatus(bobsPostSubject.uri, REVIEWOPEN)
99
+ })
100
+ })