@atproto/ozone 0.1.176 → 0.2.1
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.
- package/CHANGELOG.md +22 -0
- package/dist/api/chat/getConvo.d.ts +4 -0
- package/dist/api/chat/getConvo.d.ts.map +1 -0
- package/dist/api/chat/getConvo.js +18 -0
- package/dist/api/chat/getConvo.js.map +1 -0
- package/dist/api/chat/getConvoMembers.d.ts +4 -0
- package/dist/api/chat/getConvoMembers.d.ts.map +1 -0
- package/dist/api/chat/getConvoMembers.js +18 -0
- package/dist/api/chat/getConvoMembers.js.map +1 -0
- package/dist/api/chat/index.d.ts.map +1 -1
- package/dist/api/chat/index.js +4 -0
- package/dist/api/chat/index.js.map +1 -1
- package/dist/daemon/event-reverser.d.ts.map +1 -1
- package/dist/daemon/event-reverser.js +1 -0
- package/dist/daemon/event-reverser.js.map +1 -1
- package/dist/db/migrations/20260513T202941104Z-add-subject-convo-id.d.ts +4 -0
- package/dist/db/migrations/20260513T202941104Z-add-subject-convo-id.d.ts.map +1 -0
- package/dist/db/migrations/20260513T202941104Z-add-subject-convo-id.js +107 -0
- package/dist/db/migrations/20260513T202941104Z-add-subject-convo-id.js.map +1 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +1 -0
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/schema/expiring_tag.d.ts +1 -0
- package/dist/db/schema/expiring_tag.d.ts.map +1 -1
- package/dist/db/schema/expiring_tag.js.map +1 -1
- package/dist/db/schema/moderation_event.d.ts +2 -1
- package/dist/db/schema/moderation_event.d.ts.map +1 -1
- package/dist/db/schema/moderation_event.js.map +1 -1
- package/dist/db/schema/moderation_subject_status.d.ts +1 -0
- package/dist/db/schema/moderation_subject_status.d.ts.map +1 -1
- package/dist/db/schema/moderation_subject_status.js.map +1 -1
- package/dist/db/schema/report.d.ts +1 -0
- package/dist/db/schema/report.d.ts.map +1 -1
- package/dist/db/schema/report.js.map +1 -1
- package/dist/lexicon/index.d.ts +21 -2
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +36 -2
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +1617 -305
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +762 -44
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +4 -0
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/embed/external.d.ts +48 -2
- package/dist/lexicon/types/app/bsky/embed/external.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/embed/external.js +21 -0
- package/dist/lexicon/types/app/bsky/embed/external.js.map +1 -1
- package/dist/lexicon/types/app/bsky/embed/getEmbedExternalView.d.ts +31 -0
- package/dist/lexicon/types/app/bsky/embed/getEmbedExternalView.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/embed/getEmbedExternalView.js +5 -0
- package/dist/lexicon/types/app/bsky/embed/getEmbedExternalView.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/actor/getStatus.d.ts +24 -0
- package/dist/lexicon/types/chat/bsky/actor/getStatus.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/{group/getJoinLinkPreview.js → actor/getStatus.js} +2 -2
- package/dist/lexicon/types/chat/bsky/actor/getStatus.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/defs.d.ts +39 -7
- package/dist/lexicon/types/chat/bsky/convo/defs.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/defs.js +21 -0
- package/dist/lexicon/types/chat/bsky/convo/defs.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/getConvoForMembers.d.ts +1 -1
- package/dist/lexicon/types/chat/bsky/convo/getConvoForMembers.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/getConvoForMembers.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/getLog.d.ts +1 -1
- package/dist/lexicon/types/chat/bsky/convo/getLog.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/getLog.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/listConvoRequests.d.ts +1 -1
- package/dist/lexicon/types/chat/bsky/convo/listConvoRequests.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/convo/listConvoRequests.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/embed/joinLink.d.ts +19 -0
- package/dist/lexicon/types/chat/bsky/embed/joinLink.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/embed/joinLink.js +19 -0
- package/dist/lexicon/types/chat/bsky/embed/joinLink.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/group/addMembers.d.ts +1 -1
- package/dist/lexicon/types/chat/bsky/group/addMembers.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/group/addMembers.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/group/createGroup.d.ts +1 -1
- package/dist/lexicon/types/chat/bsky/group/createGroup.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/group/createGroup.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/group/defs.d.ts +23 -0
- package/dist/lexicon/types/chat/bsky/group/defs.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/group/defs.js +14 -0
- package/dist/lexicon/types/chat/bsky/group/defs.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/group/{getJoinLinkPreview.d.ts → getJoinLinkPreviews.d.ts} +3 -4
- package/dist/lexicon/types/chat/bsky/group/getJoinLinkPreviews.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/group/getJoinLinkPreviews.js +5 -0
- package/dist/lexicon/types/chat/bsky/group/getJoinLinkPreviews.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/group/updateJoinRequestsRead.d.ts +24 -0
- package/dist/lexicon/types/chat/bsky/group/updateJoinRequestsRead.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/group/updateJoinRequestsRead.js +5 -0
- package/dist/lexicon/types/chat/bsky/group/updateJoinRequestsRead.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/group/withdrawJoinRequest.d.ts +24 -0
- package/dist/lexicon/types/chat/bsky/group/withdrawJoinRequest.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/group/withdrawJoinRequest.js +5 -0
- package/dist/lexicon/types/chat/bsky/group/withdrawJoinRequest.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/defs.d.ts +42 -0
- package/dist/lexicon/types/chat/bsky/moderation/defs.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/defs.js +26 -0
- package/dist/lexicon/types/chat/bsky/moderation/defs.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvo.d.ts +23 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvo.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvo.js +5 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvo.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvoMembers.d.ts +26 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvoMembers.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvoMembers.js +5 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvoMembers.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvos.d.ts +22 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvos.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvos.js +5 -0
- package/dist/lexicon/types/chat/bsky/moderation/getConvos.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/subscribeModEvents.d.ts +13 -1
- package/dist/lexicon/types/chat/bsky/moderation/subscribeModEvents.d.ts.map +1 -1
- package/dist/lexicon/types/chat/bsky/moderation/subscribeModEvents.js +7 -0
- package/dist/lexicon/types/chat/bsky/moderation/subscribeModEvents.js.map +1 -1
- package/dist/lexicon/types/com/atproto/server/getServiceAuth.d.ts +1 -1
- package/dist/lexicon/types/com/atproto/server/getServiceAuth.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/server/getServiceAuth.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +10 -3
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js +7 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts +2 -2
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +2 -2
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.js.map +1 -1
- package/dist/mod-service/expiring-tags.d.ts +3 -0
- package/dist/mod-service/expiring-tags.d.ts.map +1 -1
- package/dist/mod-service/expiring-tags.js +5 -2
- package/dist/mod-service/expiring-tags.js.map +1 -1
- package/dist/mod-service/index.d.ts +3 -1
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +48 -10
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/status.d.ts +7 -1
- package/dist/mod-service/status.d.ts.map +1 -1
- package/dist/mod-service/status.js +18 -3
- package/dist/mod-service/status.js.map +1 -1
- package/dist/mod-service/subject.d.ts +38 -2
- package/dist/mod-service/subject.d.ts.map +1 -1
- package/dist/mod-service/subject.js +67 -0
- package/dist/mod-service/subject.js.map +1 -1
- package/dist/mod-service/views.d.ts +2 -1
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +45 -23
- package/dist/mod-service/views.js.map +1 -1
- package/dist/queue/service.d.ts.map +1 -1
- package/dist/queue/service.js +7 -3
- package/dist/queue/service.js.map +1 -1
- package/dist/safelink/service.d.ts +1 -1
- package/package.json +5 -5
- package/src/api/chat/getConvo.ts +23 -0
- package/src/api/chat/getConvoMembers.ts +23 -0
- package/src/api/chat/index.ts +4 -0
- package/src/daemon/event-reverser.ts +1 -0
- package/src/db/migrations/20260513T202941104Z-add-subject-convo-id.ts +114 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/expiring_tag.ts +1 -0
- package/src/db/schema/moderation_event.ts +2 -0
- package/src/db/schema/moderation_subject_status.ts +4 -0
- package/src/db/schema/report.ts +2 -1
- package/src/mod-service/expiring-tags.ts +8 -2
- package/src/mod-service/index.ts +52 -16
- package/src/mod-service/status.ts +24 -2
- package/src/mod-service/subject.ts +79 -1
- package/src/mod-service/views.ts +52 -24
- package/src/queue/service.ts +8 -4
- package/tests/__snapshots__/verification.test.ts.snap +2 -0
- package/tests/expiring-tags.test.ts +1 -0
- package/tests/moderation-events.test.ts +108 -1
- package/tests/moderation-status-tags.test.ts +23 -0
- package/tests/moderation-statuses.test.ts +82 -0
- package/tests/moderation.test.ts +73 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/lexicon/types/chat/bsky/group/getJoinLinkPreview.d.ts.map +0 -1
- package/dist/lexicon/types/chat/bsky/group/getJoinLinkPreview.js.map +0 -1
|
@@ -16,6 +16,9 @@ const isStrongRef = asPredicate(ComAtprotoRepoStrongRef.validateMain)
|
|
|
16
16
|
type MessageRef = ChatBskyConvoDefs.MessageRef
|
|
17
17
|
const isValidMessageRef = asPredicate(ChatBskyConvoDefs.validateMessageRef)
|
|
18
18
|
|
|
19
|
+
type ConvoRef = ChatBskyConvoDefs.ConvoRef
|
|
20
|
+
const isValidConvoRef = asPredicate(ChatBskyConvoDefs.validateConvoRef)
|
|
21
|
+
|
|
19
22
|
const isMessageRefWithoutConvoId = (
|
|
20
23
|
subject: unknown,
|
|
21
24
|
): subject is $Typed<Omit<MessageRef, 'convoId'> & { convoId?: string }> =>
|
|
@@ -41,6 +44,11 @@ export const subjectFromInput = (
|
|
|
41
44
|
if (isValidMessageRef(subject)) {
|
|
42
45
|
return new MessageSubject(subject.did, subject.convoId, subject.messageId)
|
|
43
46
|
}
|
|
47
|
+
// @NOTE #convoRef is a new type for reporting entire conversations.
|
|
48
|
+
// Similar to messageRef, we take advantage of the open union to support this.
|
|
49
|
+
if (isValidConvoRef(subject)) {
|
|
50
|
+
return new ConvoSubject(subject.did, subject.convoId)
|
|
51
|
+
}
|
|
44
52
|
// @TODO we should start to require subject.convoId is a string in order to properly validate
|
|
45
53
|
// the #messageRef. temporarily allowing it to be optional as a stopgap for rollout.
|
|
46
54
|
// The next "if" can be removed once convoId is consistently provided.
|
|
@@ -73,6 +81,11 @@ export const subjectFromEventRow = (row: ModerationEventRow): ModSubject => {
|
|
|
73
81
|
const convoId =
|
|
74
82
|
typeof row.meta?.['convoId'] === 'string' ? row.meta['convoId'] : ''
|
|
75
83
|
return new MessageSubject(row.subjectDid, convoId, row.subjectMessageId)
|
|
84
|
+
} else if (
|
|
85
|
+
row.subjectType === 'chat.bsky.convo.defs#convoRef' &&
|
|
86
|
+
row.subjectConvoId
|
|
87
|
+
) {
|
|
88
|
+
return new ConvoSubject(row.subjectDid, row.subjectConvoId)
|
|
76
89
|
} else {
|
|
77
90
|
return new RepoSubject(row.subjectDid)
|
|
78
91
|
}
|
|
@@ -86,6 +99,9 @@ export const subjectFromStatusRow = (
|
|
|
86
99
|
// which is what the last 2 params of .make() arguments are
|
|
87
100
|
const uri = AtUri.make(row.did, ...row.recordPath.split('/')).toString()
|
|
88
101
|
return new RecordSubject(uri.toString(), row.recordCid, row.blobCids ?? [])
|
|
102
|
+
} else if (row.convoId) {
|
|
103
|
+
// Conversation subject - restore from subject status row
|
|
104
|
+
return new ConvoSubject(row.did, row.convoId)
|
|
89
105
|
} else {
|
|
90
106
|
return new RepoSubject(row.did)
|
|
91
107
|
}
|
|
@@ -96,28 +112,37 @@ type SubjectInfo = {
|
|
|
96
112
|
| 'com.atproto.admin.defs#repoRef'
|
|
97
113
|
| 'com.atproto.repo.strongRef'
|
|
98
114
|
| 'chat.bsky.convo.defs#messageRef'
|
|
115
|
+
| 'chat.bsky.convo.defs#convoRef'
|
|
99
116
|
subjectDid: string
|
|
100
117
|
subjectUri: string | null
|
|
101
118
|
subjectCid: string | null
|
|
102
119
|
subjectBlobCids: string[] | null
|
|
103
120
|
subjectMessageId: string | null
|
|
121
|
+
subjectConvoId: string | null
|
|
104
122
|
meta: Record<string, string | undefined> | null
|
|
105
123
|
}
|
|
106
124
|
|
|
107
125
|
export interface ModSubject {
|
|
108
126
|
did: string
|
|
109
127
|
recordPath: string | undefined
|
|
128
|
+
convoId: string | undefined
|
|
110
129
|
blobCids?: string[]
|
|
111
130
|
isRepo(): this is RepoSubject
|
|
112
131
|
isRecord(): this is RecordSubject
|
|
113
132
|
isMessage(): this is MessageSubject
|
|
133
|
+
isConvo(): this is ConvoSubject
|
|
114
134
|
info(): SubjectInfo
|
|
115
|
-
lex():
|
|
135
|
+
lex():
|
|
136
|
+
| $Typed<RepoRef>
|
|
137
|
+
| $Typed<StrongRef>
|
|
138
|
+
| $Typed<MessageRef>
|
|
139
|
+
| $Typed<ConvoRef>
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
export class RepoSubject implements ModSubject {
|
|
119
143
|
blobCids = undefined
|
|
120
144
|
recordPath = undefined
|
|
145
|
+
convoId = undefined
|
|
121
146
|
constructor(public did: string) {}
|
|
122
147
|
isRepo(): this is RepoSubject {
|
|
123
148
|
return true
|
|
@@ -128,6 +153,9 @@ export class RepoSubject implements ModSubject {
|
|
|
128
153
|
isMessage(): this is MessageSubject {
|
|
129
154
|
return false
|
|
130
155
|
}
|
|
156
|
+
isConvo(): this is ConvoSubject {
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
131
159
|
info() {
|
|
132
160
|
return {
|
|
133
161
|
subjectType: 'com.atproto.admin.defs#repoRef' as const,
|
|
@@ -136,6 +164,7 @@ export class RepoSubject implements ModSubject {
|
|
|
136
164
|
subjectCid: null,
|
|
137
165
|
subjectBlobCids: null,
|
|
138
166
|
subjectMessageId: null,
|
|
167
|
+
subjectConvoId: null,
|
|
139
168
|
meta: null,
|
|
140
169
|
}
|
|
141
170
|
}
|
|
@@ -151,6 +180,7 @@ export class RecordSubject implements ModSubject {
|
|
|
151
180
|
parsedUri: AtUri
|
|
152
181
|
did: string
|
|
153
182
|
recordPath: string
|
|
183
|
+
convoId = undefined
|
|
154
184
|
constructor(
|
|
155
185
|
public uri: string,
|
|
156
186
|
public cid: string,
|
|
@@ -169,6 +199,9 @@ export class RecordSubject implements ModSubject {
|
|
|
169
199
|
isMessage(): this is MessageSubject {
|
|
170
200
|
return false
|
|
171
201
|
}
|
|
202
|
+
isConvo(): this is ConvoSubject {
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
172
205
|
info() {
|
|
173
206
|
return {
|
|
174
207
|
subjectType: 'com.atproto.repo.strongRef' as const,
|
|
@@ -177,6 +210,7 @@ export class RecordSubject implements ModSubject {
|
|
|
177
210
|
subjectCid: this.cid,
|
|
178
211
|
subjectBlobCids: this.blobCids ?? [],
|
|
179
212
|
subjectMessageId: null,
|
|
213
|
+
subjectConvoId: null,
|
|
180
214
|
meta: null,
|
|
181
215
|
}
|
|
182
216
|
}
|
|
@@ -206,6 +240,9 @@ export class MessageSubject implements ModSubject {
|
|
|
206
240
|
isMessage(): this is MessageSubject {
|
|
207
241
|
return true
|
|
208
242
|
}
|
|
243
|
+
isConvo(): this is ConvoSubject {
|
|
244
|
+
return false
|
|
245
|
+
}
|
|
209
246
|
info() {
|
|
210
247
|
return {
|
|
211
248
|
subjectType: 'chat.bsky.convo.defs#messageRef' as const,
|
|
@@ -214,6 +251,7 @@ export class MessageSubject implements ModSubject {
|
|
|
214
251
|
subjectCid: null,
|
|
215
252
|
subjectBlobCids: null,
|
|
216
253
|
subjectMessageId: this.messageId,
|
|
254
|
+
subjectConvoId: this.convoId,
|
|
217
255
|
meta: { convoId: this.convoId || undefined },
|
|
218
256
|
}
|
|
219
257
|
}
|
|
@@ -226,3 +264,43 @@ export class MessageSubject implements ModSubject {
|
|
|
226
264
|
}
|
|
227
265
|
}
|
|
228
266
|
}
|
|
267
|
+
|
|
268
|
+
export class ConvoSubject implements ModSubject {
|
|
269
|
+
blobCids = undefined
|
|
270
|
+
recordPath = undefined
|
|
271
|
+
constructor(
|
|
272
|
+
public did: string,
|
|
273
|
+
public convoId: string,
|
|
274
|
+
) {}
|
|
275
|
+
isRepo(): this is RepoSubject {
|
|
276
|
+
return false
|
|
277
|
+
}
|
|
278
|
+
isRecord(): this is RecordSubject {
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
isMessage(): this is MessageSubject {
|
|
282
|
+
return false
|
|
283
|
+
}
|
|
284
|
+
isConvo(): this is ConvoSubject {
|
|
285
|
+
return true
|
|
286
|
+
}
|
|
287
|
+
info() {
|
|
288
|
+
return {
|
|
289
|
+
subjectType: 'chat.bsky.convo.defs#convoRef' as const,
|
|
290
|
+
subjectDid: this.did,
|
|
291
|
+
subjectUri: null,
|
|
292
|
+
subjectCid: null,
|
|
293
|
+
subjectBlobCids: null,
|
|
294
|
+
subjectMessageId: null,
|
|
295
|
+
subjectConvoId: this.convoId,
|
|
296
|
+
meta: { convoId: this.convoId || undefined },
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
lex(): $Typed<ConvoRef> {
|
|
300
|
+
return {
|
|
301
|
+
$type: 'chat.bsky.convo.defs#convoRef',
|
|
302
|
+
did: this.did,
|
|
303
|
+
convoId: this.convoId,
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
package/src/mod-service/views.ts
CHANGED
|
@@ -51,7 +51,11 @@ import { Un$Typed, asPredicate } from '../lexicon/util.js'
|
|
|
51
51
|
import { dbLogger, httpLogger } from '../logger.js'
|
|
52
52
|
import { ParsedLabelers } from '../util.js'
|
|
53
53
|
import { moderationSubjectStatusQueryBuilder } from './status.js'
|
|
54
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
ModSubject,
|
|
56
|
+
subjectFromEventRow,
|
|
57
|
+
subjectFromStatusRow,
|
|
58
|
+
} from './subject.js'
|
|
55
59
|
import {
|
|
56
60
|
ModerationEventRowWithHandle,
|
|
57
61
|
ModerationSubjectStatusRowWithHandle,
|
|
@@ -148,7 +152,9 @@ export class ModerationViews {
|
|
|
148
152
|
modTool: row.modTool
|
|
149
153
|
? {
|
|
150
154
|
name: row.modTool.name,
|
|
151
|
-
meta: row.modTool.meta
|
|
155
|
+
meta: sanitizeUnsafeIntegers(row.modTool.meta) as
|
|
156
|
+
| Record<string, unknown>
|
|
157
|
+
| undefined,
|
|
152
158
|
}
|
|
153
159
|
: undefined,
|
|
154
160
|
}
|
|
@@ -297,14 +303,8 @@ export class ModerationViews {
|
|
|
297
303
|
async eventDetail(
|
|
298
304
|
result: ModerationEventRowWithHandle,
|
|
299
305
|
): Promise<ModEventViewDetail> {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
? result.subjectDid
|
|
303
|
-
: result.subjectUri
|
|
304
|
-
if (!subjectId) {
|
|
305
|
-
throw new Error(`Bad subject: ${result.id}`)
|
|
306
|
-
}
|
|
307
|
-
const subject = await this.subject(subjectId)
|
|
306
|
+
const modSubject = subjectFromEventRow(result)
|
|
307
|
+
const subject = await this.subject(modSubject)
|
|
308
308
|
const eventView = this.formatEvent(result)
|
|
309
309
|
const allBlobs = 'value' in subject ? findBlobRefs(subject.value) : []
|
|
310
310
|
const subjectBlobs = await this.blob(
|
|
@@ -555,10 +555,16 @@ export class ModerationViews {
|
|
|
555
555
|
}
|
|
556
556
|
// Partial view for subjects
|
|
557
557
|
|
|
558
|
-
async subject(subject:
|
|
559
|
-
if (subject.
|
|
560
|
-
|
|
561
|
-
|
|
558
|
+
async subject(subject: ModSubject): Promise<SubjectView> {
|
|
559
|
+
if (subject.isConvo()) {
|
|
560
|
+
return {
|
|
561
|
+
$type: 'tools.ozone.moderation.defs#convoView',
|
|
562
|
+
did: subject.did,
|
|
563
|
+
convoId: subject.convoId,
|
|
564
|
+
}
|
|
565
|
+
} else if (subject.isRepo()) {
|
|
566
|
+
const repos = await this.repos([subject.did])
|
|
567
|
+
const repo = repos.get(subject.did)
|
|
562
568
|
if (repo) {
|
|
563
569
|
return {
|
|
564
570
|
...repo,
|
|
@@ -567,24 +573,24 @@ export class ModerationViews {
|
|
|
567
573
|
} else {
|
|
568
574
|
return {
|
|
569
575
|
$type: 'tools.ozone.moderation.defs#repoViewNotFound',
|
|
570
|
-
did: subject,
|
|
576
|
+
did: subject.did,
|
|
571
577
|
}
|
|
572
578
|
}
|
|
573
|
-
} else {
|
|
574
|
-
const
|
|
575
|
-
const
|
|
579
|
+
} else if (subject.isRecord()) {
|
|
580
|
+
const uri = subject.uri
|
|
581
|
+
const records = await this.records([{ uri }])
|
|
582
|
+
const record = records.get(uri)
|
|
576
583
|
if (record) {
|
|
577
584
|
return {
|
|
578
585
|
...record,
|
|
579
586
|
$type: 'tools.ozone.moderation.defs#recordView',
|
|
580
587
|
}
|
|
581
|
-
} else {
|
|
582
|
-
return {
|
|
583
|
-
$type: 'tools.ozone.moderation.defs#recordViewNotFound',
|
|
584
|
-
uri: subject,
|
|
585
|
-
}
|
|
586
588
|
}
|
|
587
589
|
}
|
|
590
|
+
return {
|
|
591
|
+
$type: 'tools.ozone.moderation.defs#repoViewNotFound',
|
|
592
|
+
did: subject.did,
|
|
593
|
+
}
|
|
588
594
|
}
|
|
589
595
|
|
|
590
596
|
// Partial view for blobs
|
|
@@ -691,7 +697,8 @@ export class ModerationViews {
|
|
|
691
697
|
'moderation_subject_status.recordPath',
|
|
692
698
|
'=',
|
|
693
699
|
sub.recordPath ?? '',
|
|
694
|
-
)
|
|
700
|
+
)
|
|
701
|
+
.where('moderation_subject_status.convoId', '=', ''),
|
|
695
702
|
)
|
|
696
703
|
}
|
|
697
704
|
return qb
|
|
@@ -882,3 +889,24 @@ export function getSelfLabels(details: {
|
|
|
882
889
|
return { src, uri, cid, val, cts }
|
|
883
890
|
})
|
|
884
891
|
}
|
|
892
|
+
|
|
893
|
+
// The atproto data model requires all integers to fit within 53 bits
|
|
894
|
+
// (Number.MAX_SAFE_INTEGER). External tools may write larger numbers into
|
|
895
|
+
// "unknown"-typed fields like modTool.meta, which causes lex-json parse
|
|
896
|
+
// failures on the client side. This helper converts unsafe integers to strings.
|
|
897
|
+
function sanitizeUnsafeIntegers(value: unknown): unknown {
|
|
898
|
+
if (typeof value === 'number') {
|
|
899
|
+
return Number.isSafeInteger(value) ? value : String(value)
|
|
900
|
+
}
|
|
901
|
+
if (Array.isArray(value)) {
|
|
902
|
+
return value.map(sanitizeUnsafeIntegers)
|
|
903
|
+
}
|
|
904
|
+
if (value !== null && typeof value === 'object') {
|
|
905
|
+
const out: Record<string, unknown> = {}
|
|
906
|
+
for (const [k, v] of Object.entries(value)) {
|
|
907
|
+
out[k] = sanitizeUnsafeIntegers(v)
|
|
908
|
+
}
|
|
909
|
+
return out
|
|
910
|
+
}
|
|
911
|
+
return value
|
|
912
|
+
}
|
package/src/queue/service.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { viewQueueStats } from '../report/views.js'
|
|
|
13
13
|
const MOD_EVENT_REPORT_ACTION = 'tools.ozone.moderation.defs#modEventReport'
|
|
14
14
|
const REASON_OTHER = 'com.atproto.moderation.defs#reasonOther'
|
|
15
15
|
|
|
16
|
-
type SubjectType = 'account' | 'record' | 'message'
|
|
16
|
+
type SubjectType = 'account' | 'record' | 'message' | 'conversation'
|
|
17
17
|
|
|
18
18
|
type ResolvedAssignment = {
|
|
19
19
|
queueId: number
|
|
@@ -478,6 +478,7 @@ export class QueueService {
|
|
|
478
478
|
'subjectDid',
|
|
479
479
|
'subjectUri',
|
|
480
480
|
'subjectMessageId',
|
|
481
|
+
'subjectConvoId',
|
|
481
482
|
'meta',
|
|
482
483
|
'createdAt',
|
|
483
484
|
])
|
|
@@ -503,9 +504,11 @@ export class QueueService {
|
|
|
503
504
|
const rows = events.map((event) => {
|
|
504
505
|
const subjectType: SubjectType = event.subjectMessageId
|
|
505
506
|
? 'message'
|
|
506
|
-
: event.
|
|
507
|
-
? '
|
|
508
|
-
:
|
|
507
|
+
: event.subjectConvoId
|
|
508
|
+
? 'conversation'
|
|
509
|
+
: event.subjectUri
|
|
510
|
+
? 'record'
|
|
511
|
+
: 'account'
|
|
509
512
|
|
|
510
513
|
let collection: string | null = null
|
|
511
514
|
let recordPath = ''
|
|
@@ -545,6 +548,7 @@ export class QueueService {
|
|
|
545
548
|
did: event.subjectDid,
|
|
546
549
|
recordPath,
|
|
547
550
|
subjectMessageId: event.subjectMessageId,
|
|
551
|
+
subjectConvoId: event.subjectConvoId,
|
|
548
552
|
createdAt: now,
|
|
549
553
|
updatedAt: now,
|
|
550
554
|
}
|
|
@@ -122,6 +122,8 @@ exports[`verification list returns paginated list of verifications 1`] = `
|
|
|
122
122
|
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
123
123
|
"isValid": true,
|
|
124
124
|
"issuer": "user(0)",
|
|
125
|
+
"issuerDisplayName": "ali",
|
|
126
|
+
"issuerHandle": "alice.test",
|
|
125
127
|
"uri": "record(0)",
|
|
126
128
|
},
|
|
127
129
|
],
|
|
@@ -207,8 +207,25 @@ describe('moderation-events', () => {
|
|
|
207
207
|
}),
|
|
208
208
|
])
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
// Verify all spam events have the correct report type
|
|
211
211
|
expect(spamEvents.events.length).toEqual(6)
|
|
212
|
+
spamEvents.events.forEach((event) => {
|
|
213
|
+
expect(event.event.$type).toEqual(
|
|
214
|
+
'tools.ozone.moderation.defs#modEventReport',
|
|
215
|
+
)
|
|
216
|
+
expect((event.event as any).reportType).toEqual(REASONSPAM)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// Verify all misleading events have one of the correct report types
|
|
220
|
+
expect(misleadingEvents.events.length).toEqual(2)
|
|
221
|
+
misleadingEvents.events.forEach((event) => {
|
|
222
|
+
expect(event.event.$type).toEqual(
|
|
223
|
+
'tools.ozone.moderation.defs#modEventReport',
|
|
224
|
+
)
|
|
225
|
+
expect([REASONMISLEADING, REASONAPPEAL]).toContain(
|
|
226
|
+
(event.event as any).reportType,
|
|
227
|
+
)
|
|
228
|
+
})
|
|
212
229
|
})
|
|
213
230
|
|
|
214
231
|
it('returns events matching keyword in comment', async () => {
|
|
@@ -501,6 +518,96 @@ describe('moderation-events', () => {
|
|
|
501
518
|
expect(event.createdBy).toEqual(network.ozone.moderatorAccnt.did)
|
|
502
519
|
})
|
|
503
520
|
})
|
|
521
|
+
|
|
522
|
+
it('queries events by conversation', async () => {
|
|
523
|
+
const convoId1 = 'conversation-123'
|
|
524
|
+
const convoId2 = 'conversation-456'
|
|
525
|
+
|
|
526
|
+
// create reports
|
|
527
|
+
await sc.createReport({
|
|
528
|
+
reasonType: REASONSPAM,
|
|
529
|
+
reason: 'spam in convo 1',
|
|
530
|
+
subject: {
|
|
531
|
+
$type: 'chat.bsky.convo.defs#convoRef',
|
|
532
|
+
did: sc.dids.carol,
|
|
533
|
+
convoId: convoId1,
|
|
534
|
+
},
|
|
535
|
+
reportedBy: sc.dids.alice,
|
|
536
|
+
})
|
|
537
|
+
await sc.createReport({
|
|
538
|
+
reasonType: REASONMISLEADING,
|
|
539
|
+
reason: 'misleading in convo 1',
|
|
540
|
+
subject: {
|
|
541
|
+
$type: 'chat.bsky.convo.defs#convoRef',
|
|
542
|
+
did: sc.dids.carol,
|
|
543
|
+
convoId: convoId1,
|
|
544
|
+
},
|
|
545
|
+
reportedBy: sc.dids.bob,
|
|
546
|
+
})
|
|
547
|
+
await sc.createReport({
|
|
548
|
+
reasonType: REASONSPAM,
|
|
549
|
+
reason: 'spam in convo 2',
|
|
550
|
+
subject: {
|
|
551
|
+
$type: 'chat.bsky.convo.defs#convoRef',
|
|
552
|
+
did: sc.dids.carol,
|
|
553
|
+
convoId: convoId2,
|
|
554
|
+
},
|
|
555
|
+
reportedBy: sc.dids.alice,
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
// Query events (filter by report type since auto-tagging creates extra events)
|
|
559
|
+
const convo1Events = await modClient.queryEvents({
|
|
560
|
+
subject: `at://${sc.dids.carol}/chat.bsky.convo/${convoId1}`,
|
|
561
|
+
includeAllUserRecords: false,
|
|
562
|
+
types: ['tools.ozone.moderation.defs#modEventReport'],
|
|
563
|
+
})
|
|
564
|
+
const convo2Events = await modClient.queryEvents({
|
|
565
|
+
subject: `at://${sc.dids.carol}/chat.bsky.convo/${convoId2}`,
|
|
566
|
+
includeAllUserRecords: false,
|
|
567
|
+
types: ['tools.ozone.moderation.defs#modEventReport'],
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
// Verify conversation 1 events are correct
|
|
571
|
+
expect(convo1Events.events.length).toBeGreaterThan(0)
|
|
572
|
+
convo1Events.events.forEach((e) => {
|
|
573
|
+
// All events should be conversation refs
|
|
574
|
+
expect(e.subject.$type).toEqual('chat.bsky.convo.defs#convoRef')
|
|
575
|
+
// All events should be for conversation 1
|
|
576
|
+
const subject = e.subject as any
|
|
577
|
+
expect(subject.convoId).toEqual(convoId1)
|
|
578
|
+
expect(subject.did).toEqual(sc.dids.carol)
|
|
579
|
+
// All events should be reports
|
|
580
|
+
expect(e.event.$type).toEqual(
|
|
581
|
+
'tools.ozone.moderation.defs#modEventReport',
|
|
582
|
+
)
|
|
583
|
+
})
|
|
584
|
+
// Verify we got both reports we created
|
|
585
|
+
const convo1Comments = convo1Events.events.map(
|
|
586
|
+
(e) => (e.event as any).comment,
|
|
587
|
+
)
|
|
588
|
+
expect(convo1Comments).toContain('spam in convo 1')
|
|
589
|
+
expect(convo1Comments).toContain('misleading in convo 1')
|
|
590
|
+
|
|
591
|
+
// Verify conversation 2 events are correct
|
|
592
|
+
expect(convo2Events.events.length).toBeGreaterThan(0)
|
|
593
|
+
convo2Events.events.forEach((e) => {
|
|
594
|
+
// All events should be conversation refs
|
|
595
|
+
expect(e.subject.$type).toEqual('chat.bsky.convo.defs#convoRef')
|
|
596
|
+
// All events should be for conversation 2
|
|
597
|
+
const subject = e.subject as any
|
|
598
|
+
expect(subject.convoId).toEqual(convoId2)
|
|
599
|
+
expect(subject.did).toEqual(sc.dids.carol)
|
|
600
|
+
// All events should be reports
|
|
601
|
+
expect(e.event.$type).toEqual(
|
|
602
|
+
'tools.ozone.moderation.defs#modEventReport',
|
|
603
|
+
)
|
|
604
|
+
})
|
|
605
|
+
// Verify we got the report we created
|
|
606
|
+
const convo2Comments = convo2Events.events.map(
|
|
607
|
+
(e) => (e.event as any).comment,
|
|
608
|
+
)
|
|
609
|
+
expect(convo2Comments).toContain('spam in convo 2')
|
|
610
|
+
})
|
|
504
611
|
})
|
|
505
612
|
|
|
506
613
|
describe('get event', () => {
|
|
@@ -113,5 +113,28 @@ describe('moderation-status-tags', () => {
|
|
|
113
113
|
expect(englishOrJapaneseDids).toContain(sc.dids.alice)
|
|
114
114
|
expect(englishOrJapaneseDids).toContain(sc.dids.bob)
|
|
115
115
|
})
|
|
116
|
+
|
|
117
|
+
it('adds tag to conversation', async () => {
|
|
118
|
+
const subject = {
|
|
119
|
+
$type: 'chat.bsky.convo.defs#convoRef',
|
|
120
|
+
did: sc.dids.alice,
|
|
121
|
+
convoId: '123',
|
|
122
|
+
}
|
|
123
|
+
await modClient.emitEvent({
|
|
124
|
+
subject: subject,
|
|
125
|
+
event: {
|
|
126
|
+
$type: 'tools.ozone.moderation.defs#modEventTag',
|
|
127
|
+
add: ['interaction-churn'],
|
|
128
|
+
remove: [],
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
const status = await network.ozone.ctx.db.db
|
|
132
|
+
.selectFrom('moderation_subject_status')
|
|
133
|
+
.selectAll()
|
|
134
|
+
.where('did', '=', subject.did)
|
|
135
|
+
.where('convoId', '=', subject.convoId)
|
|
136
|
+
.executeTakeFirstOrThrow()
|
|
137
|
+
expect(status.tags).toContain('interaction-churn')
|
|
138
|
+
})
|
|
116
139
|
})
|
|
117
140
|
})
|
|
@@ -276,6 +276,88 @@ describe('moderation-statuses', () => {
|
|
|
276
276
|
"only bob's account statuses are returned, no events have a URI even though the subjectType is record",
|
|
277
277
|
)
|
|
278
278
|
})
|
|
279
|
+
|
|
280
|
+
it('returns statuses for conversations', async () => {
|
|
281
|
+
const convoId1 = 'test-convo-123'
|
|
282
|
+
const convoId2 = 'test-convo-456'
|
|
283
|
+
|
|
284
|
+
// Create reports for conversation 1
|
|
285
|
+
await sc.createReport({
|
|
286
|
+
reasonType: REASONSPAM,
|
|
287
|
+
reason: 'spam in convo 1',
|
|
288
|
+
subject: {
|
|
289
|
+
$type: 'chat.bsky.convo.defs#convoRef',
|
|
290
|
+
did: sc.dids.carol,
|
|
291
|
+
convoId: convoId1,
|
|
292
|
+
},
|
|
293
|
+
reportedBy: sc.dids.alice,
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
// Create another report for conversation 1
|
|
297
|
+
await sc.createReport({
|
|
298
|
+
reasonType: REASONMISLEADING,
|
|
299
|
+
reason: 'misleading in convo 1',
|
|
300
|
+
subject: {
|
|
301
|
+
$type: 'chat.bsky.convo.defs#convoRef',
|
|
302
|
+
did: sc.dids.carol,
|
|
303
|
+
convoId: convoId1,
|
|
304
|
+
},
|
|
305
|
+
reportedBy: sc.dids.bob,
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
// Create report for conversation 2
|
|
309
|
+
await sc.createReport({
|
|
310
|
+
reasonType: REASONSPAM,
|
|
311
|
+
reason: 'spam in convo 2',
|
|
312
|
+
subject: {
|
|
313
|
+
$type: 'chat.bsky.convo.defs#convoRef',
|
|
314
|
+
did: sc.dids.carol,
|
|
315
|
+
convoId: convoId2,
|
|
316
|
+
},
|
|
317
|
+
reportedBy: sc.dids.alice,
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// Query statuses for conversation 1 using AT URI format
|
|
321
|
+
const convo1Statuses = await modClient.queryStatuses({
|
|
322
|
+
subject: `at://${sc.dids.carol}/chat.bsky.convo/${convoId1}`,
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
// Query statuses for conversation 2
|
|
326
|
+
const convo2Statuses = await modClient.queryStatuses({
|
|
327
|
+
subject: `at://${sc.dids.carol}/chat.bsky.convo/${convoId2}`,
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
// Query all conversation statuses for carol
|
|
331
|
+
const allCarolConvoStatuses = await modClient.queryStatuses({
|
|
332
|
+
subject: sc.dids.carol,
|
|
333
|
+
includeAllUserRecords: true,
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
// Verify conversation 1 has exactly 1 status (multiple reports create one status)
|
|
337
|
+
expect(convo1Statuses.subjectStatuses.length).toEqual(1)
|
|
338
|
+
expect(convo1Statuses.subjectStatuses[0].subject.$type).toEqual(
|
|
339
|
+
'chat.bsky.convo.defs#convoRef',
|
|
340
|
+
)
|
|
341
|
+
expect(convo1Statuses.subjectStatuses[0].reviewState).toEqual(REVIEWOPEN)
|
|
342
|
+
|
|
343
|
+
// Verify conversation 2 has exactly 1 status
|
|
344
|
+
expect(convo2Statuses.subjectStatuses.length).toEqual(1)
|
|
345
|
+
expect(convo2Statuses.subjectStatuses[0].subject.$type).toEqual(
|
|
346
|
+
'chat.bsky.convo.defs#convoRef',
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
// Verify statuses are properly isolated by conversation
|
|
350
|
+
const convo1Subject = convo1Statuses.subjectStatuses[0].subject as any
|
|
351
|
+
const convo2Subject = convo2Statuses.subjectStatuses[0].subject as any
|
|
352
|
+
expect(convo1Subject.convoId).toEqual(convoId1)
|
|
353
|
+
expect(convo2Subject.convoId).toEqual(convoId2)
|
|
354
|
+
|
|
355
|
+
// Verify includeAllUserRecords includes conversations
|
|
356
|
+
const convoStatuses = allCarolConvoStatuses.subjectStatuses.filter(
|
|
357
|
+
(s) => s.subject.$type === 'chat.bsky.convo.defs#convoRef',
|
|
358
|
+
)
|
|
359
|
+
expect(convoStatuses.length).toBeGreaterThanOrEqual(2)
|
|
360
|
+
})
|
|
279
361
|
})
|
|
280
362
|
|
|
281
363
|
describe('reviewState changes', () => {
|