@atproto/ozone 0.1.44 → 0.1.46
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 +26 -0
- package/dist/api/moderation/emitEvent.d.ts.map +1 -1
- package/dist/api/moderation/emitEvent.js +3 -0
- package/dist/api/moderation/emitEvent.js.map +1 -1
- package/dist/api/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/api/moderation/queryStatuses.js +2 -1
- package/dist/api/moderation/queryStatuses.js.map +1 -1
- package/dist/api/util.d.ts.map +1 -1
- package/dist/api/util.js +11 -7
- package/dist/api/util.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +77 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +86 -3
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +16 -0
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js +9 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/profile.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/actor/profile.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/profile.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +13 -2
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js +21 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/getAuthorFeed.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.d.ts +2 -0
- package/dist/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.d.ts +2 -0
- package/dist/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +2 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +3 -0
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.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 -6
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +7 -0
- package/dist/mod-service/views.js.map +1 -1
- package/package.json +9 -9
- package/src/api/moderation/emitEvent.ts +4 -0
- package/src/api/moderation/queryStatuses.ts +2 -0
- package/src/api/util.ts +23 -7
- package/src/lexicon/lexicons.ts +92 -3
- package/src/lexicon/types/app/bsky/actor/defs.ts +25 -0
- package/src/lexicon/types/app/bsky/actor/profile.ts +1 -0
- package/src/lexicon/types/app/bsky/feed/defs.ts +38 -2
- package/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +1 -0
- package/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts +2 -0
- package/src/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.ts +2 -0
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +2 -0
- package/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +3 -0
- package/src/mod-service/index.ts +64 -4
- package/src/mod-service/views.ts +10 -0
- package/tests/ack-all-subjects-of-account.test.ts +131 -0
|
@@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util'
|
|
|
7
7
|
import { CID } from 'multiformats/cid'
|
|
8
8
|
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
|
|
9
9
|
import * as AppBskyGraphDefs from '../graph/defs'
|
|
10
|
+
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
|
|
10
11
|
|
|
11
12
|
export interface ProfileViewBasic {
|
|
12
13
|
did: string
|
|
@@ -74,6 +75,7 @@ export interface ProfileViewDetailed {
|
|
|
74
75
|
createdAt?: string
|
|
75
76
|
viewer?: ViewerState
|
|
76
77
|
labels?: ComAtprotoLabelDefs.Label[]
|
|
78
|
+
pinnedPost?: ComAtprotoRepoStrongRef.Main
|
|
77
79
|
[k: string]: unknown
|
|
78
80
|
}
|
|
79
81
|
|
|
@@ -469,6 +471,8 @@ export interface BskyAppStatePref {
|
|
|
469
471
|
activeProgressGuide?: BskyAppProgressGuide
|
|
470
472
|
/** An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user. */
|
|
471
473
|
queuedNudges?: string[]
|
|
474
|
+
/** Storage for NUXs the user has encountered. */
|
|
475
|
+
nuxs?: Nux[]
|
|
472
476
|
[k: string]: unknown
|
|
473
477
|
}
|
|
474
478
|
|
|
@@ -501,3 +505,24 @@ export function isBskyAppProgressGuide(v: unknown): v is BskyAppProgressGuide {
|
|
|
501
505
|
export function validateBskyAppProgressGuide(v: unknown): ValidationResult {
|
|
502
506
|
return lexicons.validate('app.bsky.actor.defs#bskyAppProgressGuide', v)
|
|
503
507
|
}
|
|
508
|
+
|
|
509
|
+
/** A new user experiences (NUX) storage object */
|
|
510
|
+
export interface Nux {
|
|
511
|
+
id: string
|
|
512
|
+
completed: boolean
|
|
513
|
+
/** Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters. */
|
|
514
|
+
data?: string
|
|
515
|
+
/** The date and time at which the NUX will expire and should be considered completed. */
|
|
516
|
+
expiresAt?: string
|
|
517
|
+
[k: string]: unknown
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export function isNux(v: unknown): v is Nux {
|
|
521
|
+
return (
|
|
522
|
+
isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.actor.defs#nux'
|
|
523
|
+
)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function validateNux(v: unknown): ValidationResult {
|
|
527
|
+
return lexicons.validate('app.bsky.actor.defs#nux', v)
|
|
528
|
+
}
|
|
@@ -55,6 +55,7 @@ export interface ViewerState {
|
|
|
55
55
|
threadMuted?: boolean
|
|
56
56
|
replyDisabled?: boolean
|
|
57
57
|
embeddingDisabled?: boolean
|
|
58
|
+
pinned?: boolean
|
|
58
59
|
[k: string]: unknown
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -73,7 +74,7 @@ export function validateViewerState(v: unknown): ValidationResult {
|
|
|
73
74
|
export interface FeedViewPost {
|
|
74
75
|
post: PostView
|
|
75
76
|
reply?: ReplyRef
|
|
76
|
-
reason?: ReasonRepost | { $type: string; [k: string]: unknown }
|
|
77
|
+
reason?: ReasonRepost | ReasonPin | { $type: string; [k: string]: unknown }
|
|
77
78
|
/** Context provided by feed generator that may be passed back alongside interactions. */
|
|
78
79
|
feedContext?: string
|
|
79
80
|
[k: string]: unknown
|
|
@@ -134,6 +135,22 @@ export function validateReasonRepost(v: unknown): ValidationResult {
|
|
|
134
135
|
return lexicons.validate('app.bsky.feed.defs#reasonRepost', v)
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
export interface ReasonPin {
|
|
139
|
+
[k: string]: unknown
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function isReasonPin(v: unknown): v is ReasonPin {
|
|
143
|
+
return (
|
|
144
|
+
isObj(v) &&
|
|
145
|
+
hasProp(v, '$type') &&
|
|
146
|
+
v.$type === 'app.bsky.feed.defs#reasonPin'
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function validateReasonPin(v: unknown): ValidationResult {
|
|
151
|
+
return lexicons.validate('app.bsky.feed.defs#reasonPin', v)
|
|
152
|
+
}
|
|
153
|
+
|
|
137
154
|
export interface ThreadViewPost {
|
|
138
155
|
post: PostView
|
|
139
156
|
parent?:
|
|
@@ -265,7 +282,10 @@ export function validateGeneratorViewerState(v: unknown): ValidationResult {
|
|
|
265
282
|
|
|
266
283
|
export interface SkeletonFeedPost {
|
|
267
284
|
post: string
|
|
268
|
-
reason?:
|
|
285
|
+
reason?:
|
|
286
|
+
| SkeletonReasonRepost
|
|
287
|
+
| SkeletonReasonPin
|
|
288
|
+
| { $type: string; [k: string]: unknown }
|
|
269
289
|
/** Context that will be passed through to client and may be passed to feed generator back alongside interactions. */
|
|
270
290
|
feedContext?: string
|
|
271
291
|
[k: string]: unknown
|
|
@@ -300,6 +320,22 @@ export function validateSkeletonReasonRepost(v: unknown): ValidationResult {
|
|
|
300
320
|
return lexicons.validate('app.bsky.feed.defs#skeletonReasonRepost', v)
|
|
301
321
|
}
|
|
302
322
|
|
|
323
|
+
export interface SkeletonReasonPin {
|
|
324
|
+
[k: string]: unknown
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function isSkeletonReasonPin(v: unknown): v is SkeletonReasonPin {
|
|
328
|
+
return (
|
|
329
|
+
isObj(v) &&
|
|
330
|
+
hasProp(v, '$type') &&
|
|
331
|
+
v.$type === 'app.bsky.feed.defs#skeletonReasonPin'
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function validateSkeletonReasonPin(v: unknown): ValidationResult {
|
|
336
|
+
return lexicons.validate('app.bsky.feed.defs#skeletonReasonPin', v)
|
|
337
|
+
}
|
|
338
|
+
|
|
303
339
|
export interface ThreadgateView {
|
|
304
340
|
uri?: string
|
|
305
341
|
cid?: string
|
|
@@ -17,6 +17,8 @@ export type InputSchema = undefined
|
|
|
17
17
|
|
|
18
18
|
export interface OutputSchema {
|
|
19
19
|
suggestions: AppBskyActorDefs.ProfileView[]
|
|
20
|
+
/** If true, response has fallen-back to generic results, and is not scoped using relativeToDid */
|
|
21
|
+
isFallback?: boolean
|
|
20
22
|
[k: string]: unknown
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -23,6 +23,8 @@ export type InputSchema = undefined
|
|
|
23
23
|
export interface OutputSchema {
|
|
24
24
|
cursor?: string
|
|
25
25
|
actors: AppBskyUnspeccedDefs.SkeletonSearchActor[]
|
|
26
|
+
/** DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer. */
|
|
27
|
+
relativeToDid?: string
|
|
26
28
|
[k: string]: unknown
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -162,6 +162,8 @@ export interface ModEventTakedown {
|
|
|
162
162
|
comment?: string
|
|
163
163
|
/** Indicates how long the takedown should be in effect before automatically expiring. */
|
|
164
164
|
durationInHours?: number
|
|
165
|
+
/** If true, all other reports on content authored by this account will be resolved (acknowledged). */
|
|
166
|
+
acknowledgeAccountSubjects?: boolean
|
|
165
167
|
[k: string]: unknown
|
|
166
168
|
}
|
|
167
169
|
|
|
@@ -10,6 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
|
10
10
|
import * as ToolsOzoneModerationDefs from './defs'
|
|
11
11
|
|
|
12
12
|
export interface QueryParams {
|
|
13
|
+
/** All subjects belonging to the account specified in the 'subject' param will be returned. */
|
|
14
|
+
includeAllUserRecords?: boolean
|
|
15
|
+
/** The subject to get the status for. */
|
|
13
16
|
subject?: string
|
|
14
17
|
/** Search subjects by keyword from comments */
|
|
15
18
|
comment?: string
|
package/src/mod-service/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Insertable, sql } from 'kysely'
|
|
|
3
3
|
import { CID } from 'multiformats/cid'
|
|
4
4
|
import { AtUri, INVALID_HANDLE } from '@atproto/syntax'
|
|
5
5
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
6
|
-
import { addHoursToDate } from '@atproto/common'
|
|
6
|
+
import { addHoursToDate, chunkArray } from '@atproto/common'
|
|
7
7
|
import { Keypair } from '@atproto/crypto'
|
|
8
8
|
import { IdResolver } from '@atproto/identity'
|
|
9
9
|
import { AtpAgent } from '@atproto/api'
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
isModEventTakedown,
|
|
19
19
|
isModEventEmail,
|
|
20
20
|
isModEventTag,
|
|
21
|
+
REVIEWESCALATED,
|
|
22
|
+
REVIEWOPEN,
|
|
21
23
|
} from '../lexicon/types/tools/ozone/moderation/defs'
|
|
22
24
|
import { RepoRef, RepoBlobRef } from '../lexicon/types/com/atproto/admin/defs'
|
|
23
25
|
import {
|
|
@@ -279,6 +281,52 @@ export class ModerationService {
|
|
|
279
281
|
return await builder.execute()
|
|
280
282
|
}
|
|
281
283
|
|
|
284
|
+
async resolveSubjectsForAccount(did: string, createdBy: string) {
|
|
285
|
+
const subjectsToBeResolved = await this.db.db
|
|
286
|
+
.selectFrom('moderation_subject_status')
|
|
287
|
+
.where('did', '=', did)
|
|
288
|
+
.where('recordPath', '!=', '')
|
|
289
|
+
.where('reviewState', 'in', [REVIEWESCALATED, REVIEWOPEN])
|
|
290
|
+
.selectAll()
|
|
291
|
+
.execute()
|
|
292
|
+
|
|
293
|
+
if (subjectsToBeResolved.length === 0) {
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Process subjects in chunks of 100 since each of these will trigger multiple db queries
|
|
298
|
+
for (const subjects of chunkArray(subjectsToBeResolved, 100)) {
|
|
299
|
+
await Promise.all(
|
|
300
|
+
subjects.map(async (subject) => {
|
|
301
|
+
const eventData = {
|
|
302
|
+
createdBy,
|
|
303
|
+
subject: subjectFromStatusRow(subject),
|
|
304
|
+
}
|
|
305
|
+
// For consistency's sake, when acknowledging appealed subjects, we should first resolve the appeal
|
|
306
|
+
if (subject.appealed) {
|
|
307
|
+
await this.logEvent({
|
|
308
|
+
event: {
|
|
309
|
+
$type: 'tools.ozone.moderation.defs#modEventResolveAppeal',
|
|
310
|
+
comment:
|
|
311
|
+
'[AUTO_RESOLVE_FOR_TAKENDOWN_ACCOUNT]: Automatically resolving all appealed content for a takendown account',
|
|
312
|
+
},
|
|
313
|
+
...eventData,
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
await this.logEvent({
|
|
318
|
+
event: {
|
|
319
|
+
$type: 'tools.ozone.moderation.defs#modEventAcknowledge',
|
|
320
|
+
comment:
|
|
321
|
+
'[AUTO_RESOLVE_FOR_TAKENDOWN_ACCOUNT]: Automatically resolving all reported content for a takendown account',
|
|
322
|
+
},
|
|
323
|
+
...eventData,
|
|
324
|
+
})
|
|
325
|
+
}),
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
282
330
|
async logEvent(info: {
|
|
283
331
|
event: ModEventType
|
|
284
332
|
subject: ModSubject
|
|
@@ -320,6 +368,10 @@ export class ModerationService {
|
|
|
320
368
|
}
|
|
321
369
|
}
|
|
322
370
|
|
|
371
|
+
if (isModEventTakedown(event) && event.acknowledgeAccountSubjects) {
|
|
372
|
+
meta.acknowledgeAccountSubjects = true
|
|
373
|
+
}
|
|
374
|
+
|
|
323
375
|
// Keep trace of reports that came in while the reporter was in muted stated
|
|
324
376
|
if (isModEventReport(event)) {
|
|
325
377
|
const isReportingMuted = await this.isReportingMutedForSubject(createdBy)
|
|
@@ -677,6 +729,7 @@ export class ModerationService {
|
|
|
677
729
|
}
|
|
678
730
|
|
|
679
731
|
async getSubjectStatuses({
|
|
732
|
+
includeAllUserRecords,
|
|
680
733
|
cursor,
|
|
681
734
|
limit = 50,
|
|
682
735
|
takendown,
|
|
@@ -696,6 +749,7 @@ export class ModerationService {
|
|
|
696
749
|
tags,
|
|
697
750
|
excludeTags,
|
|
698
751
|
}: {
|
|
752
|
+
includeAllUserRecords?: boolean
|
|
699
753
|
cursor?: string
|
|
700
754
|
limit?: number
|
|
701
755
|
takendown?: boolean
|
|
@@ -720,13 +774,19 @@ export class ModerationService {
|
|
|
720
774
|
|
|
721
775
|
if (subject) {
|
|
722
776
|
const subjectInfo = getStatusIdentifierFromSubject(subject)
|
|
723
|
-
builder = builder
|
|
724
|
-
|
|
725
|
-
|
|
777
|
+
builder = builder.where(
|
|
778
|
+
'moderation_subject_status.did',
|
|
779
|
+
'=',
|
|
780
|
+
subjectInfo.did,
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
if (!includeAllUserRecords) {
|
|
784
|
+
builder = builder.where((qb) =>
|
|
726
785
|
subjectInfo.recordPath
|
|
727
786
|
? qb.where('recordPath', '=', subjectInfo.recordPath)
|
|
728
787
|
: qb.where('recordPath', '=', ''),
|
|
729
788
|
)
|
|
789
|
+
}
|
|
730
790
|
}
|
|
731
791
|
|
|
732
792
|
if (ignoreSubjects?.length) {
|
package/src/mod-service/views.ts
CHANGED
|
@@ -122,6 +122,16 @@ export class ModerationViews {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
if (
|
|
126
|
+
event.action === 'tools.ozone.moderation.defs#modEventTakedown' &&
|
|
127
|
+
event.meta?.acknowledgeAccountSubjects
|
|
128
|
+
) {
|
|
129
|
+
eventView.event = {
|
|
130
|
+
...eventView.event,
|
|
131
|
+
acknowledgeAccountSubjects: event.meta?.acknowledgeAccountSubjects,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
125
135
|
if (event.action === 'tools.ozone.moderation.defs#modEventLabel') {
|
|
126
136
|
eventView.event = {
|
|
127
137
|
...eventView.event,
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TestNetwork,
|
|
3
|
+
RecordRef,
|
|
4
|
+
SeedClient,
|
|
5
|
+
basicSeed,
|
|
6
|
+
ModeratorClient,
|
|
7
|
+
} from '@atproto/dev-env'
|
|
8
|
+
import {
|
|
9
|
+
REASONAPPEAL,
|
|
10
|
+
REASONOTHER,
|
|
11
|
+
REASONSPAM,
|
|
12
|
+
} from '../src/lexicon/types/com/atproto/moderation/defs'
|
|
13
|
+
import {
|
|
14
|
+
REVIEWCLOSED,
|
|
15
|
+
REVIEWESCALATED,
|
|
16
|
+
REVIEWOPEN,
|
|
17
|
+
SubjectStatusView,
|
|
18
|
+
} from '../src/lexicon/types/tools/ozone/moderation/defs'
|
|
19
|
+
import { isRepoRef } from '../src/lexicon/types/com/atproto/admin/defs'
|
|
20
|
+
import { ComAtprotoRepoStrongRef } from '@atproto/api'
|
|
21
|
+
|
|
22
|
+
describe('moderation', () => {
|
|
23
|
+
let network: TestNetwork
|
|
24
|
+
let sc: SeedClient
|
|
25
|
+
let modClient: ModeratorClient
|
|
26
|
+
|
|
27
|
+
const repoSubject = (did: string) => ({
|
|
28
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
29
|
+
did,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const recordSubject = (ref: RecordRef) => ({
|
|
33
|
+
$type: 'com.atproto.repo.strongRef',
|
|
34
|
+
uri: ref.uriStr,
|
|
35
|
+
cid: ref.cidStr,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
beforeAll(async () => {
|
|
39
|
+
network = await TestNetwork.create({
|
|
40
|
+
dbPostgresSchema: 'ozone_ack_all_subjects_of_account',
|
|
41
|
+
})
|
|
42
|
+
sc = network.getSeedClient()
|
|
43
|
+
modClient = network.ozone.getModClient()
|
|
44
|
+
await basicSeed(sc)
|
|
45
|
+
await network.processAll()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
afterAll(async () => {
|
|
49
|
+
await network.close()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('acknowledges all open/escalated review subjects.', async () => {
|
|
53
|
+
const postOne = sc.posts[sc.dids.bob][0].ref
|
|
54
|
+
const postTwo = sc.posts[sc.dids.bob][1].ref
|
|
55
|
+
await Promise.all([
|
|
56
|
+
sc.createReport({
|
|
57
|
+
reasonType: REASONSPAM,
|
|
58
|
+
subject: repoSubject(sc.dids.bob),
|
|
59
|
+
reportedBy: sc.dids.alice,
|
|
60
|
+
}),
|
|
61
|
+
sc.createReport({
|
|
62
|
+
reasonType: REASONOTHER,
|
|
63
|
+
reason: 'defamation',
|
|
64
|
+
subject: recordSubject(postOne),
|
|
65
|
+
reportedBy: sc.dids.carol,
|
|
66
|
+
}),
|
|
67
|
+
sc.createReport({
|
|
68
|
+
reasonType: REASONOTHER,
|
|
69
|
+
reason: 'defamation',
|
|
70
|
+
subject: recordSubject(postTwo),
|
|
71
|
+
reportedBy: sc.dids.carol,
|
|
72
|
+
}),
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
await modClient.emitEvent({
|
|
76
|
+
event: {
|
|
77
|
+
$type: 'tools.ozone.moderation.defs#modEventReport',
|
|
78
|
+
reportType: REASONAPPEAL,
|
|
79
|
+
},
|
|
80
|
+
subject: recordSubject(postTwo),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const { subjectStatuses: statusesBefore } = await modClient.queryStatuses({
|
|
84
|
+
subject: sc.dids.bob,
|
|
85
|
+
includeAllUserRecords: true,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
await modClient.performTakedown({
|
|
89
|
+
subject: repoSubject(sc.dids.bob),
|
|
90
|
+
acknowledgeAccountSubjects: true,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const { subjectStatuses: statusesAfter } = await modClient.queryStatuses({
|
|
94
|
+
subject: sc.dids.bob,
|
|
95
|
+
includeAllUserRecords: true,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const getReviewStateBySubject = (subjects: SubjectStatusView[]) => {
|
|
99
|
+
const states = new Map<string, SubjectStatusView>()
|
|
100
|
+
|
|
101
|
+
subjects.forEach((item) => {
|
|
102
|
+
if (ComAtprotoRepoStrongRef.isMain(item.subject)) {
|
|
103
|
+
states.set(item.subject.uri, item)
|
|
104
|
+
} else if (isRepoRef(item.subject)) {
|
|
105
|
+
states.set(item.subject.did, item)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
return states
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const reviewStatesBefore = getReviewStateBySubject(statusesBefore)
|
|
113
|
+
const reviewStatesAfter = getReviewStateBySubject(statusesAfter)
|
|
114
|
+
|
|
115
|
+
// Check that review states before were different for different subjects
|
|
116
|
+
expect(reviewStatesBefore.get(postOne.uriStr)?.reviewState).toBe(REVIEWOPEN)
|
|
117
|
+
expect(reviewStatesBefore.get(postTwo.uriStr)?.reviewState).toBe(
|
|
118
|
+
REVIEWESCALATED,
|
|
119
|
+
)
|
|
120
|
+
expect(reviewStatesBefore.get(sc.dids.bob)?.reviewState).toBe(REVIEWOPEN)
|
|
121
|
+
|
|
122
|
+
// Check that review states after are all closed
|
|
123
|
+
expect(reviewStatesAfter.get(postOne.uriStr)?.reviewState).toBe(
|
|
124
|
+
REVIEWCLOSED,
|
|
125
|
+
)
|
|
126
|
+
expect(reviewStatesAfter.get(postTwo.uriStr)?.reviewState).toBe(
|
|
127
|
+
REVIEWCLOSED,
|
|
128
|
+
)
|
|
129
|
+
expect(reviewStatesAfter.get(sc.dids.bob)?.reviewState).toBe(REVIEWCLOSED)
|
|
130
|
+
})
|
|
131
|
+
})
|