@atproto/bsky 0.0.15 → 0.0.17
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 +18 -0
- package/dist/api/com/atproto/moderation/util.d.ts +4 -3
- package/dist/cache/read-through.d.ts +30 -0
- package/dist/config.d.ts +18 -0
- package/dist/context.d.ts +21 -6
- package/dist/daemon/config.d.ts +15 -0
- package/dist/daemon/context.d.ts +15 -0
- package/dist/daemon/index.d.ts +23 -0
- package/dist/daemon/logger.d.ts +3 -0
- package/dist/daemon/notifications.d.ts +18 -0
- package/dist/daemon/services.d.ts +11 -0
- package/dist/db/database-schema.d.ts +1 -2
- package/dist/db/index.js +41 -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/20231205T000257238Z-remove-did-cache.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +2 -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/did-cache.d.ts +10 -7
- package/dist/feed-gen/types.d.ts +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4370 -2758
- package/dist/index.js.map +3 -3
- package/dist/indexer/context.d.ts +2 -0
- package/dist/indexer/index.d.ts +1 -0
- package/dist/lexicon/index.d.ts +23 -18
- package/dist/lexicon/lexicons.d.ts +561 -412
- 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/{resolveModerationReports.d.ts → deleteAccount.d.ts} +2 -13
- 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/lexicon/types/com/atproto/{admin/getModerationReport.d.ts → temp/importRepo.d.ts} +10 -7
- package/dist/lexicon/types/com/atproto/temp/pushBlob.d.ts +25 -0
- package/dist/lexicon/types/com/atproto/{admin/reverseModerationAction.d.ts → temp/transferAccount.d.ts} +11 -5
- package/dist/logger.d.ts +1 -0
- package/dist/migrate-moderation-data.d.ts +1 -0
- package/dist/redis.d.ts +10 -1
- package/dist/services/actor/index.d.ts +18 -4
- package/dist/services/actor/views.d.ts +6 -8
- package/dist/services/feed/index.d.ts +7 -4
- package/dist/services/feed/util.d.ts +9 -1
- package/dist/services/feed/views.d.ts +11 -21
- package/dist/services/graph/index.d.ts +5 -29
- package/dist/services/graph/types.d.ts +1 -0
- package/dist/services/index.d.ts +3 -7
- package/dist/services/label/index.d.ts +10 -4
- package/dist/services/moderation/index.d.ts +134 -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/services/types.d.ts +3 -0
- package/dist/services/util/notification.d.ts +5 -0
- package/dist/services/util/post.d.ts +6 -6
- package/dist/util/debug.d.ts +1 -1
- package/dist/util/retry.d.ts +1 -6
- 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/cache/read-through.ts +151 -0
- package/src/config.ts +90 -1
- package/src/context.ts +11 -7
- package/src/daemon/config.ts +60 -0
- package/src/daemon/context.ts +27 -0
- package/src/daemon/index.ts +78 -0
- package/src/daemon/logger.ts +6 -0
- package/src/daemon/notifications.ts +54 -0
- package/src/daemon/services.ts +22 -0
- package/src/db/database-schema.ts +0 -2
- package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
- package/src/db/migrations/20231205T000257238Z-remove-did-cache.ts +14 -0
- package/src/db/migrations/index.ts +2 -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/did-cache.ts +33 -56
- package/src/feed-gen/bsky-team.ts +1 -1
- package/src/feed-gen/hot-classic.ts +1 -1
- package/src/feed-gen/index.ts +0 -4
- package/src/feed-gen/mutuals.ts +6 -2
- package/src/feed-gen/types.ts +1 -1
- package/src/index.ts +57 -17
- package/src/indexer/context.ts +5 -0
- package/src/indexer/index.ts +10 -7
- package/src/lexicon/index.ts +80 -67
- package/src/lexicon/lexicons.ts +698 -507
- 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/{resolveModerationReports.ts → deleteAccount.ts} +2 -13
- 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/lexicon/types/com/atproto/{admin/getModerationAction.ts → temp/importRepo.ts} +11 -7
- package/src/lexicon/types/com/atproto/temp/pushBlob.ts +39 -0
- package/src/lexicon/types/com/atproto/{admin/reverseModerationAction.ts → temp/transferAccount.ts} +18 -5
- package/src/logger.ts +2 -0
- package/src/migrate-moderation-data.ts +414 -0
- package/src/redis.ts +43 -3
- package/src/services/actor/index.ts +55 -7
- package/src/services/actor/views.ts +18 -21
- package/src/services/feed/index.ts +52 -19
- package/src/services/feed/util.ts +47 -19
- package/src/services/feed/views.ts +87 -13
- package/src/services/graph/index.ts +21 -3
- package/src/services/graph/types.ts +1 -0
- package/src/services/index.ts +14 -14
- package/src/services/indexing/index.ts +7 -10
- 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 +16 -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 +68 -25
- package/src/services/moderation/index.ts +380 -395
- package/src/services/moderation/pagination.ts +96 -0
- package/src/services/moderation/status.ts +241 -0
- package/src/services/moderation/types.ts +49 -0
- package/src/services/moderation/views.ts +278 -329
- package/src/services/types.ts +4 -0
- package/src/services/util/notification.ts +70 -0
- package/src/util/debug.ts +2 -2
- package/src/util/retry.ts +1 -44
- 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 +10 -12
- 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 +3 -3
- package/tests/algos/hot-classic.test.ts +1 -2
- package/tests/auth.test.ts +1 -1
- package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
- package/tests/auto-moderator/labeler.test.ts +19 -20
- package/tests/auto-moderator/takedowns.test.ts +61 -28
- package/tests/blob-resolver.test.ts +4 -2
- package/tests/daemon.test.ts +191 -0
- package/tests/did-cache.test.ts +20 -5
- package/tests/feed-generation.test.ts +57 -9
- package/tests/handle-invalidation.test.ts +1 -5
- package/tests/indexing.test.ts +20 -13
- package/tests/redis-cache.test.ts +231 -0
- package/tests/seeds/basic.ts +3 -0
- package/tests/subscription/repo.test.ts +4 -7
- 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 -19
- package/tests/views/thread.test.ts +117 -94
- package/tests/views/threadgating.test.ts +89 -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/db/tables/did-cache.d.ts +0 -10
- package/dist/feed-gen/best-of-follows.d.ts +0 -29
- package/dist/feed-gen/whats-hot.d.ts +0 -29
- package/dist/feed-gen/with-friends.d.ts +0 -3
- package/dist/label-cache.d.ts +0 -19
- 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/db/tables/did-cache.ts +0 -13
- package/src/feed-gen/best-of-follows.ts +0 -74
- package/src/feed-gen/whats-hot.ts +0 -101
- package/src/feed-gen/with-friends.ts +0 -39
- package/src/label-cache.ts +0 -90
- 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/tests/algos/whats-hot.test.ts +0 -118
- package/tests/algos/with-friends.test.ts +0 -145
- /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
|
@@ -2,11 +2,14 @@ import AppContext from '../../../../context'
|
|
|
2
2
|
import { Server } from '../../../../lexicon'
|
|
3
3
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
4
4
|
import AtpAgent from '@atproto/api'
|
|
5
|
-
import { AtUri } from '@atproto/syntax'
|
|
6
5
|
import { mapDefined } from '@atproto/common'
|
|
7
6
|
import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/searchPosts'
|
|
8
7
|
import { Database } from '../../../../db'
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
FeedHydrationState,
|
|
10
|
+
FeedRow,
|
|
11
|
+
FeedService,
|
|
12
|
+
} from '../../../../services/feed'
|
|
10
13
|
import { ActorService } from '../../../../services/actor'
|
|
11
14
|
import { createPipeline } from '../../../../pipeline'
|
|
12
15
|
|
|
@@ -51,9 +54,11 @@ const skeleton = async (
|
|
|
51
54
|
cursor: params.cursor,
|
|
52
55
|
limit: params.limit,
|
|
53
56
|
})
|
|
57
|
+
const postUris = res.data.posts.map((a) => a.uri)
|
|
58
|
+
const feedItems = await ctx.feedService.postUrisToFeedItems(postUris)
|
|
54
59
|
return {
|
|
55
60
|
params,
|
|
56
|
-
|
|
61
|
+
feedItems,
|
|
57
62
|
cursor: res.data.cursor,
|
|
58
63
|
hitsTotal: res.data.hitsTotal,
|
|
59
64
|
}
|
|
@@ -64,12 +69,10 @@ const hydration = async (
|
|
|
64
69
|
ctx: Context,
|
|
65
70
|
): Promise<HydrationState> => {
|
|
66
71
|
const { feedService } = ctx
|
|
67
|
-
const { params,
|
|
68
|
-
const
|
|
69
|
-
const dids = new Set<string>(postUris.map((uri) => new AtUri(uri).hostname))
|
|
72
|
+
const { params, feedItems } = state
|
|
73
|
+
const refs = feedService.feedItemRefs(feedItems)
|
|
70
74
|
const hydrated = await feedService.feedHydration({
|
|
71
|
-
|
|
72
|
-
dids,
|
|
75
|
+
...refs,
|
|
73
76
|
viewer: params.viewer,
|
|
74
77
|
})
|
|
75
78
|
return { ...state, ...hydrated }
|
|
@@ -77,32 +80,32 @@ const hydration = async (
|
|
|
77
80
|
|
|
78
81
|
const noBlocks = (state: HydrationState): HydrationState => {
|
|
79
82
|
const { viewer } = state.params
|
|
80
|
-
state.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return !state.bam.block([viewer, post.creator])
|
|
83
|
+
state.feedItems = state.feedItems.filter((item) => {
|
|
84
|
+
if (!viewer) return true
|
|
85
|
+
return !state.bam.block([viewer, item.postAuthorDid])
|
|
84
86
|
})
|
|
85
87
|
return state
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
const presentation = (state: HydrationState, ctx: Context) => {
|
|
89
91
|
const { feedService, actorService } = ctx
|
|
90
|
-
const {
|
|
92
|
+
const { feedItems, profiles, params } = state
|
|
91
93
|
const actors = actorService.views.profileBasicPresentation(
|
|
92
94
|
Object.keys(profiles),
|
|
93
95
|
state,
|
|
94
|
-
|
|
96
|
+
params.viewer,
|
|
95
97
|
)
|
|
96
98
|
|
|
97
|
-
const postViews = mapDefined(
|
|
99
|
+
const postViews = mapDefined(feedItems, (item) =>
|
|
98
100
|
feedService.views.formatPostView(
|
|
99
|
-
|
|
101
|
+
item.postUri,
|
|
100
102
|
actors,
|
|
101
103
|
state.posts,
|
|
102
104
|
state.threadgates,
|
|
103
105
|
state.embeds,
|
|
104
106
|
state.labels,
|
|
105
107
|
state.lists,
|
|
108
|
+
params.viewer,
|
|
106
109
|
),
|
|
107
110
|
)
|
|
108
111
|
return { posts: postViews, cursor: state.cursor, hitsTotal: state.hitsTotal }
|
|
@@ -119,7 +122,7 @@ type Params = QueryParams & { viewer: string | null }
|
|
|
119
122
|
|
|
120
123
|
type SkeletonState = {
|
|
121
124
|
params: Params
|
|
122
|
-
|
|
125
|
+
feedItems: FeedRow[]
|
|
123
126
|
hitsTotal?: number
|
|
124
127
|
cursor?: string
|
|
125
128
|
}
|
|
@@ -91,17 +91,20 @@ const presentation = (state: HydrationState, ctx: Context) => {
|
|
|
91
91
|
const actors = actorService.views.profilePresentation(
|
|
92
92
|
Object.keys(profileState.profiles),
|
|
93
93
|
profileState,
|
|
94
|
-
|
|
94
|
+
params.viewer,
|
|
95
95
|
)
|
|
96
96
|
const creator = actors[list.creator]
|
|
97
97
|
if (!creator) {
|
|
98
98
|
throw new InvalidRequestError(`Actor not found: ${list.handle}`)
|
|
99
99
|
}
|
|
100
100
|
const listView = graphService.formatListView(list, actors)
|
|
101
|
+
if (!listView) {
|
|
102
|
+
throw new InvalidRequestError('List not found')
|
|
103
|
+
}
|
|
101
104
|
const items = mapDefined(listItems, (item) => {
|
|
102
105
|
const subject = actors[item.did]
|
|
103
106
|
if (!subject) return
|
|
104
|
-
return { subject }
|
|
107
|
+
return { uri: item.uri, subject }
|
|
105
108
|
})
|
|
106
109
|
return { list: listView, items, cursor }
|
|
107
110
|
}
|
|
@@ -119,7 +122,7 @@ type Params = QueryParams & {
|
|
|
119
122
|
type SkeletonState = {
|
|
120
123
|
params: Params
|
|
121
124
|
list: Actor & ListInfo
|
|
122
|
-
listItems: (Actor & { cid: string; sortAt: string })[]
|
|
125
|
+
listItems: (Actor & { uri: string; cid: string; sortAt: string })[]
|
|
123
126
|
cursor?: string
|
|
124
127
|
}
|
|
125
128
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapDefined } from '@atproto/common'
|
|
1
2
|
import { Server } from '../../../../lexicon'
|
|
2
3
|
import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getListBlocks'
|
|
3
4
|
import { paginate, TimeCidKeyset } from '../../../../db/pagination'
|
|
@@ -87,9 +88,9 @@ const presentation = (state: HydrationState, ctx: Context) => {
|
|
|
87
88
|
const actors = actorService.views.profilePresentation(
|
|
88
89
|
Object.keys(profileState.profiles),
|
|
89
90
|
profileState,
|
|
90
|
-
|
|
91
|
+
params.viewer,
|
|
91
92
|
)
|
|
92
|
-
const lists = listInfos
|
|
93
|
+
const lists = mapDefined(listInfos, (list) =>
|
|
93
94
|
graphService.formatListView(list, actors),
|
|
94
95
|
)
|
|
95
96
|
return { lists, cursor }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapDefined } from '@atproto/common'
|
|
1
2
|
import { Server } from '../../../../lexicon'
|
|
2
3
|
import { paginate, TimeCidKeyset } from '../../../../db/pagination'
|
|
3
4
|
import AppContext from '../../../../context'
|
|
@@ -34,7 +35,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
34
35
|
const actorService = ctx.services.actor(db)
|
|
35
36
|
const profiles = await actorService.views.profiles(listsRes, requester)
|
|
36
37
|
|
|
37
|
-
const lists = listsRes
|
|
38
|
+
const lists = mapDefined(listsRes, (row) =>
|
|
38
39
|
graphService.formatListView(row, profiles),
|
|
39
40
|
)
|
|
40
41
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapDefined } from '@atproto/common'
|
|
1
2
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
3
|
import { Server } from '../../../../lexicon'
|
|
3
4
|
import { paginate, TimeCidKeyset } from '../../../../db/pagination'
|
|
@@ -39,7 +40,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
39
40
|
throw new InvalidRequestError(`Actor not found: ${actor}`)
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const lists = listsRes
|
|
43
|
+
const lists = mapDefined(listsRes, (row) =>
|
|
43
44
|
graphService.formatListView(row, profiles),
|
|
44
45
|
)
|
|
45
46
|
|
|
@@ -57,7 +57,9 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
57
57
|
const gen = genInfos[row.uri]
|
|
58
58
|
if (!gen) continue
|
|
59
59
|
const view = feedService.views.formatFeedGeneratorView(gen, profiles)
|
|
60
|
-
|
|
60
|
+
if (view) {
|
|
61
|
+
genViews.push(view)
|
|
62
|
+
}
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
return {
|
package/src/api/blob-resolver.ts
CHANGED
|
@@ -6,11 +6,11 @@ import { CID } from 'multiformats/cid'
|
|
|
6
6
|
import { ensureValidDid } from '@atproto/syntax'
|
|
7
7
|
import { forwardStreamErrors, VerifyCidTransform } from '@atproto/common'
|
|
8
8
|
import { IdResolver, DidNotFoundError } from '@atproto/identity'
|
|
9
|
-
import { TAKEDOWN } from '../lexicon/types/com/atproto/admin/defs'
|
|
10
9
|
import AppContext from '../context'
|
|
11
10
|
import { httpLogger as log } from '../logger'
|
|
12
11
|
import { retryHttp } from '../util/retry'
|
|
13
12
|
import { Database } from '../db'
|
|
13
|
+
import { sql } from 'kysely'
|
|
14
14
|
|
|
15
15
|
// Resolve and verify blob from its origin host
|
|
16
16
|
|
|
@@ -84,19 +84,14 @@ export async function resolveBlob(
|
|
|
84
84
|
idResolver: IdResolver,
|
|
85
85
|
) {
|
|
86
86
|
const cidStr = cid.toString()
|
|
87
|
+
|
|
87
88
|
const [{ pds }, takedown] = await Promise.all([
|
|
88
89
|
idResolver.did.resolveAtprotoData(did), // @TODO cache did info
|
|
89
90
|
db.db
|
|
90
|
-
.selectFrom('
|
|
91
|
-
.select('
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
'moderation_action.id',
|
|
95
|
-
'moderation_action_subject_blob.actionId',
|
|
96
|
-
)
|
|
97
|
-
.where('cid', '=', cidStr)
|
|
98
|
-
.where('action', '=', TAKEDOWN)
|
|
99
|
-
.where('reversedAt', 'is', null)
|
|
91
|
+
.selectFrom('moderation_subject_status')
|
|
92
|
+
.select('id')
|
|
93
|
+
.where('blobCids', '@>', sql`CAST(${JSON.stringify([cidStr])} AS JSONB)`)
|
|
94
|
+
.where('takendown', 'is', true)
|
|
100
95
|
.executeTakeFirst(),
|
|
101
96
|
])
|
|
102
97
|
if (takedown) {
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { AtUri } from '@atproto/syntax'
|
|
3
|
+
import {
|
|
4
|
+
AuthRequiredError,
|
|
5
|
+
InvalidRequestError,
|
|
6
|
+
UpstreamFailureError,
|
|
7
|
+
} from '@atproto/xrpc-server'
|
|
8
|
+
import { Server } from '../../../../lexicon'
|
|
9
|
+
import AppContext from '../../../../context'
|
|
10
|
+
import { getSubject } from '../moderation/util'
|
|
11
|
+
import {
|
|
12
|
+
isModEventLabel,
|
|
13
|
+
isModEventReverseTakedown,
|
|
14
|
+
isModEventTakedown,
|
|
15
|
+
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
16
|
+
import { TakedownSubjects } from '../../../../services/moderation'
|
|
17
|
+
import { retryHttp } from '../../../../util/retry'
|
|
18
|
+
|
|
19
|
+
export default function (server: Server, ctx: AppContext) {
|
|
20
|
+
server.com.atproto.admin.emitModerationEvent({
|
|
21
|
+
auth: ctx.roleVerifier,
|
|
22
|
+
handler: async ({ input, auth }) => {
|
|
23
|
+
const access = auth.credentials
|
|
24
|
+
const db = ctx.db.getPrimary()
|
|
25
|
+
const moderationService = ctx.services.moderation(db)
|
|
26
|
+
const { subject, createdBy, subjectBlobCids, event } = input.body
|
|
27
|
+
const isTakedownEvent = isModEventTakedown(event)
|
|
28
|
+
const isReverseTakedownEvent = isModEventReverseTakedown(event)
|
|
29
|
+
const isLabelEvent = isModEventLabel(event)
|
|
30
|
+
|
|
31
|
+
// apply access rules
|
|
32
|
+
|
|
33
|
+
// if less than moderator access then can not takedown an account
|
|
34
|
+
if (!access.moderator && isTakedownEvent && 'did' in subject) {
|
|
35
|
+
throw new AuthRequiredError(
|
|
36
|
+
'Must be a full moderator to perform an account takedown',
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
// if less than moderator access then can only take ack and escalation actions
|
|
40
|
+
if (!access.moderator && (isTakedownEvent || isReverseTakedownEvent)) {
|
|
41
|
+
throw new AuthRequiredError(
|
|
42
|
+
'Must be a full moderator to take this type of action',
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
// if less than moderator access then can not apply labels
|
|
46
|
+
if (!access.moderator && isLabelEvent) {
|
|
47
|
+
throw new AuthRequiredError('Must be a full moderator to label content')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isLabelEvent) {
|
|
51
|
+
validateLabels([
|
|
52
|
+
...(event.createLabelVals ?? []),
|
|
53
|
+
...(event.negateLabelVals ?? []),
|
|
54
|
+
])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const subjectInfo = getSubject(subject)
|
|
58
|
+
|
|
59
|
+
if (isTakedownEvent || isReverseTakedownEvent) {
|
|
60
|
+
const isSubjectTakendown = await moderationService.isSubjectTakendown(
|
|
61
|
+
subjectInfo,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if (isSubjectTakendown && isTakedownEvent) {
|
|
65
|
+
throw new InvalidRequestError(`Subject is already taken down`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!isSubjectTakendown && isReverseTakedownEvent) {
|
|
69
|
+
throw new InvalidRequestError(`Subject is not taken down`)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { result: moderationEvent, takenDown } = await db.transaction(
|
|
74
|
+
async (dbTxn) => {
|
|
75
|
+
const moderationTxn = ctx.services.moderation(dbTxn)
|
|
76
|
+
const labelTxn = ctx.services.label(dbTxn)
|
|
77
|
+
|
|
78
|
+
const result = await moderationTxn.logEvent({
|
|
79
|
+
event,
|
|
80
|
+
subject: subjectInfo,
|
|
81
|
+
subjectBlobCids:
|
|
82
|
+
subjectBlobCids?.map((cid) => CID.parse(cid)) ?? [],
|
|
83
|
+
createdBy,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
let takenDown: TakedownSubjects | undefined
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
result.subjectType === 'com.atproto.admin.defs#repoRef' &&
|
|
90
|
+
result.subjectDid
|
|
91
|
+
) {
|
|
92
|
+
// No credentials to revoke on appview
|
|
93
|
+
if (isTakedownEvent) {
|
|
94
|
+
takenDown = await moderationTxn.takedownRepo({
|
|
95
|
+
takedownId: result.id,
|
|
96
|
+
did: result.subjectDid,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (isReverseTakedownEvent) {
|
|
101
|
+
await moderationTxn.reverseTakedownRepo({
|
|
102
|
+
did: result.subjectDid,
|
|
103
|
+
})
|
|
104
|
+
takenDown = {
|
|
105
|
+
subjects: [
|
|
106
|
+
{
|
|
107
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
108
|
+
did: result.subjectDid,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
did: result.subjectDid,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
result.subjectType === 'com.atproto.repo.strongRef' &&
|
|
118
|
+
result.subjectUri
|
|
119
|
+
) {
|
|
120
|
+
const blobCids = subjectBlobCids?.map((cid) => CID.parse(cid)) ?? []
|
|
121
|
+
if (isTakedownEvent) {
|
|
122
|
+
takenDown = await moderationTxn.takedownRecord({
|
|
123
|
+
takedownId: result.id,
|
|
124
|
+
uri: new AtUri(result.subjectUri),
|
|
125
|
+
// TODO: I think this will always be available for strongRefs?
|
|
126
|
+
cid: CID.parse(result.subjectCid as string),
|
|
127
|
+
blobCids,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (isReverseTakedownEvent) {
|
|
132
|
+
await moderationTxn.reverseTakedownRecord({
|
|
133
|
+
uri: new AtUri(result.subjectUri),
|
|
134
|
+
})
|
|
135
|
+
takenDown = {
|
|
136
|
+
did: result.subjectDid,
|
|
137
|
+
subjects: [
|
|
138
|
+
{
|
|
139
|
+
$type: 'com.atproto.repo.strongRef',
|
|
140
|
+
uri: result.subjectUri,
|
|
141
|
+
cid: result.subjectCid ?? '',
|
|
142
|
+
},
|
|
143
|
+
...blobCids.map((cid) => ({
|
|
144
|
+
$type: 'com.atproto.admin.defs#repoBlobRef',
|
|
145
|
+
did: result.subjectDid,
|
|
146
|
+
cid: cid.toString(),
|
|
147
|
+
recordUri: result.subjectUri,
|
|
148
|
+
})),
|
|
149
|
+
],
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (isLabelEvent) {
|
|
155
|
+
await labelTxn.formatAndCreate(
|
|
156
|
+
ctx.cfg.labelerDid,
|
|
157
|
+
result.subjectUri ?? result.subjectDid,
|
|
158
|
+
result.subjectCid,
|
|
159
|
+
{
|
|
160
|
+
create: result.createLabelVals?.length
|
|
161
|
+
? result.createLabelVals.split(' ')
|
|
162
|
+
: undefined,
|
|
163
|
+
negate: result.negateLabelVals?.length
|
|
164
|
+
? result.negateLabelVals.split(' ')
|
|
165
|
+
: undefined,
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { result, takenDown }
|
|
171
|
+
},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if (takenDown && ctx.moderationPushAgent) {
|
|
175
|
+
const { did, subjects } = takenDown
|
|
176
|
+
if (did && subjects.length > 0) {
|
|
177
|
+
const agent = ctx.moderationPushAgent
|
|
178
|
+
const results = await Promise.allSettled(
|
|
179
|
+
subjects.map((subject) =>
|
|
180
|
+
retryHttp(() =>
|
|
181
|
+
agent.api.com.atproto.admin.updateSubjectStatus({
|
|
182
|
+
subject,
|
|
183
|
+
takedown: isTakedownEvent
|
|
184
|
+
? {
|
|
185
|
+
applied: true,
|
|
186
|
+
ref: moderationEvent.id.toString(),
|
|
187
|
+
}
|
|
188
|
+
: {
|
|
189
|
+
applied: false,
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
),
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
const hadFailure = results.some((r) => r.status === 'rejected')
|
|
196
|
+
if (hadFailure) {
|
|
197
|
+
throw new UpstreamFailureError('failed to apply action on PDS')
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
encoding: 'application/json',
|
|
204
|
+
body: await moderationService.views.event(moderationEvent),
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const validateLabels = (labels: string[]) => {
|
|
211
|
+
for (const label of labels) {
|
|
212
|
+
for (const char of badChars) {
|
|
213
|
+
if (label.includes(char)) {
|
|
214
|
+
throw new InvalidRequestError(`Invalid label: ${label}`)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const badChars = [' ', ',', ';', `'`, `"`]
|
|
@@ -2,23 +2,17 @@ import { Server } from '../../../../lexicon'
|
|
|
2
2
|
import AppContext from '../../../../context'
|
|
3
3
|
|
|
4
4
|
export default function (server: Server, ctx: AppContext) {
|
|
5
|
-
server.com.atproto.admin.
|
|
5
|
+
server.com.atproto.admin.getModerationEvent({
|
|
6
6
|
auth: ctx.roleVerifier,
|
|
7
7
|
handler: async ({ params }) => {
|
|
8
|
-
const {
|
|
8
|
+
const { id } = params
|
|
9
9
|
const db = ctx.db.getPrimary()
|
|
10
10
|
const moderationService = ctx.services.moderation(db)
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
limit,
|
|
14
|
-
cursor,
|
|
15
|
-
})
|
|
11
|
+
const event = await moderationService.getEventOrThrow(id)
|
|
12
|
+
const eventDetail = await moderationService.views.eventDetail(event)
|
|
16
13
|
return {
|
|
17
14
|
encoding: 'application/json',
|
|
18
|
-
body:
|
|
19
|
-
cursor: results.at(-1)?.id.toString() ?? undefined,
|
|
20
|
-
actions: await moderationService.views.action(results),
|
|
21
|
-
},
|
|
15
|
+
body: eventDetail,
|
|
22
16
|
}
|
|
23
17
|
},
|
|
24
18
|
})
|
|
@@ -18,6 +18,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
18
18
|
if (!result) {
|
|
19
19
|
throw new InvalidRequestError('Record not found', 'RecordNotFound')
|
|
20
20
|
}
|
|
21
|
+
|
|
21
22
|
const [record, accountInfo] = await Promise.all([
|
|
22
23
|
ctx.services.moderation(db).views.recordDetail(result),
|
|
23
24
|
getPdsAccountInfo(ctx, result.did),
|
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
import { Server } from '../../../../lexicon'
|
|
2
2
|
import AppContext from '../../../../context'
|
|
3
|
+
import { getEventType } from '../moderation/util'
|
|
3
4
|
|
|
4
5
|
export default function (server: Server, ctx: AppContext) {
|
|
5
|
-
server.com.atproto.admin.
|
|
6
|
+
server.com.atproto.admin.queryModerationEvents({
|
|
6
7
|
auth: ctx.roleVerifier,
|
|
7
8
|
handler: async ({ params }) => {
|
|
8
9
|
const {
|
|
9
10
|
subject,
|
|
10
|
-
resolved,
|
|
11
|
-
actionType,
|
|
12
11
|
limit = 50,
|
|
13
12
|
cursor,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
sortDirection = 'desc',
|
|
14
|
+
types,
|
|
15
|
+
includeAllUserRecords = false,
|
|
16
|
+
createdBy,
|
|
18
17
|
} = params
|
|
19
18
|
const db = ctx.db.getPrimary()
|
|
20
19
|
const moderationService = ctx.services.moderation(db)
|
|
21
|
-
const results = await moderationService.
|
|
20
|
+
const results = await moderationService.getEvents({
|
|
21
|
+
types: types?.length ? types.map(getEventType) : [],
|
|
22
22
|
subject,
|
|
23
|
-
|
|
24
|
-
actionType,
|
|
23
|
+
createdBy,
|
|
25
24
|
limit,
|
|
26
25
|
cursor,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
reporters,
|
|
30
|
-
actionedBy,
|
|
26
|
+
sortDirection,
|
|
27
|
+
includeAllUserRecords,
|
|
31
28
|
})
|
|
32
29
|
return {
|
|
33
30
|
encoding: 'application/json',
|
|
34
31
|
body: {
|
|
35
|
-
cursor: results.
|
|
36
|
-
|
|
32
|
+
cursor: results.cursor,
|
|
33
|
+
events: await moderationService.views.event(results.events),
|
|
37
34
|
},
|
|
38
35
|
}
|
|
39
36
|
},
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Server } from '../../../../lexicon'
|
|
2
|
+
import AppContext from '../../../../context'
|
|
3
|
+
import { getReviewState } from '../moderation/util'
|
|
4
|
+
|
|
5
|
+
export default function (server: Server, ctx: AppContext) {
|
|
6
|
+
server.com.atproto.admin.queryModerationStatuses({
|
|
7
|
+
auth: ctx.roleVerifier,
|
|
8
|
+
handler: async ({ params }) => {
|
|
9
|
+
const {
|
|
10
|
+
subject,
|
|
11
|
+
takendown,
|
|
12
|
+
reviewState,
|
|
13
|
+
reviewedAfter,
|
|
14
|
+
reviewedBefore,
|
|
15
|
+
reportedAfter,
|
|
16
|
+
reportedBefore,
|
|
17
|
+
ignoreSubjects,
|
|
18
|
+
lastReviewedBy,
|
|
19
|
+
sortDirection = 'desc',
|
|
20
|
+
sortField = 'lastReportedAt',
|
|
21
|
+
includeMuted = false,
|
|
22
|
+
limit = 50,
|
|
23
|
+
cursor,
|
|
24
|
+
} = params
|
|
25
|
+
const db = ctx.db.getPrimary()
|
|
26
|
+
const moderationService = ctx.services.moderation(db)
|
|
27
|
+
const results = await moderationService.getSubjectStatuses({
|
|
28
|
+
reviewState: getReviewState(reviewState),
|
|
29
|
+
subject,
|
|
30
|
+
takendown,
|
|
31
|
+
reviewedAfter,
|
|
32
|
+
reviewedBefore,
|
|
33
|
+
reportedAfter,
|
|
34
|
+
reportedBefore,
|
|
35
|
+
includeMuted,
|
|
36
|
+
ignoreSubjects,
|
|
37
|
+
sortDirection,
|
|
38
|
+
lastReviewedBy,
|
|
39
|
+
sortField,
|
|
40
|
+
limit,
|
|
41
|
+
cursor,
|
|
42
|
+
})
|
|
43
|
+
const subjectStatuses = moderationService.views.subjectStatus(
|
|
44
|
+
results.statuses,
|
|
45
|
+
)
|
|
46
|
+
return {
|
|
47
|
+
encoding: 'application/json',
|
|
48
|
+
body: {
|
|
49
|
+
cursor: results.cursor,
|
|
50
|
+
subjectStatuses,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
}
|
|
@@ -22,15 +22,17 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
const report = await db.transaction(async (dbTxn) => {
|
|
26
|
+
const moderationTxn = ctx.services.moderation(dbTxn)
|
|
27
|
+
return moderationTxn.report({
|
|
28
|
+
reasonType: getReasonType(reasonType),
|
|
29
|
+
reason,
|
|
30
|
+
subject: getSubject(subject),
|
|
31
|
+
reportedBy: requester || ctx.cfg.serverDid,
|
|
32
|
+
})
|
|
32
33
|
})
|
|
33
34
|
|
|
35
|
+
const moderationService = ctx.services.moderation(db)
|
|
34
36
|
return {
|
|
35
37
|
encoding: 'application/json',
|
|
36
38
|
body: moderationService.views.reportPublic(report),
|