@atproto/bsky 0.0.15 → 0.0.16
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 +11 -0
- package/dist/api/com/atproto/moderation/util.d.ts +4 -3
- package/dist/context.d.ts +15 -0
- package/dist/db/index.js +26 -1
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/pagination.d.ts +2 -1
- package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
- package/dist/db/tables/moderation.d.ts +24 -34
- package/dist/feed-gen/types.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2750 -2121
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +11 -18
- package/dist/lexicon/lexicons.d.ts +414 -399
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
- package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +114 -48
- package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
- package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
- package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
- package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
- package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
- package/dist/migrate-moderation-data.d.ts +1 -0
- package/dist/services/actor/views.d.ts +2 -5
- package/dist/services/feed/index.d.ts +1 -0
- package/dist/services/feed/util.d.ts +9 -1
- package/dist/services/feed/views.d.ts +6 -17
- package/dist/services/graph/index.d.ts +5 -29
- package/dist/services/graph/types.d.ts +1 -0
- package/dist/services/moderation/index.d.ts +135 -72
- package/dist/services/moderation/pagination.d.ts +36 -0
- package/dist/services/moderation/status.d.ts +13 -0
- package/dist/services/moderation/types.d.ts +35 -0
- package/dist/services/moderation/views.d.ts +18 -14
- package/dist/util/debug.d.ts +1 -1
- package/package.json +11 -11
- package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
- package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
- package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
- package/src/api/app/bsky/feed/getFeed.ts +9 -9
- package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
- package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
- package/src/api/app/bsky/feed/getListFeed.ts +1 -3
- package/src/api/app/bsky/feed/getPostThread.ts +15 -54
- package/src/api/app/bsky/feed/getPosts.ts +21 -18
- package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
- package/src/api/app/bsky/feed/getTimeline.ts +1 -3
- package/src/api/app/bsky/feed/searchPosts.ts +20 -17
- package/src/api/app/bsky/graph/getList.ts +6 -3
- package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
- package/src/api/app/bsky/graph/getListMutes.ts +2 -1
- package/src/api/app/bsky/graph/getLists.ts +2 -1
- package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
- package/src/api/blob-resolver.ts +6 -11
- package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
- package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
- package/src/api/com/atproto/admin/getRecord.ts +1 -0
- package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
- package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
- package/src/api/com/atproto/moderation/createReport.ts +9 -7
- package/src/api/com/atproto/moderation/util.ts +38 -20
- package/src/api/index.ts +8 -14
- package/src/auth.ts +29 -21
- package/src/auto-moderator/index.ts +26 -19
- package/src/context.ts +4 -0
- package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/pagination.ts +26 -3
- package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +50 -46
- package/src/db/tables/moderation.ts +35 -52
- package/src/feed-gen/best-of-follows.ts +6 -3
- package/src/feed-gen/bsky-team.ts +1 -1
- package/src/feed-gen/hot-classic.ts +1 -1
- package/src/feed-gen/mutuals.ts +6 -2
- package/src/feed-gen/types.ts +1 -1
- package/src/feed-gen/whats-hot.ts +1 -1
- package/src/feed-gen/with-friends.ts +7 -3
- package/src/index.ts +2 -1
- package/src/lexicon/index.ts +30 -67
- package/src/lexicon/lexicons.ts +526 -491
- package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
- package/src/lexicon/types/app/bsky/graph/defs.ts +1 -0
- package/src/lexicon/types/com/atproto/admin/defs.ts +276 -84
- package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
- package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
- package/src/lexicon/types/com/atproto/admin/{getModerationActions.ts → queryModerationEvents.ts} +8 -1
- package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationStatuses.ts} +21 -14
- package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
- package/src/migrate-moderation-data.ts +414 -0
- package/src/services/actor/views.ts +5 -14
- package/src/services/feed/index.ts +26 -7
- package/src/services/feed/util.ts +47 -19
- package/src/services/feed/views.ts +68 -4
- package/src/services/graph/index.ts +21 -3
- package/src/services/graph/types.ts +1 -0
- package/src/services/indexing/plugins/block.ts +2 -3
- package/src/services/indexing/plugins/feed-generator.ts +2 -3
- package/src/services/indexing/plugins/follow.ts +2 -3
- package/src/services/indexing/plugins/like.ts +2 -3
- package/src/services/indexing/plugins/list-block.ts +2 -3
- package/src/services/indexing/plugins/list-item.ts +2 -3
- package/src/services/indexing/plugins/list.ts +2 -3
- package/src/services/indexing/plugins/post.ts +3 -4
- package/src/services/indexing/plugins/repost.ts +2 -3
- package/src/services/indexing/plugins/thread-gate.ts +2 -3
- package/src/services/label/index.ts +2 -3
- package/src/services/moderation/index.ts +380 -395
- package/src/services/moderation/pagination.ts +96 -0
- package/src/services/moderation/status.ts +244 -0
- package/src/services/moderation/types.ts +49 -0
- package/src/services/moderation/views.ts +278 -329
- package/src/util/debug.ts +2 -2
- package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
- package/tests/__snapshots__/indexing.test.ts.snap +0 -6
- package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
- package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
- package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
- package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
- package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
- package/tests/admin/get-record.test.ts +5 -9
- package/tests/admin/get-repo.test.ts +5 -9
- package/tests/admin/moderation-events.test.ts +221 -0
- package/tests/admin/moderation-statuses.test.ts +145 -0
- package/tests/admin/moderation.test.ts +512 -860
- package/tests/admin/repo-search.test.ts +2 -3
- package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
- package/tests/auto-moderator/takedowns.test.ts +45 -18
- package/tests/feed-generation.test.ts +57 -9
- package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
- package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
- package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
- package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
- package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
- package/tests/views/actor-search.test.ts +2 -3
- package/tests/views/author-feed.test.ts +42 -36
- package/tests/views/follows.test.ts +40 -35
- package/tests/views/list-feed.test.ts +17 -9
- package/tests/views/notifications.test.ts +13 -9
- package/tests/views/profile.test.ts +20 -18
- package/tests/views/thread.test.ts +54 -26
- package/tests/views/threadgating.test.ts +51 -19
- package/tests/views/timeline.test.ts +21 -13
- package/dist/api/com/atproto/admin/resolveModerationReports.d.ts +0 -3
- package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
- package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
- package/dist/lexicon/types/com/atproto/admin/getModerationReport.d.ts +0 -29
- package/dist/lexicon/types/com/atproto/admin/resolveModerationReports.d.ts +0 -36
- package/dist/lexicon/types/com/atproto/admin/reverseModerationAction.d.ts +0 -36
- package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
- package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
- package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
- package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
- package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
- package/src/lexicon/types/com/atproto/admin/getModerationAction.ts +0 -41
- package/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts +0 -49
- package/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts +0 -49
- package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
- package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
- package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
- package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
- package/tests/admin/get-moderation-action.test.ts +0 -100
- package/tests/admin/get-moderation-actions.test.ts +0 -164
- package/tests/admin/get-moderation-report.test.ts +0 -100
- package/tests/admin/get-moderation-reports.test.ts +0 -332
- /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
|
+
import { DynamicModule, sql } from 'kysely'
|
|
3
|
+
|
|
4
|
+
import { Cursor, GenericKeyset } from '../../db/pagination'
|
|
5
|
+
|
|
6
|
+
type StatusKeysetParam = {
|
|
7
|
+
lastReviewedAt: string | null
|
|
8
|
+
lastReportedAt: string | null
|
|
9
|
+
id: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class StatusKeyset extends GenericKeyset<StatusKeysetParam, Cursor> {
|
|
13
|
+
labelResult(result: StatusKeysetParam): Cursor
|
|
14
|
+
labelResult(result: StatusKeysetParam) {
|
|
15
|
+
const primaryField = (
|
|
16
|
+
this.primary as ReturnType<DynamicModule['ref']>
|
|
17
|
+
).dynamicReference.includes('lastReviewedAt')
|
|
18
|
+
? 'lastReviewedAt'
|
|
19
|
+
: 'lastReportedAt'
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
primary: result[primaryField]
|
|
23
|
+
? new Date(`${result[primaryField]}`).getTime().toString()
|
|
24
|
+
: '',
|
|
25
|
+
secondary: result.id.toString(),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
labeledResultToCursor(labeled: Cursor) {
|
|
29
|
+
return {
|
|
30
|
+
primary: labeled.primary,
|
|
31
|
+
secondary: labeled.secondary,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
cursorToLabeledResult(cursor: Cursor) {
|
|
35
|
+
return {
|
|
36
|
+
primary: cursor.primary
|
|
37
|
+
? new Date(parseInt(cursor.primary, 10)).toISOString()
|
|
38
|
+
: '',
|
|
39
|
+
secondary: cursor.secondary,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
unpackCursor(cursorStr?: string): Cursor | undefined {
|
|
43
|
+
if (!cursorStr) return
|
|
44
|
+
const result = cursorStr.split('::')
|
|
45
|
+
const [primary, secondary, ...others] = result
|
|
46
|
+
if (!secondary || others.length > 0) {
|
|
47
|
+
throw new InvalidRequestError('Malformed cursor')
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
primary,
|
|
51
|
+
secondary,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// This is specifically built to handle nullable columns as primary sorting column
|
|
55
|
+
getSql(labeled?: Cursor, direction?: 'asc' | 'desc') {
|
|
56
|
+
if (labeled === undefined) return
|
|
57
|
+
if (direction === 'asc') {
|
|
58
|
+
return !labeled.primary
|
|
59
|
+
? sql`(${this.primary} IS NULL AND ${this.secondary} > ${labeled.secondary})`
|
|
60
|
+
: sql`((${this.primary}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
|
|
61
|
+
} else {
|
|
62
|
+
return !labeled.primary
|
|
63
|
+
? sql`(${this.primary} IS NULL AND ${this.secondary} < ${labeled.secondary})`
|
|
64
|
+
: sql`((${this.primary}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}) OR (${this.primary} is null))`
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type TimeIdKeysetParam = {
|
|
70
|
+
id: number
|
|
71
|
+
createdAt: string
|
|
72
|
+
}
|
|
73
|
+
type TimeIdResult = TimeIdKeysetParam
|
|
74
|
+
|
|
75
|
+
export class TimeIdKeyset extends GenericKeyset<TimeIdKeysetParam, Cursor> {
|
|
76
|
+
labelResult(result: TimeIdResult): Cursor
|
|
77
|
+
labelResult(result: TimeIdResult) {
|
|
78
|
+
return { primary: result.createdAt, secondary: result.id.toString() }
|
|
79
|
+
}
|
|
80
|
+
labeledResultToCursor(labeled: Cursor) {
|
|
81
|
+
return {
|
|
82
|
+
primary: new Date(labeled.primary).getTime().toString(),
|
|
83
|
+
secondary: labeled.secondary,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
cursorToLabeledResult(cursor: Cursor) {
|
|
87
|
+
const primaryDate = new Date(parseInt(cursor.primary, 10))
|
|
88
|
+
if (isNaN(primaryDate.getTime())) {
|
|
89
|
+
throw new InvalidRequestError('Malformed cursor')
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
primary: primaryDate.toISOString(),
|
|
93
|
+
secondary: cursor.secondary,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// This may require better organization but for now, just dumping functions here containing DB queries for moderation status
|
|
2
|
+
|
|
3
|
+
import { AtUri } from '@atproto/syntax'
|
|
4
|
+
import { PrimaryDatabase } from '../../db'
|
|
5
|
+
import {
|
|
6
|
+
ModerationEvent,
|
|
7
|
+
ModerationSubjectStatus,
|
|
8
|
+
} from '../../db/tables/moderation'
|
|
9
|
+
import {
|
|
10
|
+
REVIEWOPEN,
|
|
11
|
+
REVIEWCLOSED,
|
|
12
|
+
REVIEWESCALATED,
|
|
13
|
+
} from '../../lexicon/types/com/atproto/admin/defs'
|
|
14
|
+
import { ModerationEventRow, ModerationSubjectStatusRow } from './types'
|
|
15
|
+
import { HOUR } from '@atproto/common'
|
|
16
|
+
import { CID } from 'multiformats/cid'
|
|
17
|
+
import { sql } from 'kysely'
|
|
18
|
+
|
|
19
|
+
const getSubjectStatusForModerationEvent = ({
|
|
20
|
+
action,
|
|
21
|
+
createdBy,
|
|
22
|
+
createdAt,
|
|
23
|
+
durationInHours,
|
|
24
|
+
}: {
|
|
25
|
+
action: string
|
|
26
|
+
createdBy: string
|
|
27
|
+
createdAt: string
|
|
28
|
+
durationInHours: number | null
|
|
29
|
+
}): Partial<ModerationSubjectStatusRow> | null => {
|
|
30
|
+
switch (action) {
|
|
31
|
+
case 'com.atproto.admin.defs#modEventAcknowledge':
|
|
32
|
+
return {
|
|
33
|
+
lastReviewedBy: createdBy,
|
|
34
|
+
reviewState: REVIEWCLOSED,
|
|
35
|
+
lastReviewedAt: createdAt,
|
|
36
|
+
}
|
|
37
|
+
case 'com.atproto.admin.defs#modEventReport':
|
|
38
|
+
return {
|
|
39
|
+
reviewState: REVIEWOPEN,
|
|
40
|
+
lastReportedAt: createdAt,
|
|
41
|
+
}
|
|
42
|
+
case 'com.atproto.admin.defs#modEventEscalate':
|
|
43
|
+
return {
|
|
44
|
+
lastReviewedBy: createdBy,
|
|
45
|
+
reviewState: REVIEWESCALATED,
|
|
46
|
+
lastReviewedAt: createdAt,
|
|
47
|
+
}
|
|
48
|
+
case 'com.atproto.admin.defs#modEventReverseTakedown':
|
|
49
|
+
return {
|
|
50
|
+
lastReviewedBy: createdBy,
|
|
51
|
+
reviewState: REVIEWCLOSED,
|
|
52
|
+
takendown: false,
|
|
53
|
+
suspendUntil: null,
|
|
54
|
+
lastReviewedAt: createdAt,
|
|
55
|
+
}
|
|
56
|
+
case 'com.atproto.admin.defs#modEventUnmute':
|
|
57
|
+
return {
|
|
58
|
+
lastReviewedBy: createdBy,
|
|
59
|
+
muteUntil: null,
|
|
60
|
+
reviewState: REVIEWOPEN,
|
|
61
|
+
lastReviewedAt: createdAt,
|
|
62
|
+
}
|
|
63
|
+
case 'com.atproto.admin.defs#modEventTakedown':
|
|
64
|
+
return {
|
|
65
|
+
takendown: true,
|
|
66
|
+
lastReviewedBy: createdBy,
|
|
67
|
+
reviewState: REVIEWCLOSED,
|
|
68
|
+
lastReviewedAt: createdAt,
|
|
69
|
+
suspendUntil: durationInHours
|
|
70
|
+
? new Date(Date.now() + durationInHours * HOUR).toISOString()
|
|
71
|
+
: null,
|
|
72
|
+
}
|
|
73
|
+
case 'com.atproto.admin.defs#modEventMute':
|
|
74
|
+
return {
|
|
75
|
+
lastReviewedBy: createdBy,
|
|
76
|
+
reviewState: REVIEWOPEN,
|
|
77
|
+
lastReviewedAt: createdAt,
|
|
78
|
+
// By default, mute for 24hrs
|
|
79
|
+
muteUntil: new Date(
|
|
80
|
+
Date.now() + (durationInHours || 24) * HOUR,
|
|
81
|
+
).toISOString(),
|
|
82
|
+
}
|
|
83
|
+
case 'com.atproto.admin.defs#modEventComment':
|
|
84
|
+
return {
|
|
85
|
+
lastReviewedBy: createdBy,
|
|
86
|
+
lastReviewedAt: createdAt,
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Based on a given moderation action event, this function will update the moderation status of the subject
|
|
94
|
+
// If there's no existing status, it will create one
|
|
95
|
+
// If the action event does not affect the status, it will do nothing
|
|
96
|
+
export const adjustModerationSubjectStatus = async (
|
|
97
|
+
db: PrimaryDatabase,
|
|
98
|
+
moderationEvent: ModerationEventRow,
|
|
99
|
+
blobCids?: CID[],
|
|
100
|
+
) => {
|
|
101
|
+
const {
|
|
102
|
+
action,
|
|
103
|
+
subjectDid,
|
|
104
|
+
subjectUri,
|
|
105
|
+
subjectCid,
|
|
106
|
+
createdBy,
|
|
107
|
+
meta,
|
|
108
|
+
comment,
|
|
109
|
+
createdAt,
|
|
110
|
+
} = moderationEvent
|
|
111
|
+
|
|
112
|
+
const subjectStatus = getSubjectStatusForModerationEvent({
|
|
113
|
+
action,
|
|
114
|
+
createdBy,
|
|
115
|
+
createdAt,
|
|
116
|
+
durationInHours: moderationEvent.durationInHours,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// If there are no subjectStatus that means there are no side-effect of the incoming event
|
|
120
|
+
if (!subjectStatus) {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const now = new Date().toISOString()
|
|
125
|
+
// If subjectUri exists, it's not a repoRef so pass along the uri to get identifier back
|
|
126
|
+
const identifier = getStatusIdentifierFromSubject(subjectUri || subjectDid)
|
|
127
|
+
|
|
128
|
+
db.assertTransaction()
|
|
129
|
+
|
|
130
|
+
const currentStatus = await db.db
|
|
131
|
+
.selectFrom('moderation_subject_status')
|
|
132
|
+
.where('did', '=', identifier.did)
|
|
133
|
+
.where('recordPath', '=', identifier.recordPath)
|
|
134
|
+
.selectAll()
|
|
135
|
+
.executeTakeFirst()
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
currentStatus?.reviewState === REVIEWESCALATED &&
|
|
139
|
+
subjectStatus.reviewState === REVIEWOPEN
|
|
140
|
+
) {
|
|
141
|
+
// If the current status is escalated and the incoming event is to open the review
|
|
142
|
+
// We want to keep the status as escalated
|
|
143
|
+
subjectStatus.reviewState = REVIEWESCALATED
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Set these because we don't want to override them if they're already set
|
|
147
|
+
const defaultData = {
|
|
148
|
+
comment: null,
|
|
149
|
+
// Defaulting reviewState to open for any event may not be the desired behavior.
|
|
150
|
+
// For instance, if a subject never had any event and we just want to leave a comment to keep an eye on it
|
|
151
|
+
// that shouldn't mean we want to review the subject
|
|
152
|
+
reviewState: REVIEWOPEN,
|
|
153
|
+
recordCid: subjectCid || null,
|
|
154
|
+
}
|
|
155
|
+
const newStatus = {
|
|
156
|
+
...defaultData,
|
|
157
|
+
...subjectStatus,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
action === 'com.atproto.admin.defs#modEventReverseTakedown' &&
|
|
162
|
+
!subjectStatus.takendown
|
|
163
|
+
) {
|
|
164
|
+
newStatus.takendown = false
|
|
165
|
+
subjectStatus.takendown = false
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (action === 'com.atproto.admin.defs#modEventComment' && meta?.sticky) {
|
|
169
|
+
newStatus.comment = comment
|
|
170
|
+
subjectStatus.comment = comment
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (blobCids?.length) {
|
|
174
|
+
const newBlobCids = sql<string[]>`${JSON.stringify(
|
|
175
|
+
blobCids.map((c) => c.toString()),
|
|
176
|
+
)}` as unknown as ModerationSubjectStatusRow['blobCids']
|
|
177
|
+
newStatus.blobCids = newBlobCids
|
|
178
|
+
subjectStatus.blobCids = newBlobCids
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const insertQuery = db.db
|
|
182
|
+
.insertInto('moderation_subject_status')
|
|
183
|
+
.values({
|
|
184
|
+
...identifier,
|
|
185
|
+
...newStatus,
|
|
186
|
+
createdAt: now,
|
|
187
|
+
updatedAt: now,
|
|
188
|
+
// TODO: Need to get the types right here.
|
|
189
|
+
} as ModerationSubjectStatusRow)
|
|
190
|
+
.onConflict((oc) =>
|
|
191
|
+
oc.constraint('moderation_status_unique_idx').doUpdateSet({
|
|
192
|
+
...subjectStatus,
|
|
193
|
+
updatedAt: now,
|
|
194
|
+
}),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
const status = await insertQuery.executeTakeFirst()
|
|
198
|
+
return status
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type ModerationSubjectStatusFilter =
|
|
202
|
+
| Pick<ModerationSubjectStatus, 'did'>
|
|
203
|
+
| Pick<ModerationSubjectStatus, 'did' | 'recordPath'>
|
|
204
|
+
| Pick<ModerationSubjectStatus, 'did' | 'recordPath' | 'recordCid'>
|
|
205
|
+
export const getModerationSubjectStatus = async (
|
|
206
|
+
db: PrimaryDatabase,
|
|
207
|
+
filters: ModerationSubjectStatusFilter,
|
|
208
|
+
) => {
|
|
209
|
+
let builder = db.db
|
|
210
|
+
.selectFrom('moderation_subject_status')
|
|
211
|
+
// DID will always be passed at the very least
|
|
212
|
+
.where('did', '=', filters.did)
|
|
213
|
+
.where('recordPath', '=', 'recordPath' in filters ? filters.recordPath : '')
|
|
214
|
+
|
|
215
|
+
if ('recordCid' in filters) {
|
|
216
|
+
builder = builder.where('recordCid', '=', filters.recordCid)
|
|
217
|
+
} else {
|
|
218
|
+
builder = builder.where('recordCid', 'is', null)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return builder.executeTakeFirst()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const getStatusIdentifierFromSubject = (
|
|
225
|
+
subject: string | AtUri,
|
|
226
|
+
): { did: string; recordPath: string } => {
|
|
227
|
+
const isSubjectString = typeof subject === 'string'
|
|
228
|
+
if (isSubjectString && subject.startsWith('did:')) {
|
|
229
|
+
return {
|
|
230
|
+
did: subject,
|
|
231
|
+
recordPath: '',
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (isSubjectString && !subject.startsWith('at://')) {
|
|
236
|
+
throw new Error('Subject is neither a did nor an at-uri')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const uri = isSubjectString ? new AtUri(subject) : subject
|
|
240
|
+
return {
|
|
241
|
+
did: uri.host,
|
|
242
|
+
recordPath: `${uri.collection}/${uri.rkey}`,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Selectable } from 'kysely'
|
|
2
|
+
import {
|
|
3
|
+
ModerationEvent,
|
|
4
|
+
ModerationSubjectStatus,
|
|
5
|
+
} from '../../db/tables/moderation'
|
|
6
|
+
import { AtUri } from '@atproto/syntax'
|
|
7
|
+
import { CID } from 'multiformats/cid'
|
|
8
|
+
import { ComAtprotoAdminDefs } from '@atproto/api'
|
|
9
|
+
|
|
10
|
+
export type SubjectInfo =
|
|
11
|
+
| {
|
|
12
|
+
subjectType: 'com.atproto.admin.defs#repoRef'
|
|
13
|
+
subjectDid: string
|
|
14
|
+
subjectUri: null
|
|
15
|
+
subjectCid: null
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
subjectType: 'com.atproto.repo.strongRef'
|
|
19
|
+
subjectDid: string
|
|
20
|
+
subjectUri: string
|
|
21
|
+
subjectCid: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ModerationEventRow = Selectable<ModerationEvent>
|
|
25
|
+
export type ReversibleModerationEvent = Pick<
|
|
26
|
+
ModerationEventRow,
|
|
27
|
+
'createdBy' | 'comment' | 'action'
|
|
28
|
+
> & {
|
|
29
|
+
createdAt?: Date
|
|
30
|
+
subject: { did: string } | { uri: AtUri; cid: CID }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ModerationEventRowWithHandle = ModerationEventRow & {
|
|
34
|
+
subjectHandle?: string | null
|
|
35
|
+
creatorHandle?: string | null
|
|
36
|
+
}
|
|
37
|
+
export type ModerationSubjectStatusRow = Selectable<ModerationSubjectStatus>
|
|
38
|
+
export type ModerationSubjectStatusRowWithHandle =
|
|
39
|
+
ModerationSubjectStatusRow & { handle: string | null }
|
|
40
|
+
|
|
41
|
+
export type ModEventType =
|
|
42
|
+
| ComAtprotoAdminDefs.ModEventTakedown
|
|
43
|
+
| ComAtprotoAdminDefs.ModEventAcknowledge
|
|
44
|
+
| ComAtprotoAdminDefs.ModEventEscalate
|
|
45
|
+
| ComAtprotoAdminDefs.ModEventComment
|
|
46
|
+
| ComAtprotoAdminDefs.ModEventLabel
|
|
47
|
+
| ComAtprotoAdminDefs.ModEventReport
|
|
48
|
+
| ComAtprotoAdminDefs.ModEventMute
|
|
49
|
+
| ComAtprotoAdminDefs.ModEventReverseTakedown
|