@atproto/ozone 0.0.16 → 0.0.17-next.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/dist/api/util.d.ts +10 -0
- package/dist/auth-verifier.d.ts +8 -12
- package/dist/communication-service/template.d.ts +2 -2
- package/dist/config/config.d.ts +6 -0
- package/dist/config/env.d.ts +3 -2
- package/dist/config/secrets.d.ts +0 -2
- package/dist/context.d.ts +6 -0
- package/dist/daemon/blob-diverter.d.ts +26 -0
- package/dist/daemon/event-pusher.d.ts +6 -0
- package/dist/daemon/index.d.ts +1 -0
- package/dist/db/index.js +21 -1
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20240228T003647759Z-add-label-sigs.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/schema/index.d.ts +2 -1
- package/dist/db/schema/label.d.ts +4 -0
- package/dist/db/schema/moderation_event.d.ts +1 -1
- package/dist/db/schema/moderation_subject_status.d.ts +2 -2
- package/dist/db/schema/signing_key.d.ts +9 -0
- package/dist/index.js +10400 -10313
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +55 -27
- package/dist/lexicon/lexicons.d.ts +5046 -4757
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +23 -1
- package/dist/lexicon/types/app/bsky/embed/record.d.ts +2 -1
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/graph/defs.d.ts +3 -0
- package/dist/lexicon/types/app/bsky/labeler/defs.d.ts +41 -0
- package/dist/lexicon/types/app/bsky/labeler/getServices.d.ts +36 -0
- package/dist/lexicon/types/app/bsky/labeler/service.d.ts +14 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +0 -304
- package/dist/lexicon/types/com/atproto/label/defs.d.ts +23 -0
- package/dist/lexicon/types/{com/atproto/admin/createCommunicationTemplate.d.ts → tools/ozone/communication/createTemplate.d.ts} +2 -2
- package/dist/lexicon/types/tools/ozone/communication/defs.d.ts +14 -0
- package/dist/lexicon/types/{com/atproto/admin/listCommunicationTemplates.d.ts → tools/ozone/communication/listTemplates.d.ts} +2 -2
- package/dist/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.d.ts → tools/ozone/communication/updateTemplate.d.ts} +2 -2
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +269 -0
- package/dist/lexicon/types/{com/atproto/admin/emitModerationEvent.d.ts → tools/ozone/moderation/emitEvent.d.ts} +5 -4
- package/dist/lexicon/types/{com/atproto/admin/getModerationEvent.d.ts → tools/ozone/moderation/getEvent.d.ts} +2 -2
- package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.d.ts +2 -2
- package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.d.ts +2 -2
- package/dist/lexicon/types/{com/atproto/admin/queryModerationEvents.d.ts → tools/ozone/moderation/queryEvents.d.ts} +2 -2
- package/dist/lexicon/types/{com/atproto/admin/queryModerationStatuses.d.ts → tools/ozone/moderation/queryStatuses.d.ts} +2 -2
- package/dist/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.d.ts +2 -2
- package/dist/mod-service/index.d.ts +16 -15
- package/dist/mod-service/subject.d.ts +1 -1
- package/dist/mod-service/types.d.ts +2 -2
- package/dist/mod-service/util.d.ts +6 -0
- package/dist/mod-service/views.d.ts +9 -3
- package/dist/sequencer/sequencer.d.ts +6 -4
- package/dist/util.d.ts +2 -0
- package/package.json +9 -8
- package/src/api/{admin/createCommunicationTemplate.ts → communication/createTemplate.ts} +2 -2
- package/src/api/{admin/deleteCommunicationTemplate.ts → communication/deleteTemplate.ts} +2 -2
- package/src/api/{admin/listCommunicationTemplates.ts → communication/listTemplates.ts} +2 -2
- package/src/api/{admin/updateCommunicationTemplate.ts → communication/updateTemplate.ts} +2 -2
- package/src/api/index.ts +21 -21
- package/src/api/{temp → label}/fetchLabels.ts +5 -3
- package/src/api/label/queryLabels.ts +4 -2
- package/src/api/moderation/emitEvent.ts +218 -0
- package/src/api/{admin/getModerationEvent.ts → moderation/getEvent.ts} +2 -2
- package/src/api/{admin → moderation}/getRecord.ts +3 -3
- package/src/api/{admin → moderation}/getRepo.ts +3 -3
- package/src/api/{admin/queryModerationEvents.ts → moderation/queryEvents.ts} +3 -3
- package/src/api/{admin/queryModerationStatuses.ts → moderation/queryStatuses.ts} +3 -3
- package/src/api/{admin → moderation}/searchRepos.ts +2 -2
- package/src/api/proxied.ts +8 -8
- package/src/api/{moderation → report}/createReport.ts +2 -3
- package/src/api/util.ts +119 -0
- package/src/auth-verifier.ts +20 -30
- package/src/communication-service/template.ts +2 -2
- package/src/config/config.ts +24 -7
- package/src/config/env.ts +6 -4
- package/src/config/secrets.ts +0 -6
- package/src/context.ts +36 -12
- package/src/daemon/blob-diverter.ts +150 -0
- package/src/daemon/context.ts +11 -7
- package/src/daemon/event-pusher.ts +58 -15
- package/src/daemon/index.ts +1 -0
- package/src/db/migrations/20240228T003647759Z-add-label-sigs.ts +25 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/index.ts +2 -0
- package/src/db/schema/label.ts +3 -0
- package/src/db/schema/moderation_event.ts +11 -11
- package/src/db/schema/moderation_subject_status.ts +7 -2
- package/src/db/schema/signing_key.ts +10 -0
- package/src/lexicon/index.ts +200 -137
- package/src/lexicon/lexicons.ts +6310 -6012
- package/src/lexicon/types/app/bsky/actor/defs.ts +57 -1
- package/src/lexicon/types/app/bsky/embed/record.ts +2 -0
- package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
- package/src/lexicon/types/app/bsky/graph/defs.ts +3 -0
- package/src/lexicon/types/app/bsky/labeler/defs.ts +93 -0
- package/src/lexicon/types/app/bsky/labeler/getServices.ts +51 -0
- package/src/lexicon/types/app/bsky/labeler/service.ts +31 -0
- package/src/lexicon/types/com/atproto/admin/defs.ts +0 -694
- package/src/lexicon/types/com/atproto/label/defs.ts +78 -0
- package/src/lexicon/types/{com/atproto/admin/createCommunicationTemplate.ts → tools/ozone/communication/createTemplate.ts} +2 -2
- package/src/lexicon/types/tools/ozone/communication/defs.ts +35 -0
- package/src/lexicon/types/{com/atproto/admin/listCommunicationTemplates.ts → tools/ozone/communication/listTemplates.ts} +2 -2
- package/src/lexicon/types/{com/atproto/admin/updateCommunicationTemplate.ts → tools/ozone/communication/updateTemplate.ts} +2 -2
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +641 -0
- package/src/lexicon/types/{com/atproto/admin/emitModerationEvent.ts → tools/ozone/moderation/emitEvent.ts} +15 -14
- package/src/lexicon/types/{com/atproto/admin/getModerationEvent.ts → tools/ozone/moderation/getEvent.ts} +2 -2
- package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRecord.ts +2 -2
- package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/getRepo.ts +2 -2
- package/src/lexicon/types/{com/atproto/admin/queryModerationEvents.ts → tools/ozone/moderation/queryEvents.ts} +3 -3
- package/src/lexicon/types/{com/atproto/admin/queryModerationStatuses.ts → tools/ozone/moderation/queryStatuses.ts} +2 -2
- package/src/lexicon/types/{com/atproto/admin → tools/ozone/moderation}/searchRepos.ts +2 -2
- package/src/mod-service/index.ts +46 -50
- package/src/mod-service/lang.ts +1 -1
- package/src/mod-service/status.ts +60 -41
- package/src/mod-service/subject.ts +1 -1
- package/src/mod-service/types.ts +10 -10
- package/src/mod-service/util.ts +49 -5
- package/src/mod-service/views.ts +45 -18
- package/src/sequencer/sequencer.ts +12 -11
- package/src/util.ts +21 -0
- package/tests/__snapshots__/blob-divert.test.ts.snap +22 -0
- package/tests/__snapshots__/get-record.test.ts.snap +14 -6
- package/tests/__snapshots__/get-repo.test.ts.snap +7 -3
- package/tests/__snapshots__/moderation-events.test.ts.snap +8 -8
- package/tests/__snapshots__/moderation-statuses.test.ts.snap +6 -6
- package/tests/_util.ts +5 -0
- package/tests/blob-divert.test.ts +87 -0
- package/tests/communication-templates.test.ts +33 -37
- package/tests/db.test.ts +6 -6
- package/tests/get-record.test.ts +22 -12
- package/tests/get-repo.test.ts +33 -21
- package/tests/moderation-appeals.test.ts +39 -67
- package/tests/moderation-events.test.ts +99 -142
- package/tests/moderation-status-tags.test.ts +20 -37
- package/tests/moderation-statuses.test.ts +132 -65
- package/tests/moderation.test.ts +147 -301
- package/tests/query-labels.test.ts +86 -10
- package/tests/repo-search.test.ts +18 -11
- package/tests/sequencer.test.ts +6 -3
- package/dist/api/admin/util.d.ts +0 -5
- package/dist/api/moderation/util.d.ts +0 -4
- package/src/api/admin/emitModerationEvent.ts +0 -170
- package/src/api/admin/util.ts +0 -54
- package/src/api/moderation/util.ts +0 -67
- /package/dist/api/{admin/createCommunicationTemplate.d.ts → communication/createTemplate.d.ts} +0 -0
- /package/dist/api/{admin/deleteCommunicationTemplate.d.ts → communication/deleteTemplate.d.ts} +0 -0
- /package/dist/api/{admin/emitModerationEvent.d.ts → communication/listTemplates.d.ts} +0 -0
- /package/dist/api/{admin/getModerationEvent.d.ts → communication/updateTemplate.d.ts} +0 -0
- /package/dist/api/{temp → label}/fetchLabels.d.ts +0 -0
- /package/dist/api/{admin/getRecord.d.ts → moderation/emitEvent.d.ts} +0 -0
- /package/dist/api/{admin/getRepo.d.ts → moderation/getEvent.d.ts} +0 -0
- /package/dist/api/{admin/listCommunicationTemplates.d.ts → moderation/getRecord.d.ts} +0 -0
- /package/dist/api/{admin/queryModerationEvents.d.ts → moderation/getRepo.d.ts} +0 -0
- /package/dist/api/{admin/queryModerationStatuses.d.ts → moderation/queryEvents.d.ts} +0 -0
- /package/dist/api/{admin/searchRepos.d.ts → moderation/queryStatuses.d.ts} +0 -0
- /package/dist/api/{admin/updateCommunicationTemplate.d.ts → moderation/searchRepos.d.ts} +0 -0
- /package/dist/api/{moderation → report}/createReport.d.ts +0 -0
- /package/dist/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.d.ts → tools/ozone/communication/deleteTemplate.d.ts} +0 -0
- /package/src/lexicon/types/{com/atproto/admin/deleteCommunicationTemplate.ts → tools/ozone/communication/deleteTemplate.ts} +0 -0
|
@@ -7,7 +7,7 @@ import { lexicons } from '../../../../lexicons'
|
|
|
7
7
|
import { isObj, hasProp } from '../../../../util'
|
|
8
8
|
import { CID } from 'multiformats/cid'
|
|
9
9
|
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
10
|
-
import * as
|
|
10
|
+
import * as ToolsOzoneModerationDefs from './defs'
|
|
11
11
|
|
|
12
12
|
export interface QueryParams {
|
|
13
13
|
uri: string
|
|
@@ -15,7 +15,7 @@ export interface QueryParams {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export type InputSchema = undefined
|
|
18
|
-
export type OutputSchema =
|
|
18
|
+
export type OutputSchema = ToolsOzoneModerationDefs.RecordViewDetail
|
|
19
19
|
export type HandlerInput = undefined
|
|
20
20
|
|
|
21
21
|
export interface HandlerSuccess {
|
|
@@ -7,14 +7,14 @@ import { lexicons } from '../../../../lexicons'
|
|
|
7
7
|
import { isObj, hasProp } from '../../../../util'
|
|
8
8
|
import { CID } from 'multiformats/cid'
|
|
9
9
|
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
10
|
-
import * as
|
|
10
|
+
import * as ToolsOzoneModerationDefs from './defs'
|
|
11
11
|
|
|
12
12
|
export interface QueryParams {
|
|
13
13
|
did: string
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export type InputSchema = undefined
|
|
17
|
-
export type OutputSchema =
|
|
17
|
+
export type OutputSchema = ToolsOzoneModerationDefs.RepoViewDetail
|
|
18
18
|
export type HandlerInput = undefined
|
|
19
19
|
|
|
20
20
|
export interface HandlerSuccess {
|
|
@@ -7,10 +7,10 @@ import { lexicons } from '../../../../lexicons'
|
|
|
7
7
|
import { isObj, hasProp } from '../../../../util'
|
|
8
8
|
import { CID } from 'multiformats/cid'
|
|
9
9
|
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
10
|
-
import * as
|
|
10
|
+
import * as ToolsOzoneModerationDefs from './defs'
|
|
11
11
|
|
|
12
12
|
export interface QueryParams {
|
|
13
|
-
/** The types of events (fully qualified string in the format of
|
|
13
|
+
/** The types of events (fully qualified string in the format of tools.ozone.moderation.defs#modEvent<name>) to filter by. If not specified, all events are returned. */
|
|
14
14
|
types?: string[]
|
|
15
15
|
createdBy?: string
|
|
16
16
|
/** Sort direction for the events. Defaults to descending order of created at timestamp. */
|
|
@@ -43,7 +43,7 @@ export type InputSchema = undefined
|
|
|
43
43
|
|
|
44
44
|
export interface OutputSchema {
|
|
45
45
|
cursor?: string
|
|
46
|
-
events:
|
|
46
|
+
events: ToolsOzoneModerationDefs.ModEventView[]
|
|
47
47
|
[k: string]: unknown
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -7,7 +7,7 @@ import { lexicons } from '../../../../lexicons'
|
|
|
7
7
|
import { isObj, hasProp } from '../../../../util'
|
|
8
8
|
import { CID } from 'multiformats/cid'
|
|
9
9
|
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
10
|
-
import * as
|
|
10
|
+
import * as ToolsOzoneModerationDefs from './defs'
|
|
11
11
|
|
|
12
12
|
export interface QueryParams {
|
|
13
13
|
subject?: string
|
|
@@ -44,7 +44,7 @@ export type InputSchema = undefined
|
|
|
44
44
|
|
|
45
45
|
export interface OutputSchema {
|
|
46
46
|
cursor?: string
|
|
47
|
-
subjectStatuses:
|
|
47
|
+
subjectStatuses: ToolsOzoneModerationDefs.SubjectStatusView[]
|
|
48
48
|
[k: string]: unknown
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -7,7 +7,7 @@ import { lexicons } from '../../../../lexicons'
|
|
|
7
7
|
import { isObj, hasProp } from '../../../../util'
|
|
8
8
|
import { CID } from 'multiformats/cid'
|
|
9
9
|
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
10
|
-
import * as
|
|
10
|
+
import * as ToolsOzoneModerationDefs from './defs'
|
|
11
11
|
|
|
12
12
|
export interface QueryParams {
|
|
13
13
|
/** DEPRECATED: use 'q' instead */
|
|
@@ -21,7 +21,7 @@ export type InputSchema = undefined
|
|
|
21
21
|
|
|
22
22
|
export interface OutputSchema {
|
|
23
23
|
cursor?: string
|
|
24
|
-
repos:
|
|
24
|
+
repos: ToolsOzoneModerationDefs.RepoView[]
|
|
25
25
|
[k: string]: unknown
|
|
26
26
|
}
|
|
27
27
|
|
package/src/mod-service/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { CID } from 'multiformats/cid'
|
|
|
4
4
|
import { AtUri, INVALID_HANDLE } from '@atproto/syntax'
|
|
5
5
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
6
6
|
import { addHoursToDate } from '@atproto/common'
|
|
7
|
+
import { Keypair } from '@atproto/crypto'
|
|
7
8
|
import { IdResolver } from '@atproto/identity'
|
|
8
9
|
import AtpAgent from '@atproto/api'
|
|
9
10
|
import { Database } from '../db'
|
|
@@ -17,9 +18,8 @@ import {
|
|
|
17
18
|
isModEventTakedown,
|
|
18
19
|
isModEventEmail,
|
|
19
20
|
isModEventTag,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from '../lexicon/types/com/atproto/admin/defs'
|
|
21
|
+
} from '../lexicon/types/tools/ozone/moderation/defs'
|
|
22
|
+
import { RepoRef, RepoBlobRef } from '../lexicon/types/com/atproto/admin/defs'
|
|
23
23
|
import {
|
|
24
24
|
adjustModerationSubjectStatus,
|
|
25
25
|
getStatusIdentifierFromSubject,
|
|
@@ -46,6 +46,7 @@ import { LabelChannel } from '../db/schema/label'
|
|
|
46
46
|
import { BlobPushEvent } from '../db/schema/blob_push_event'
|
|
47
47
|
import { BackgroundQueue } from '../background'
|
|
48
48
|
import { EventPusher } from '../daemon'
|
|
49
|
+
import { formatLabel, formatLabelRow, signLabel } from './util'
|
|
49
50
|
import { ImageInvalidator } from '../image-invalidator'
|
|
50
51
|
import { httpLogger as log } from '../logger'
|
|
51
52
|
import { OzoneConfig } from '../config'
|
|
@@ -55,45 +56,49 @@ export type ModerationServiceCreator = (db: Database) => ModerationService
|
|
|
55
56
|
export class ModerationService {
|
|
56
57
|
constructor(
|
|
57
58
|
public db: Database,
|
|
59
|
+
public signingKey: Keypair,
|
|
60
|
+
public signingKeyId: number,
|
|
58
61
|
public cfg: OzoneConfig,
|
|
59
62
|
public backgroundQueue: BackgroundQueue,
|
|
60
63
|
public idResolver: IdResolver,
|
|
61
64
|
public eventPusher: EventPusher,
|
|
62
65
|
public appviewAgent: AtpAgent,
|
|
63
66
|
private createAuthHeaders: (aud: string) => Promise<AuthHeaders>,
|
|
64
|
-
public serverDid: string,
|
|
65
67
|
public imgInvalidator?: ImageInvalidator,
|
|
66
|
-
public cdnPaths?: string[],
|
|
67
68
|
) {}
|
|
68
69
|
|
|
69
70
|
static creator(
|
|
71
|
+
signingKey: Keypair,
|
|
72
|
+
signingKeyId: number,
|
|
70
73
|
cfg: OzoneConfig,
|
|
71
74
|
backgroundQueue: BackgroundQueue,
|
|
72
75
|
idResolver: IdResolver,
|
|
73
76
|
eventPusher: EventPusher,
|
|
74
77
|
appviewAgent: AtpAgent,
|
|
75
78
|
createAuthHeaders: (aud: string) => Promise<AuthHeaders>,
|
|
76
|
-
serverDid: string,
|
|
77
79
|
imgInvalidator?: ImageInvalidator,
|
|
78
|
-
cdnPaths?: string[],
|
|
79
80
|
) {
|
|
80
81
|
return (db: Database) =>
|
|
81
82
|
new ModerationService(
|
|
82
83
|
db,
|
|
84
|
+
signingKey,
|
|
85
|
+
signingKeyId,
|
|
83
86
|
cfg,
|
|
84
87
|
backgroundQueue,
|
|
85
88
|
idResolver,
|
|
86
89
|
eventPusher,
|
|
87
90
|
appviewAgent,
|
|
88
91
|
createAuthHeaders,
|
|
89
|
-
serverDid,
|
|
90
92
|
imgInvalidator,
|
|
91
|
-
cdnPaths,
|
|
92
93
|
)
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
views = new ModerationViews(
|
|
96
|
-
this.
|
|
96
|
+
views = new ModerationViews(
|
|
97
|
+
this.db,
|
|
98
|
+
this.signingKey,
|
|
99
|
+
this.signingKeyId,
|
|
100
|
+
this.appviewAgent,
|
|
101
|
+
() => this.createAuthHeaders(this.cfg.appview.did),
|
|
97
102
|
)
|
|
98
103
|
|
|
99
104
|
async getEvent(id: number): Promise<ModerationEventRow | undefined> {
|
|
@@ -247,7 +252,7 @@ export class ModerationService {
|
|
|
247
252
|
async getReport(id: number): Promise<ModerationEventRow | undefined> {
|
|
248
253
|
return await this.db.db
|
|
249
254
|
.selectFrom('moderation_event')
|
|
250
|
-
.where('action', '=', '
|
|
255
|
+
.where('action', '=', 'tools.ozone.moderation.defs#modEventReport')
|
|
251
256
|
.selectAll()
|
|
252
257
|
.where('id', '=', id)
|
|
253
258
|
.executeTakeFirst()
|
|
@@ -370,12 +375,12 @@ export class ModerationService {
|
|
|
370
375
|
// Means the subject was suspended and needs to be unsuspended
|
|
371
376
|
if (subject.reverseSuspend) {
|
|
372
377
|
builder = builder
|
|
373
|
-
.where('action', '=', '
|
|
378
|
+
.where('action', '=', 'tools.ozone.moderation.defs#modEventTakedown')
|
|
374
379
|
.where('durationInHours', 'is not', null)
|
|
375
380
|
}
|
|
376
381
|
if (subject.reverseMute) {
|
|
377
382
|
builder = builder
|
|
378
|
-
.where('action', '=', '
|
|
383
|
+
.where('action', '=', 'tools.ozone.moderation.defs#modEventMute')
|
|
379
384
|
.where('durationInHours', 'is not', null)
|
|
380
385
|
}
|
|
381
386
|
|
|
@@ -422,13 +427,13 @@ export class ModerationService {
|
|
|
422
427
|
subject,
|
|
423
428
|
}: ReversibleModerationEvent): Promise<ModerationEventRow> {
|
|
424
429
|
const isRevertingTakedown =
|
|
425
|
-
action === '
|
|
430
|
+
action === 'tools.ozone.moderation.defs#modEventTakedown'
|
|
426
431
|
this.db.assertTransaction()
|
|
427
432
|
const { event } = await this.logEvent({
|
|
428
433
|
event: {
|
|
429
434
|
$type: isRevertingTakedown
|
|
430
|
-
? '
|
|
431
|
-
: '
|
|
435
|
+
? 'tools.ozone.moderation.defs#modEventReverseTakedown'
|
|
436
|
+
: 'tools.ozone.moderation.defs#modEventUnmute',
|
|
432
437
|
comment: comment ?? undefined,
|
|
433
438
|
},
|
|
434
439
|
createdAt,
|
|
@@ -455,7 +460,8 @@ export class ModerationService {
|
|
|
455
460
|
const takedownRef = `BSKY-${
|
|
456
461
|
isSuspend ? 'SUSPEND' : 'TAKEDOWN'
|
|
457
462
|
}-${takedownId}`
|
|
458
|
-
|
|
463
|
+
|
|
464
|
+
const values = this.eventPusher.takedowns.map((eventType) => ({
|
|
459
465
|
eventType,
|
|
460
466
|
subjectDid: subject.did,
|
|
461
467
|
takedownRef,
|
|
@@ -516,7 +522,7 @@ export class ModerationService {
|
|
|
516
522
|
async takedownRecord(subject: RecordSubject, takedownId: number) {
|
|
517
523
|
this.db.assertTransaction()
|
|
518
524
|
const takedownRef = `BSKY-TAKEDOWN-${takedownId}`
|
|
519
|
-
const values =
|
|
525
|
+
const values = this.eventPusher.takedowns.map((eventType) => ({
|
|
520
526
|
eventType,
|
|
521
527
|
subjectDid: subject.did,
|
|
522
528
|
subjectUri: subject.uri,
|
|
@@ -555,31 +561,21 @@ export class ModerationService {
|
|
|
555
561
|
|
|
556
562
|
if (blobCids && blobCids.length > 0) {
|
|
557
563
|
const blobValues: Insertable<BlobPushEvent>[] = []
|
|
558
|
-
for (const eventType of
|
|
564
|
+
for (const eventType of this.eventPusher.takedowns) {
|
|
559
565
|
for (const cid of blobCids) {
|
|
560
566
|
blobValues.push({
|
|
561
567
|
eventType,
|
|
568
|
+
takedownRef,
|
|
562
569
|
subjectDid: subject.did,
|
|
570
|
+
subjectUri: subject.uri || null,
|
|
563
571
|
subjectBlobCid: cid.toString(),
|
|
564
|
-
takedownRef,
|
|
565
572
|
})
|
|
566
573
|
}
|
|
567
574
|
}
|
|
568
|
-
const blobEvts = await this.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
oc
|
|
573
|
-
.columns(['subjectDid', 'subjectBlobCid', 'eventType'])
|
|
574
|
-
.doUpdateSet({
|
|
575
|
-
takedownRef,
|
|
576
|
-
confirmedAt: null,
|
|
577
|
-
attempts: 0,
|
|
578
|
-
lastAttempted: null,
|
|
579
|
-
}),
|
|
580
|
-
)
|
|
581
|
-
.returning(['id', 'subjectDid', 'subjectBlobCid', 'eventType'])
|
|
582
|
-
.execute()
|
|
575
|
+
const blobEvts = await this.eventPusher.logBlobPushEvent(
|
|
576
|
+
blobValues,
|
|
577
|
+
takedownRef,
|
|
578
|
+
)
|
|
583
579
|
|
|
584
580
|
this.db.onCommit(() => {
|
|
585
581
|
this.backgroundQueue.add(async () => {
|
|
@@ -596,7 +592,7 @@ export class ModerationService {
|
|
|
596
592
|
if (this.imgInvalidator) {
|
|
597
593
|
await Promise.allSettled(
|
|
598
594
|
(subject.blobCids ?? []).map((cid) => {
|
|
599
|
-
const paths = (this.
|
|
595
|
+
const paths = (this.cfg.cdn.paths ?? []).map((path) =>
|
|
600
596
|
path.replace('%s', subject.did).replace('%s', cid),
|
|
601
597
|
)
|
|
602
598
|
return this.imgInvalidator
|
|
@@ -697,7 +693,7 @@ export class ModerationService {
|
|
|
697
693
|
|
|
698
694
|
const result = await this.logEvent({
|
|
699
695
|
event: {
|
|
700
|
-
$type: '
|
|
696
|
+
$type: 'tools.ozone.moderation.defs#modEventReport',
|
|
701
697
|
reportType: reasonType,
|
|
702
698
|
comment: reason,
|
|
703
699
|
},
|
|
@@ -875,7 +871,7 @@ export class ModerationService {
|
|
|
875
871
|
): Promise<Label[]> {
|
|
876
872
|
const { create = [], negate = [] } = labels
|
|
877
873
|
const toCreate = create.map((val) => ({
|
|
878
|
-
src: this.
|
|
874
|
+
src: this.cfg.service.did,
|
|
879
875
|
uri,
|
|
880
876
|
cid: cid ?? undefined,
|
|
881
877
|
val,
|
|
@@ -883,7 +879,7 @@ export class ModerationService {
|
|
|
883
879
|
cts: new Date().toISOString(),
|
|
884
880
|
}))
|
|
885
881
|
const toNegate = negate.map((val) => ({
|
|
886
|
-
src: this.
|
|
882
|
+
src: this.cfg.service.did,
|
|
887
883
|
uri,
|
|
888
884
|
cid: cid ?? undefined,
|
|
889
885
|
val,
|
|
@@ -891,21 +887,19 @@ export class ModerationService {
|
|
|
891
887
|
cts: new Date().toISOString(),
|
|
892
888
|
}))
|
|
893
889
|
const formatted = [...toCreate, ...toNegate]
|
|
894
|
-
|
|
895
|
-
return formatted
|
|
890
|
+
return this.createLabels(formatted)
|
|
896
891
|
}
|
|
897
892
|
|
|
898
|
-
async createLabels(labels: Label[]) {
|
|
899
|
-
if (labels.length < 1) return
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}))
|
|
893
|
+
async createLabels(labels: Label[]): Promise<Label[]> {
|
|
894
|
+
if (labels.length < 1) return []
|
|
895
|
+
const signedLabels = await Promise.all(
|
|
896
|
+
labels.map((l) => signLabel(l, this.signingKey)),
|
|
897
|
+
)
|
|
898
|
+
const dbVals = signedLabels.map((l) => formatLabelRow(l, this.signingKeyId))
|
|
905
899
|
const { ref } = this.db.db.dynamic
|
|
906
900
|
await sql`notify ${ref(LabelChannel)}`.execute(this.db.db)
|
|
907
901
|
const excluded = (col: string) => ref(`excluded.${col}`)
|
|
908
|
-
await this.db.db
|
|
902
|
+
const res = await this.db.db
|
|
909
903
|
.insertInto('label')
|
|
910
904
|
.values(dbVals)
|
|
911
905
|
.onConflict((oc) =>
|
|
@@ -915,7 +909,9 @@ export class ModerationService {
|
|
|
915
909
|
cts: sql`${excluded('cts')}`,
|
|
916
910
|
}),
|
|
917
911
|
)
|
|
912
|
+
.returningAll()
|
|
918
913
|
.execute()
|
|
914
|
+
return res.map((row) => formatLabel(row))
|
|
919
915
|
}
|
|
920
916
|
|
|
921
917
|
async sendEmail(opts: {
|
package/src/mod-service/lang.ts
CHANGED
|
@@ -25,7 +25,7 @@ export class ModerationLangService {
|
|
|
25
25
|
})
|
|
26
26
|
await this.moderationService.logEvent({
|
|
27
27
|
event: {
|
|
28
|
-
$type: '
|
|
28
|
+
$type: 'tools.ozone.moderation.defs#modEventTag',
|
|
29
29
|
add: recordLangs
|
|
30
30
|
? recordLangs.map((lang) => `lang:${lang}`)
|
|
31
31
|
: ['lang:und'],
|
|
@@ -7,42 +7,49 @@ import {
|
|
|
7
7
|
REVIEWOPEN,
|
|
8
8
|
REVIEWCLOSED,
|
|
9
9
|
REVIEWESCALATED,
|
|
10
|
-
|
|
10
|
+
REVIEWNONE,
|
|
11
|
+
} from '../lexicon/types/tools/ozone/moderation/defs'
|
|
11
12
|
import { ModerationEventRow, ModerationSubjectStatusRow } from './types'
|
|
12
13
|
import { HOUR } from '@atproto/common'
|
|
13
14
|
import { REASONAPPEAL } from '../lexicon/types/com/atproto/moderation/defs'
|
|
14
15
|
import { jsonb } from '../db/types'
|
|
15
16
|
|
|
16
17
|
const getSubjectStatusForModerationEvent = ({
|
|
18
|
+
currentStatus,
|
|
17
19
|
action,
|
|
18
20
|
createdBy,
|
|
19
21
|
createdAt,
|
|
20
22
|
durationInHours,
|
|
21
23
|
}: {
|
|
24
|
+
currentStatus?: ModerationSubjectStatusRow
|
|
22
25
|
action: string
|
|
23
26
|
createdBy: string
|
|
24
27
|
createdAt: string
|
|
25
28
|
durationInHours: number | null
|
|
26
|
-
}): Partial<ModerationSubjectStatusRow>
|
|
29
|
+
}): Partial<ModerationSubjectStatusRow> => {
|
|
30
|
+
const defaultReviewState = currentStatus
|
|
31
|
+
? currentStatus.reviewState
|
|
32
|
+
: REVIEWNONE
|
|
33
|
+
|
|
27
34
|
switch (action) {
|
|
28
|
-
case '
|
|
35
|
+
case 'tools.ozone.moderation.defs#modEventAcknowledge':
|
|
29
36
|
return {
|
|
30
37
|
lastReviewedBy: createdBy,
|
|
31
38
|
reviewState: REVIEWCLOSED,
|
|
32
39
|
lastReviewedAt: createdAt,
|
|
33
40
|
}
|
|
34
|
-
case '
|
|
41
|
+
case 'tools.ozone.moderation.defs#modEventReport':
|
|
35
42
|
return {
|
|
36
43
|
reviewState: REVIEWOPEN,
|
|
37
44
|
lastReportedAt: createdAt,
|
|
38
45
|
}
|
|
39
|
-
case '
|
|
46
|
+
case 'tools.ozone.moderation.defs#modEventEscalate':
|
|
40
47
|
return {
|
|
41
48
|
lastReviewedBy: createdBy,
|
|
42
49
|
reviewState: REVIEWESCALATED,
|
|
43
50
|
lastReviewedAt: createdAt,
|
|
44
51
|
}
|
|
45
|
-
case '
|
|
52
|
+
case 'tools.ozone.moderation.defs#modEventReverseTakedown':
|
|
46
53
|
return {
|
|
47
54
|
lastReviewedBy: createdBy,
|
|
48
55
|
reviewState: REVIEWCLOSED,
|
|
@@ -50,14 +57,16 @@ const getSubjectStatusForModerationEvent = ({
|
|
|
50
57
|
suspendUntil: null,
|
|
51
58
|
lastReviewedAt: createdAt,
|
|
52
59
|
}
|
|
53
|
-
case '
|
|
60
|
+
case 'tools.ozone.moderation.defs#modEventUnmute':
|
|
54
61
|
return {
|
|
55
62
|
lastReviewedBy: createdBy,
|
|
56
63
|
muteUntil: null,
|
|
57
|
-
|
|
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,
|
|
58
67
|
lastReviewedAt: createdAt,
|
|
59
68
|
}
|
|
60
|
-
case '
|
|
69
|
+
case 'tools.ozone.moderation.defs#modEventTakedown':
|
|
61
70
|
return {
|
|
62
71
|
takendown: true,
|
|
63
72
|
lastReviewedBy: createdBy,
|
|
@@ -67,29 +76,32 @@ const getSubjectStatusForModerationEvent = ({
|
|
|
67
76
|
? new Date(Date.now() + durationInHours * HOUR).toISOString()
|
|
68
77
|
: null,
|
|
69
78
|
}
|
|
70
|
-
case '
|
|
79
|
+
case 'tools.ozone.moderation.defs#modEventMute':
|
|
71
80
|
return {
|
|
72
81
|
lastReviewedBy: createdBy,
|
|
73
|
-
reviewState: REVIEWOPEN,
|
|
74
82
|
lastReviewedAt: createdAt,
|
|
75
83
|
// By default, mute for 24hrs
|
|
76
84
|
muteUntil: new Date(
|
|
77
85
|
Date.now() + (durationInHours || 24) * HOUR,
|
|
78
86
|
).toISOString(),
|
|
87
|
+
// It's not likely to receive a mute event on a subject that does not already have a status row
|
|
88
|
+
// but if it does happen, default to unnecessary
|
|
89
|
+
reviewState: defaultReviewState,
|
|
79
90
|
}
|
|
80
|
-
case '
|
|
91
|
+
case 'tools.ozone.moderation.defs#modEventComment':
|
|
81
92
|
return {
|
|
82
93
|
lastReviewedBy: createdBy,
|
|
83
94
|
lastReviewedAt: createdAt,
|
|
95
|
+
reviewState: defaultReviewState,
|
|
84
96
|
}
|
|
85
|
-
case '
|
|
86
|
-
return { tags: [] }
|
|
87
|
-
case '
|
|
97
|
+
case 'tools.ozone.moderation.defs#modEventTag':
|
|
98
|
+
return { tags: [], reviewState: defaultReviewState }
|
|
99
|
+
case 'tools.ozone.moderation.defs#modEventResolveAppeal':
|
|
88
100
|
return {
|
|
89
101
|
appealed: false,
|
|
90
102
|
}
|
|
91
103
|
default:
|
|
92
|
-
return
|
|
104
|
+
return {}
|
|
93
105
|
}
|
|
94
106
|
}
|
|
95
107
|
|
|
@@ -114,23 +126,6 @@ export const adjustModerationSubjectStatus = async (
|
|
|
114
126
|
createdAt,
|
|
115
127
|
} = moderationEvent
|
|
116
128
|
|
|
117
|
-
const isAppealEvent =
|
|
118
|
-
action === 'com.atproto.admin.defs#modEventReport' &&
|
|
119
|
-
meta?.reportType === REASONAPPEAL
|
|
120
|
-
|
|
121
|
-
const subjectStatus = getSubjectStatusForModerationEvent({
|
|
122
|
-
action,
|
|
123
|
-
createdBy,
|
|
124
|
-
createdAt,
|
|
125
|
-
durationInHours: moderationEvent.durationInHours,
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// If there are no subjectStatus that means there are no side-effect of the incoming event
|
|
129
|
-
if (!subjectStatus) {
|
|
130
|
-
return null
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const now = new Date().toISOString()
|
|
134
129
|
// If subjectUri exists, it's not a repoRef so pass along the uri to get identifier back
|
|
135
130
|
const identifier = getStatusIdentifierFromSubject(subjectUri || subjectDid)
|
|
136
131
|
|
|
@@ -140,25 +135,46 @@ export const adjustModerationSubjectStatus = async (
|
|
|
140
135
|
.selectFrom('moderation_subject_status')
|
|
141
136
|
.where('did', '=', identifier.did)
|
|
142
137
|
.where('recordPath', '=', identifier.recordPath)
|
|
138
|
+
// Make sure we respect other updates that may be happening at the same time
|
|
139
|
+
.forUpdate()
|
|
143
140
|
.selectAll()
|
|
144
141
|
.executeTakeFirst()
|
|
145
142
|
|
|
143
|
+
const isAppealEvent =
|
|
144
|
+
action === 'tools.ozone.moderation.defs#modEventReport' &&
|
|
145
|
+
meta?.reportType === REASONAPPEAL
|
|
146
|
+
|
|
147
|
+
const subjectStatus = getSubjectStatusForModerationEvent({
|
|
148
|
+
currentStatus,
|
|
149
|
+
action,
|
|
150
|
+
createdBy,
|
|
151
|
+
createdAt,
|
|
152
|
+
durationInHours: moderationEvent.durationInHours,
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const now = new Date().toISOString()
|
|
146
156
|
if (
|
|
147
157
|
currentStatus?.reviewState === REVIEWESCALATED &&
|
|
148
|
-
subjectStatus.reviewState
|
|
158
|
+
subjectStatus.reviewState !== REVIEWCLOSED
|
|
149
159
|
) {
|
|
150
|
-
// If the current status is escalated
|
|
151
|
-
//
|
|
160
|
+
// If the current status is escalated only allow incoming events to move the state to
|
|
161
|
+
// reviewClosed because escalated subjects should never move to any other state
|
|
152
162
|
subjectStatus.reviewState = REVIEWESCALATED
|
|
153
163
|
}
|
|
154
164
|
|
|
165
|
+
if (currentStatus && subjectStatus.reviewState === REVIEWNONE) {
|
|
166
|
+
// reviewNone is ONLY allowed when there is no current status
|
|
167
|
+
// If there is a current status, it should not be allowed to move back to reviewNone
|
|
168
|
+
subjectStatus.reviewState = currentStatus.reviewState
|
|
169
|
+
}
|
|
170
|
+
|
|
155
171
|
// Set these because we don't want to override them if they're already set
|
|
156
172
|
const defaultData = {
|
|
157
173
|
comment: null,
|
|
158
174
|
// Defaulting reviewState to open for any event may not be the desired behavior.
|
|
159
175
|
// For instance, if a subject never had any event and we just want to leave a comment to keep an eye on it
|
|
160
176
|
// that shouldn't mean we want to review the subject
|
|
161
|
-
reviewState:
|
|
177
|
+
reviewState: REVIEWNONE,
|
|
162
178
|
recordCid: subjectCid || null,
|
|
163
179
|
}
|
|
164
180
|
const newStatus = {
|
|
@@ -167,7 +183,7 @@ export const adjustModerationSubjectStatus = async (
|
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
if (
|
|
170
|
-
action === '
|
|
186
|
+
action === 'tools.ozone.moderation.defs#modEventReverseTakedown' &&
|
|
171
187
|
!subjectStatus.takendown
|
|
172
188
|
) {
|
|
173
189
|
newStatus.takendown = false
|
|
@@ -185,19 +201,22 @@ export const adjustModerationSubjectStatus = async (
|
|
|
185
201
|
}
|
|
186
202
|
|
|
187
203
|
if (
|
|
188
|
-
action === '
|
|
204
|
+
action === 'tools.ozone.moderation.defs#modEventResolveAppeal' &&
|
|
189
205
|
subjectStatus.appealed
|
|
190
206
|
) {
|
|
191
207
|
newStatus.appealed = false
|
|
192
208
|
subjectStatus.appealed = false
|
|
193
209
|
}
|
|
194
210
|
|
|
195
|
-
if (
|
|
211
|
+
if (
|
|
212
|
+
action === 'tools.ozone.moderation.defs#modEventComment' &&
|
|
213
|
+
meta?.sticky
|
|
214
|
+
) {
|
|
196
215
|
newStatus.comment = comment
|
|
197
216
|
subjectStatus.comment = comment
|
|
198
217
|
}
|
|
199
218
|
|
|
200
|
-
if (action === '
|
|
219
|
+
if (action === 'tools.ozone.moderation.defs#modEventTag') {
|
|
201
220
|
let tags = currentStatus?.tags || []
|
|
202
221
|
if (addedTags?.length) {
|
|
203
222
|
tags = tags.concat(addedTags)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AtUri } from '@atproto/syntax'
|
|
2
2
|
import { InputSchema as ReportInput } from '../lexicon/types/com/atproto/moderation/createReport'
|
|
3
|
-
import { InputSchema as ActionInput } from '../lexicon/types/
|
|
3
|
+
import { InputSchema as ActionInput } from '../lexicon/types/tools/ozone/moderation/emitEvent'
|
|
4
4
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
5
5
|
import { ModerationEventRow, ModerationSubjectStatusRow } from './types'
|
|
6
6
|
import { RepoRef } from '../lexicon/types/com/atproto/admin/defs'
|
package/src/mod-service/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Selectable } from 'kysely'
|
|
2
2
|
import { ModerationEvent } from '../db/schema/moderation_event'
|
|
3
3
|
import { ModerationSubjectStatus } from '../db/schema/moderation_subject_status'
|
|
4
|
-
import {
|
|
4
|
+
import { ToolsOzoneModerationDefs } from '@atproto/api'
|
|
5
5
|
import { ModSubject } from './subject'
|
|
6
6
|
|
|
7
7
|
export type ModerationEventRow = Selectable<ModerationEvent>
|
|
@@ -22,15 +22,15 @@ export type ModerationSubjectStatusRowWithHandle =
|
|
|
22
22
|
ModerationSubjectStatusRow & { handle: string | null }
|
|
23
23
|
|
|
24
24
|
export type ModEventType =
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
25
|
+
| ToolsOzoneModerationDefs.ModEventTakedown
|
|
26
|
+
| ToolsOzoneModerationDefs.ModEventAcknowledge
|
|
27
|
+
| ToolsOzoneModerationDefs.ModEventEscalate
|
|
28
|
+
| ToolsOzoneModerationDefs.ModEventComment
|
|
29
|
+
| ToolsOzoneModerationDefs.ModEventLabel
|
|
30
|
+
| ToolsOzoneModerationDefs.ModEventReport
|
|
31
|
+
| ToolsOzoneModerationDefs.ModEventMute
|
|
32
|
+
| ToolsOzoneModerationDefs.ModEventReverseTakedown
|
|
33
|
+
| ToolsOzoneModerationDefs.ModEventTag
|
|
34
34
|
|
|
35
35
|
export const UNSPECCED_TAKEDOWN_LABEL = '!unspecced-takedown'
|
|
36
36
|
|