@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
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
import { sql } from 'kysely'
|
|
2
|
+
import { wait } from '@atproto/common'
|
|
2
3
|
import { Database } from '../../db'
|
|
3
4
|
import { notSoftDeletedClause } from '../../db/util'
|
|
4
5
|
import { ActorViews } from './views'
|
|
5
6
|
import { ImageUriBuilder } from '../../image/uri'
|
|
6
7
|
import { Actor } from '../../db/tables/actor'
|
|
7
|
-
import { LabelCache } from '../../label-cache'
|
|
8
8
|
import { TimeCidKeyset, paginate } from '../../db/pagination'
|
|
9
9
|
import { SearchKeyset, getUserSearchQuery } from '../util/search'
|
|
10
|
+
import { FromDb } from '../types'
|
|
11
|
+
import { GraphService } from '../graph'
|
|
12
|
+
import { LabelService } from '../label'
|
|
10
13
|
|
|
11
14
|
export * from './types'
|
|
12
15
|
|
|
13
16
|
export class ActorService {
|
|
17
|
+
views: ActorViews
|
|
18
|
+
|
|
14
19
|
constructor(
|
|
15
20
|
public db: Database,
|
|
16
21
|
public imgUriBuilder: ImageUriBuilder,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return (db: Database) => new ActorService(db, imgUriBuilder, labelCache)
|
|
22
|
+
private graph: FromDb<GraphService>,
|
|
23
|
+
private label: FromDb<LabelService>,
|
|
24
|
+
) {
|
|
25
|
+
this.views = new ActorViews(this.db, this.imgUriBuilder, graph, label)
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
static creator(
|
|
29
|
+
imgUriBuilder: ImageUriBuilder,
|
|
30
|
+
graph: FromDb<GraphService>,
|
|
31
|
+
label: FromDb<LabelService>,
|
|
32
|
+
) {
|
|
33
|
+
return (db: Database) => new ActorService(db, imgUriBuilder, graph, label)
|
|
34
|
+
}
|
|
25
35
|
|
|
26
36
|
async getActorDid(handleOrDid: string): Promise<string | null> {
|
|
27
37
|
if (handleOrDid.startsWith('did:')) {
|
|
@@ -144,6 +154,44 @@ export class ActorService {
|
|
|
144
154
|
.executeTakeFirst()
|
|
145
155
|
return res?.repoRev ?? null
|
|
146
156
|
}
|
|
157
|
+
|
|
158
|
+
async *all(
|
|
159
|
+
opts: {
|
|
160
|
+
batchSize?: number
|
|
161
|
+
forever?: boolean
|
|
162
|
+
cooldownMs?: number
|
|
163
|
+
startFromDid?: string
|
|
164
|
+
} = {},
|
|
165
|
+
) {
|
|
166
|
+
const {
|
|
167
|
+
cooldownMs = 1000,
|
|
168
|
+
batchSize = 1000,
|
|
169
|
+
forever = false,
|
|
170
|
+
startFromDid,
|
|
171
|
+
} = opts
|
|
172
|
+
const baseQuery = this.db.db
|
|
173
|
+
.selectFrom('actor')
|
|
174
|
+
.selectAll()
|
|
175
|
+
.orderBy('did')
|
|
176
|
+
.limit(batchSize)
|
|
177
|
+
while (true) {
|
|
178
|
+
let cursor = startFromDid
|
|
179
|
+
do {
|
|
180
|
+
const actors = cursor
|
|
181
|
+
? await baseQuery.where('did', '>', cursor).execute()
|
|
182
|
+
: await baseQuery.execute()
|
|
183
|
+
for (const actor of actors) {
|
|
184
|
+
yield actor
|
|
185
|
+
}
|
|
186
|
+
cursor = actors.at(-1)?.did
|
|
187
|
+
} while (cursor)
|
|
188
|
+
if (forever) {
|
|
189
|
+
await wait(cooldownMs)
|
|
190
|
+
} else {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
147
195
|
}
|
|
148
196
|
|
|
149
197
|
type ActorResult = Actor
|
|
@@ -11,7 +11,6 @@ import { Actor } from '../../db/tables/actor'
|
|
|
11
11
|
import { ImageUriBuilder } from '../../image/uri'
|
|
12
12
|
import { LabelService, Labels, getSelfLabels } from '../label'
|
|
13
13
|
import { BlockAndMuteState, GraphService } from '../graph'
|
|
14
|
-
import { LabelCache } from '../../label-cache'
|
|
15
14
|
import {
|
|
16
15
|
ActorInfoMap,
|
|
17
16
|
ProfileDetailHydrationState,
|
|
@@ -21,17 +20,24 @@ import {
|
|
|
21
20
|
toMapByDid,
|
|
22
21
|
} from './types'
|
|
23
22
|
import { ListInfoMap } from '../graph/types'
|
|
23
|
+
import { FromDb } from '../types'
|
|
24
24
|
|
|
25
25
|
export class ActorViews {
|
|
26
|
+
services: {
|
|
27
|
+
label: LabelService
|
|
28
|
+
graph: GraphService
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
constructor(
|
|
27
32
|
private db: Database,
|
|
28
33
|
private imgUriBuilder: ImageUriBuilder,
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
private graph: FromDb<GraphService>,
|
|
35
|
+
private label: FromDb<LabelService>,
|
|
36
|
+
) {
|
|
37
|
+
this.services = {
|
|
38
|
+
label: label(db),
|
|
39
|
+
graph: graph(db),
|
|
40
|
+
}
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
async profiles(
|
|
@@ -45,10 +51,7 @@ export class ActorViews {
|
|
|
45
51
|
viewer,
|
|
46
52
|
...opts,
|
|
47
53
|
})
|
|
48
|
-
return this.profilePresentation(dids, hydrated,
|
|
49
|
-
viewer,
|
|
50
|
-
...opts,
|
|
51
|
-
})
|
|
54
|
+
return this.profilePresentation(dids, hydrated, viewer)
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
async profilesBasic(
|
|
@@ -62,10 +65,7 @@ export class ActorViews {
|
|
|
62
65
|
viewer,
|
|
63
66
|
includeSoftDeleted: opts?.includeSoftDeleted,
|
|
64
67
|
})
|
|
65
|
-
return this.profileBasicPresentation(dids, hydrated,
|
|
66
|
-
viewer,
|
|
67
|
-
omitLabels: opts?.omitLabels,
|
|
68
|
-
})
|
|
68
|
+
return this.profileBasicPresentation(dids, hydrated, viewer, opts)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
async profilesList(
|
|
@@ -293,11 +293,8 @@ export class ActorViews {
|
|
|
293
293
|
labels: Labels
|
|
294
294
|
bam: BlockAndMuteState
|
|
295
295
|
},
|
|
296
|
-
|
|
297
|
-
viewer?: string | null
|
|
298
|
-
},
|
|
296
|
+
viewer: string | null,
|
|
299
297
|
): ProfileViewMap {
|
|
300
|
-
const { viewer } = opts ?? {}
|
|
301
298
|
const { profiles, lists, labels, bam } = state
|
|
302
299
|
return dids.reduce((acc, did) => {
|
|
303
300
|
const prof = profiles[did]
|
|
@@ -357,12 +354,12 @@ export class ActorViews {
|
|
|
357
354
|
profileBasicPresentation(
|
|
358
355
|
dids: string[],
|
|
359
356
|
state: ProfileHydrationState,
|
|
357
|
+
viewer: string | null,
|
|
360
358
|
opts?: {
|
|
361
|
-
viewer?: string | null
|
|
362
359
|
omitLabels?: boolean
|
|
363
360
|
},
|
|
364
361
|
): ProfileViewMap {
|
|
365
|
-
const result = this.profilePresentation(dids, state,
|
|
362
|
+
const result = this.profilePresentation(dids, state, viewer)
|
|
366
363
|
return Object.values(result).reduce((acc, prof) => {
|
|
367
364
|
const profileBasic = {
|
|
368
365
|
did: prof.did,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sql } from 'kysely'
|
|
2
2
|
import { AtUri } from '@atproto/syntax'
|
|
3
3
|
import { jsonStringToLex } from '@atproto/lexicon'
|
|
4
|
+
import { mapDefined } from '@atproto/common'
|
|
4
5
|
import { Database } from '../../db'
|
|
5
6
|
import { countAll, noMatch, notSoftDeletedClause } from '../../db/util'
|
|
6
7
|
import { ImageUriBuilder } from '../../image/uri'
|
|
@@ -42,28 +43,42 @@ import {
|
|
|
42
43
|
RelationshipPair,
|
|
43
44
|
} from '../graph'
|
|
44
45
|
import { FeedViews } from './views'
|
|
45
|
-
import { LabelCache } from '../../label-cache'
|
|
46
46
|
import { threadgateToPostUri, postToThreadgateUri } from './util'
|
|
47
|
+
import { FromDb } from '../types'
|
|
47
48
|
|
|
48
49
|
export * from './types'
|
|
49
50
|
|
|
50
51
|
export class FeedService {
|
|
52
|
+
views: FeedViews
|
|
53
|
+
services: {
|
|
54
|
+
label: LabelService
|
|
55
|
+
actor: ActorService
|
|
56
|
+
graph: GraphService
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
constructor(
|
|
52
60
|
public db: Database,
|
|
53
61
|
public imgUriBuilder: ImageUriBuilder,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
private actor: FromDb<ActorService>,
|
|
63
|
+
private label: FromDb<LabelService>,
|
|
64
|
+
private graph: FromDb<GraphService>,
|
|
65
|
+
) {
|
|
66
|
+
this.views = new FeedViews(this.db, this.imgUriBuilder, actor, graph)
|
|
67
|
+
this.services = {
|
|
68
|
+
label: label(this.db),
|
|
69
|
+
actor: actor(this.db),
|
|
70
|
+
graph: graph(this.db),
|
|
71
|
+
}
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
static creator(
|
|
66
|
-
|
|
74
|
+
static creator(
|
|
75
|
+
imgUriBuilder: ImageUriBuilder,
|
|
76
|
+
actor: FromDb<ActorService>,
|
|
77
|
+
label: FromDb<LabelService>,
|
|
78
|
+
graph: FromDb<GraphService>,
|
|
79
|
+
) {
|
|
80
|
+
return (db: Database) =>
|
|
81
|
+
new FeedService(db, imgUriBuilder, actor, label, graph)
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
selectPostQb() {
|
|
@@ -205,6 +220,11 @@ export class FeedService {
|
|
|
205
220
|
}, {} as Record<string, FeedRow>)
|
|
206
221
|
}
|
|
207
222
|
|
|
223
|
+
async postUrisToFeedItems(uris: string[]): Promise<FeedRow[]> {
|
|
224
|
+
const feedItems = await this.getFeedItems(uris)
|
|
225
|
+
return mapDefined(uris, (uri) => feedItems[uri])
|
|
226
|
+
}
|
|
227
|
+
|
|
208
228
|
feedItemRefs(items: FeedRow[]) {
|
|
209
229
|
const actorDids = new Set<string>()
|
|
210
230
|
const postUris = new Set<string>()
|
|
@@ -399,20 +419,32 @@ export class FeedService {
|
|
|
399
419
|
const actorInfos = this.services.actor.views.profileBasicPresentation(
|
|
400
420
|
[...nestedDids],
|
|
401
421
|
feedState,
|
|
402
|
-
|
|
422
|
+
viewer,
|
|
403
423
|
)
|
|
404
424
|
const recordEmbedViews: RecordEmbedViewRecordMap = {}
|
|
405
425
|
for (const uri of nestedUris) {
|
|
406
426
|
const collection = new AtUri(uri).collection
|
|
407
427
|
if (collection === ids.AppBskyFeedGenerator && feedGenInfos[uri]) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
428
|
+
const genView = this.views.formatFeedGeneratorView(
|
|
429
|
+
feedGenInfos[uri],
|
|
430
|
+
actorInfos,
|
|
431
|
+
)
|
|
432
|
+
if (genView) {
|
|
433
|
+
recordEmbedViews[uri] = {
|
|
434
|
+
$type: 'app.bsky.feed.defs#generatorView',
|
|
435
|
+
...genView,
|
|
436
|
+
}
|
|
411
437
|
}
|
|
412
438
|
} else if (collection === ids.AppBskyGraphList && listViews[uri]) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
439
|
+
const listView = this.services.graph.formatListView(
|
|
440
|
+
listViews[uri],
|
|
441
|
+
actorInfos,
|
|
442
|
+
)
|
|
443
|
+
if (listView) {
|
|
444
|
+
recordEmbedViews[uri] = {
|
|
445
|
+
$type: 'app.bsky.graph.defs#listView',
|
|
446
|
+
...listView,
|
|
447
|
+
}
|
|
416
448
|
}
|
|
417
449
|
} else if (collection === ids.AppBskyFeedPost && feedState.posts[uri]) {
|
|
418
450
|
const formatted = this.views.formatPostView(
|
|
@@ -423,6 +455,7 @@ export class FeedService {
|
|
|
423
455
|
feedState.embeds,
|
|
424
456
|
feedState.labels,
|
|
425
457
|
feedState.lists,
|
|
458
|
+
viewer,
|
|
426
459
|
)
|
|
427
460
|
recordEmbedViews[uri] = this.views.getRecordEmbedView(
|
|
428
461
|
uri,
|
|
@@ -36,43 +36,72 @@ export const invalidReplyRoot = (
|
|
|
36
36
|
return parent.record.reply?.root.uri !== replyRoot
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
type ParsedThreadGate = {
|
|
40
|
+
canReply?: boolean
|
|
41
|
+
allowMentions?: boolean
|
|
42
|
+
allowFollowing?: boolean
|
|
43
|
+
allowListUris?: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const parseThreadGate = (
|
|
47
|
+
replierDid: string,
|
|
48
|
+
ownerDid: string,
|
|
49
|
+
rootPost: PostRecord | null,
|
|
44
50
|
gate: GateRecord | null,
|
|
45
|
-
) => {
|
|
46
|
-
if (
|
|
47
|
-
|
|
51
|
+
): ParsedThreadGate => {
|
|
52
|
+
if (replierDid === ownerDid) {
|
|
53
|
+
return { canReply: true }
|
|
54
|
+
}
|
|
55
|
+
// if gate.allow is unset then *any* reply is allowed, if it is an empty array then *no* reply is allowed
|
|
56
|
+
if (!gate || !gate.allow) {
|
|
57
|
+
return { canReply: true }
|
|
58
|
+
}
|
|
48
59
|
|
|
49
|
-
const allowMentions = gate.allow.find(isMentionRule)
|
|
50
|
-
const allowFollowing = gate.allow.find(isFollowingRule)
|
|
60
|
+
const allowMentions = !!gate.allow.find(isMentionRule)
|
|
61
|
+
const allowFollowing = !!gate.allow.find(isFollowingRule)
|
|
51
62
|
const allowListUris = gate.allow?.filter(isListRule).map((item) => item.list)
|
|
52
63
|
|
|
53
64
|
// check mentions first since it's quick and synchronous
|
|
54
65
|
if (allowMentions) {
|
|
55
|
-
const isMentioned =
|
|
56
|
-
return facet.features.some(
|
|
66
|
+
const isMentioned = rootPost?.facets?.some((facet) => {
|
|
67
|
+
return facet.features.some(
|
|
68
|
+
(item) => isMention(item) && item.did === replierDid,
|
|
69
|
+
)
|
|
57
70
|
})
|
|
58
71
|
if (isMentioned) {
|
|
59
|
-
return
|
|
72
|
+
return { canReply: true, allowMentions, allowFollowing, allowListUris }
|
|
60
73
|
}
|
|
61
74
|
}
|
|
75
|
+
return { allowMentions, allowFollowing, allowListUris }
|
|
76
|
+
}
|
|
62
77
|
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
export const violatesThreadGate = async (
|
|
79
|
+
db: DatabaseSchema,
|
|
80
|
+
replierDid: string,
|
|
81
|
+
ownerDid: string,
|
|
82
|
+
rootPost: PostRecord | null,
|
|
83
|
+
gate: GateRecord | null,
|
|
84
|
+
) => {
|
|
85
|
+
const {
|
|
86
|
+
canReply,
|
|
87
|
+
allowFollowing,
|
|
88
|
+
allowListUris = [],
|
|
89
|
+
} = parseThreadGate(replierDid, ownerDid, rootPost, gate)
|
|
90
|
+
if (canReply) {
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
if (!allowFollowing && !allowListUris?.length) {
|
|
65
94
|
return true
|
|
66
95
|
}
|
|
67
96
|
const { ref } = db.dynamic
|
|
68
97
|
const nullResult = sql<null>`${null}`
|
|
69
98
|
const check = await db
|
|
70
|
-
.selectFrom(valuesList([
|
|
99
|
+
.selectFrom(valuesList([replierDid]).as(sql`subject (did)`))
|
|
71
100
|
.select([
|
|
72
101
|
allowFollowing
|
|
73
102
|
? db
|
|
74
103
|
.selectFrom('follow')
|
|
75
|
-
.where('creator', '=',
|
|
104
|
+
.where('creator', '=', ownerDid)
|
|
76
105
|
.whereRef('subjectDid', '=', ref('subject.did'))
|
|
77
106
|
.select('creator')
|
|
78
107
|
.as('isFollowed')
|
|
@@ -91,8 +120,7 @@ export const violatesThreadGate = async (
|
|
|
91
120
|
|
|
92
121
|
if (allowFollowing && check?.isFollowed) {
|
|
93
122
|
return false
|
|
94
|
-
}
|
|
95
|
-
if (allowListUris.length && check?.isInList) {
|
|
123
|
+
} else if (allowListUris.length && check?.isInList) {
|
|
96
124
|
return false
|
|
97
125
|
}
|
|
98
126
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { mapDefined } from '@atproto/common'
|
|
2
|
+
import { AtUri } from '@atproto/syntax'
|
|
2
3
|
import { Database } from '../../db'
|
|
3
4
|
import {
|
|
4
5
|
FeedViewPost,
|
|
5
6
|
GeneratorView,
|
|
6
7
|
PostView,
|
|
7
8
|
} from '../../lexicon/types/app/bsky/feed/defs'
|
|
8
|
-
import { isListRule } from '../../lexicon/types/app/bsky/feed/threadgate'
|
|
9
9
|
import {
|
|
10
10
|
Main as EmbedImages,
|
|
11
11
|
isMain as isEmbedImages,
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
ViewNotFound,
|
|
23
23
|
ViewRecord,
|
|
24
24
|
} from '../../lexicon/types/app/bsky/embed/record'
|
|
25
|
+
import { Record as PostRecord } from '../../lexicon/types/app/bsky/feed/post'
|
|
26
|
+
import { isListRule } from '../../lexicon/types/app/bsky/feed/threadgate'
|
|
25
27
|
import {
|
|
26
28
|
PostEmbedViews,
|
|
27
29
|
FeedGenInfo,
|
|
@@ -36,31 +38,45 @@ import {
|
|
|
36
38
|
} from './types'
|
|
37
39
|
import { Labels, getSelfLabels } from '../label'
|
|
38
40
|
import { ImageUriBuilder } from '../../image/uri'
|
|
39
|
-
import { LabelCache } from '../../label-cache'
|
|
40
41
|
import { ActorInfoMap, ActorService } from '../actor'
|
|
41
42
|
import { ListInfoMap, GraphService } from '../graph'
|
|
43
|
+
import { FromDb } from '../types'
|
|
44
|
+
import { parseThreadGate } from './util'
|
|
42
45
|
|
|
43
46
|
export class FeedViews {
|
|
47
|
+
services: {
|
|
48
|
+
actor: ActorService
|
|
49
|
+
graph: GraphService
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
constructor(
|
|
45
53
|
public db: Database,
|
|
46
54
|
public imgUriBuilder: ImageUriBuilder,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
private actor: FromDb<ActorService>,
|
|
56
|
+
private graph: FromDb<GraphService>,
|
|
57
|
+
) {
|
|
58
|
+
this.services = {
|
|
59
|
+
actor: actor(this.db),
|
|
60
|
+
graph: graph(this.db),
|
|
61
|
+
}
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
static creator(
|
|
65
|
+
imgUriBuilder: ImageUriBuilder,
|
|
66
|
+
actor: FromDb<ActorService>,
|
|
67
|
+
graph: FromDb<GraphService>,
|
|
68
|
+
) {
|
|
69
|
+
return (db: Database) => new FeedViews(db, imgUriBuilder, actor, graph)
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
formatFeedGeneratorView(
|
|
60
73
|
info: FeedGenInfo,
|
|
61
74
|
profiles: ActorInfoMap,
|
|
62
|
-
): GeneratorView {
|
|
75
|
+
): GeneratorView | undefined {
|
|
63
76
|
const profile = profiles[info.creator]
|
|
77
|
+
if (!profile) {
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
64
80
|
return {
|
|
65
81
|
uri: info.uri,
|
|
66
82
|
cid: info.cid,
|
|
@@ -91,8 +107,8 @@ export class FeedViews {
|
|
|
91
107
|
formatFeed(
|
|
92
108
|
items: FeedRow[],
|
|
93
109
|
state: FeedHydrationState,
|
|
110
|
+
viewer: string | null,
|
|
94
111
|
opts?: {
|
|
95
|
-
viewer?: string | null
|
|
96
112
|
usePostViewUnion?: boolean
|
|
97
113
|
},
|
|
98
114
|
): FeedViewPost[] {
|
|
@@ -101,7 +117,7 @@ export class FeedViews {
|
|
|
101
117
|
const actors = this.services.actor.views.profileBasicPresentation(
|
|
102
118
|
Object.keys(profiles),
|
|
103
119
|
state,
|
|
104
|
-
|
|
120
|
+
viewer,
|
|
105
121
|
)
|
|
106
122
|
const feed: FeedViewPost[] = []
|
|
107
123
|
for (const item of items) {
|
|
@@ -114,6 +130,7 @@ export class FeedViews {
|
|
|
114
130
|
embeds,
|
|
115
131
|
labels,
|
|
116
132
|
lists,
|
|
133
|
+
viewer,
|
|
117
134
|
)
|
|
118
135
|
// skip over not found & blocked posts
|
|
119
136
|
if (!post || blocks[post.uri]?.reply) {
|
|
@@ -149,6 +166,7 @@ export class FeedViews {
|
|
|
149
166
|
labels,
|
|
150
167
|
lists,
|
|
151
168
|
blocks,
|
|
169
|
+
viewer,
|
|
152
170
|
opts,
|
|
153
171
|
)
|
|
154
172
|
const replyRoot = this.formatMaybePostView(
|
|
@@ -160,6 +178,7 @@ export class FeedViews {
|
|
|
160
178
|
labels,
|
|
161
179
|
lists,
|
|
162
180
|
blocks,
|
|
181
|
+
viewer,
|
|
163
182
|
opts,
|
|
164
183
|
)
|
|
165
184
|
if (replyRoot && replyParent) {
|
|
@@ -182,6 +201,7 @@ export class FeedViews {
|
|
|
182
201
|
embeds: PostEmbedViews,
|
|
183
202
|
labels: Labels,
|
|
184
203
|
lists: ListInfoMap,
|
|
204
|
+
viewer: string | null,
|
|
185
205
|
): PostView | undefined {
|
|
186
206
|
const post = posts[uri]
|
|
187
207
|
const gate = threadgates[uri]
|
|
@@ -207,6 +227,14 @@ export class FeedViews {
|
|
|
207
227
|
? {
|
|
208
228
|
repost: post.requesterRepost ?? undefined,
|
|
209
229
|
like: post.requesterLike ?? undefined,
|
|
230
|
+
replyDisabled: this.userReplyDisabled(
|
|
231
|
+
uri,
|
|
232
|
+
actors,
|
|
233
|
+
posts,
|
|
234
|
+
threadgates,
|
|
235
|
+
lists,
|
|
236
|
+
viewer,
|
|
237
|
+
),
|
|
210
238
|
}
|
|
211
239
|
: undefined,
|
|
212
240
|
labels: [...postLabels, ...postSelfLabels],
|
|
@@ -217,6 +245,50 @@ export class FeedViews {
|
|
|
217
245
|
}
|
|
218
246
|
}
|
|
219
247
|
|
|
248
|
+
userReplyDisabled(
|
|
249
|
+
uri: string,
|
|
250
|
+
actors: ActorInfoMap,
|
|
251
|
+
posts: PostInfoMap,
|
|
252
|
+
threadgates: ThreadgateInfoMap,
|
|
253
|
+
lists: ListInfoMap,
|
|
254
|
+
viewer: string | null,
|
|
255
|
+
): boolean | undefined {
|
|
256
|
+
if (viewer === null) {
|
|
257
|
+
return undefined
|
|
258
|
+
} else if (posts[uri]?.violatesThreadGate) {
|
|
259
|
+
return true
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const rootUriStr: string =
|
|
263
|
+
posts[uri]?.record?.['reply']?.['root']?.['uri'] ?? uri
|
|
264
|
+
const gate = threadgates[rootUriStr]?.record
|
|
265
|
+
if (!gate) {
|
|
266
|
+
return undefined
|
|
267
|
+
}
|
|
268
|
+
const rootPost = posts[rootUriStr]?.record as PostRecord | undefined
|
|
269
|
+
const ownerDid = new AtUri(rootUriStr).hostname
|
|
270
|
+
|
|
271
|
+
const {
|
|
272
|
+
canReply,
|
|
273
|
+
allowFollowing,
|
|
274
|
+
allowListUris = [],
|
|
275
|
+
} = parseThreadGate(viewer, ownerDid, rootPost ?? null, gate ?? null)
|
|
276
|
+
|
|
277
|
+
if (canReply) {
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
if (allowFollowing && actors[ownerDid]?.viewer?.followedBy) {
|
|
281
|
+
return false
|
|
282
|
+
}
|
|
283
|
+
for (const listUri of allowListUris) {
|
|
284
|
+
const list = lists[listUri]
|
|
285
|
+
if (list?.viewerInList) {
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return true
|
|
290
|
+
}
|
|
291
|
+
|
|
220
292
|
formatMaybePostView(
|
|
221
293
|
uri: string,
|
|
222
294
|
actors: ActorInfoMap,
|
|
@@ -226,6 +298,7 @@ export class FeedViews {
|
|
|
226
298
|
labels: Labels,
|
|
227
299
|
lists: ListInfoMap,
|
|
228
300
|
blocks: PostBlocksMap,
|
|
301
|
+
viewer: string | null,
|
|
229
302
|
opts?: {
|
|
230
303
|
usePostViewUnion?: boolean
|
|
231
304
|
},
|
|
@@ -238,6 +311,7 @@ export class FeedViews {
|
|
|
238
311
|
embeds,
|
|
239
312
|
labels,
|
|
240
313
|
lists,
|
|
314
|
+
viewer,
|
|
241
315
|
)
|
|
242
316
|
if (!post) {
|
|
243
317
|
if (!opts?.usePostViewUnion) return
|
|
@@ -4,6 +4,10 @@ import { ImageUriBuilder } from '../../image/uri'
|
|
|
4
4
|
import { valuesList } from '../../db/util'
|
|
5
5
|
import { ListInfo } from './types'
|
|
6
6
|
import { ActorInfoMap } from '../actor'
|
|
7
|
+
import {
|
|
8
|
+
ListView,
|
|
9
|
+
ListViewBasic,
|
|
10
|
+
} from '../../lexicon/types/app/bsky/graph/defs'
|
|
7
11
|
|
|
8
12
|
export * from './types'
|
|
9
13
|
|
|
@@ -91,6 +95,12 @@ export class GraphService {
|
|
|
91
95
|
.whereRef('list_block.subjectUri', '=', ref('list.uri'))
|
|
92
96
|
.select('list_block.uri')
|
|
93
97
|
.as('viewerListBlockUri'),
|
|
98
|
+
this.db.db
|
|
99
|
+
.selectFrom('list_item')
|
|
100
|
+
.whereRef('list_item.listUri', '=', ref('list.uri'))
|
|
101
|
+
.where('list_item.subjectDid', '=', viewer ?? '')
|
|
102
|
+
.select('list_item.uri')
|
|
103
|
+
.as('viewerInList'),
|
|
94
104
|
])
|
|
95
105
|
}
|
|
96
106
|
|
|
@@ -99,7 +109,11 @@ export class GraphService {
|
|
|
99
109
|
.selectFrom('list_item')
|
|
100
110
|
.innerJoin('actor as subject', 'subject.did', 'list_item.subjectDid')
|
|
101
111
|
.selectAll('subject')
|
|
102
|
-
.select([
|
|
112
|
+
.select([
|
|
113
|
+
'list_item.uri as uri',
|
|
114
|
+
'list_item.cid as cid',
|
|
115
|
+
'list_item.sortAt as sortAt',
|
|
116
|
+
])
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
async getBlockAndMuteState(
|
|
@@ -229,7 +243,10 @@ export class GraphService {
|
|
|
229
243
|
)
|
|
230
244
|
}
|
|
231
245
|
|
|
232
|
-
formatListView(list: ListInfo, profiles: ActorInfoMap) {
|
|
246
|
+
formatListView(list: ListInfo, profiles: ActorInfoMap): ListView | undefined {
|
|
247
|
+
if (!profiles[list.creator]) {
|
|
248
|
+
return undefined
|
|
249
|
+
}
|
|
233
250
|
return {
|
|
234
251
|
...this.formatListViewBasic(list),
|
|
235
252
|
creator: profiles[list.creator],
|
|
@@ -237,10 +254,11 @@ export class GraphService {
|
|
|
237
254
|
descriptionFacets: list.descriptionFacets
|
|
238
255
|
? JSON.parse(list.descriptionFacets)
|
|
239
256
|
: undefined,
|
|
257
|
+
indexedAt: list.sortAt,
|
|
240
258
|
}
|
|
241
259
|
}
|
|
242
260
|
|
|
243
|
-
formatListViewBasic(list: ListInfo) {
|
|
261
|
+
formatListViewBasic(list: ListInfo): ListViewBasic {
|
|
244
262
|
return {
|
|
245
263
|
uri: list.uri,
|
|
246
264
|
cid: list.cid,
|