@atproto/bsky 0.0.147 → 0.0.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/LICENSE.txt +1 -1
- package/dist/api/app/bsky/feed/getListFeed.js +1 -1
- package/dist/api/app/bsky/feed/getListFeed.js.map +1 -1
- package/dist/api/app/bsky/graph/getFollowers.js +1 -1
- package/dist/api/app/bsky/graph/getFollowers.js.map +1 -1
- package/dist/api/app/bsky/graph/getFollows.js +1 -1
- package/dist/api/app/bsky/graph/getFollows.js.map +1 -1
- package/dist/api/app/bsky/graph/getList.js +1 -1
- package/dist/api/app/bsky/graph/getList.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getConfig.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getConfig.js +1 -0
- package/dist/api/app/bsky/unspecced/getConfig.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.js +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsers.js +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsers.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getTrends.js +1 -1
- package/dist/api/app/bsky/unspecced/getTrends.js.map +1 -1
- package/dist/api/com/atproto/identity/resolveHandle.d.ts.map +1 -1
- package/dist/api/com/atproto/identity/resolveHandle.js +3 -13
- package/dist/api/com/atproto/identity/resolveHandle.js.map +1 -1
- package/dist/api/health.js +1 -1
- package/dist/api/health.js.map +1 -1
- package/dist/api/well-known.d.ts.map +1 -1
- package/dist/api/well-known.js +4 -1
- package/dist/api/well-known.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -0
- package/dist/config.js.map +1 -1
- package/dist/data-plane/server/background.js +1 -1
- package/dist/data-plane/server/background.js.map +1 -1
- package/dist/data-plane/server/indexing/index.d.ts +2 -0
- package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
- package/dist/data-plane/server/indexing/index.js +2 -0
- package/dist/data-plane/server/indexing/index.js.map +1 -1
- package/dist/data-plane/server/indexing/plugins/status.d.ts +7 -0
- package/dist/data-plane/server/indexing/plugins/status.d.ts.map +1 -0
- package/dist/data-plane/server/indexing/plugins/status.js +73 -0
- package/dist/data-plane/server/indexing/plugins/status.js.map +1 -0
- package/dist/data-plane/server/routes/index.d.ts.map +1 -1
- package/dist/data-plane/server/routes/index.js +0 -2
- package/dist/data-plane/server/routes/index.js.map +1 -1
- package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
- package/dist/data-plane/server/routes/profile.js +8 -1
- package/dist/data-plane/server/routes/profile.js.map +1 -1
- package/dist/data-plane/server/routes/records.d.ts.map +1 -1
- package/dist/data-plane/server/routes/records.js +1 -0
- package/dist/data-plane/server/routes/records.js.map +1 -1
- package/dist/error.js +1 -1
- package/dist/error.js.map +1 -1
- package/dist/hydration/actor.d.ts +8 -1
- package/dist/hydration/actor.d.ts.map +1 -1
- package/dist/hydration/actor.js +18 -2
- package/dist/hydration/actor.js.map +1 -1
- package/dist/hydration/feed.d.ts +2 -2
- package/dist/hydration/feed.d.ts.map +1 -1
- package/dist/hydration/feed.js +10 -4
- package/dist/hydration/feed.js.map +1 -1
- package/dist/hydration/hydrator.d.ts +4 -3
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +47 -22
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/hydration/label.d.ts +1 -1
- package/dist/hydration/label.d.ts.map +1 -1
- package/dist/hydration/label.js +5 -2
- package/dist/hydration/label.js.map +1 -1
- package/dist/image/server.js +1 -1
- package/dist/image/server.js.map +1 -1
- package/dist/lexicon/index.d.ts +5 -2
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +8 -5
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +288 -82
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +146 -42
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +21 -0
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js +9 -0
- package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/status.d.ts +23 -0
- package/dist/lexicon/types/app/bsky/actor/status.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/actor/status.js +19 -0
- package/dist/lexicon/types/app/bsky/actor/status.js.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/getConfig.d.ts +9 -0
- package/dist/lexicon/types/app/bsky/unspecced/getConfig.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/getConfig.js +9 -0
- package/dist/lexicon/types/app/bsky/unspecced/getConfig.js.map +1 -1
- package/dist/proto/bsky_connect.d.ts +10 -12
- package/dist/proto/bsky_connect.d.ts.map +1 -1
- package/dist/proto/bsky_connect.js +9 -11
- package/dist/proto/bsky_connect.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +58 -37
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +189 -115
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/views/index.d.ts +2 -1
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +37 -10
- package/dist/views/index.js.map +1 -1
- package/package.json +13 -13
- package/proto/bsky.proto +16 -17
- package/src/api/app/bsky/feed/getListFeed.ts +1 -1
- package/src/api/app/bsky/graph/getFollowers.ts +4 -1
- package/src/api/app/bsky/graph/getFollows.ts +4 -1
- package/src/api/app/bsky/graph/getList.ts +1 -1
- package/src/api/app/bsky/unspecced/getConfig.ts +1 -0
- package/src/api/app/bsky/unspecced/getSuggestedStarterPacks.ts +1 -1
- package/src/api/app/bsky/unspecced/getSuggestedUsers.ts +1 -1
- package/src/api/app/bsky/unspecced/getTrends.ts +1 -1
- package/src/api/com/atproto/identity/resolveHandle.ts +3 -16
- package/src/api/health.ts +1 -1
- package/src/api/well-known.ts +4 -1
- package/src/config.ts +39 -0
- package/src/data-plane/server/background.ts +1 -1
- package/src/data-plane/server/indexing/index.ts +3 -0
- package/src/data-plane/server/indexing/plugins/status.ts +61 -0
- package/src/data-plane/server/routes/index.ts +0 -2
- package/src/data-plane/server/routes/profile.ts +43 -27
- package/src/data-plane/server/routes/records.ts +1 -0
- package/src/error.ts +1 -1
- package/src/hydration/actor.ts +27 -2
- package/src/hydration/feed.ts +16 -4
- package/src/hydration/hydrator.ts +64 -21
- package/src/hydration/label.ts +8 -2
- package/src/image/server.ts +1 -1
- package/src/lexicon/index.ts +12 -9
- package/src/lexicon/lexicons.ts +151 -43
- package/src/lexicon/types/app/bsky/actor/defs.ts +26 -0
- package/src/lexicon/types/app/bsky/actor/status.ts +40 -0
- package/src/lexicon/types/app/bsky/unspecced/getConfig.ts +17 -0
- package/src/proto/bsky_connect.ts +11 -13
- package/src/proto/bsky_pb.ts +173 -119
- package/src/views/index.ts +47 -11
- package/tests/views/__snapshots__/profile.test.ts.snap +60 -0
- package/tests/views/__snapshots__/thread.test.ts.snap +203 -11
- package/tests/views/get-config.test.ts +66 -0
- package/tests/views/profile.test.ts +189 -1
- package/tests/views/thread.test.ts +134 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
- package/dist/data-plane/server/routes/posts.d.ts +0 -6
- package/dist/data-plane/server/routes/posts.d.ts.map +0 -1
- package/dist/data-plane/server/routes/posts.js +0 -20
- package/dist/data-plane/server/routes/posts.js.map +0 -1
- package/src/data-plane/server/routes/posts.ts +0 -21
package/src/config.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
|
+
import { subLogger as log } from './logger'
|
|
3
|
+
|
|
4
|
+
type LiveNowConfig = {
|
|
5
|
+
did: string
|
|
6
|
+
domains: string[]
|
|
7
|
+
}[]
|
|
2
8
|
|
|
3
9
|
export interface ServerConfigValues {
|
|
4
10
|
// service
|
|
@@ -9,6 +15,7 @@ export interface ServerConfigValues {
|
|
|
9
15
|
serverDid: string
|
|
10
16
|
alternateAudienceDids: string[]
|
|
11
17
|
entrywayJwtPublicKeyHex?: string
|
|
18
|
+
liveNowConfig?: LiveNowConfig
|
|
12
19
|
// external services
|
|
13
20
|
etcdHosts: string[]
|
|
14
21
|
dataplaneUrls: string[]
|
|
@@ -81,6 +88,19 @@ export class ServerConfig {
|
|
|
81
88
|
const alternateAudienceDids = envList(process.env.BSKY_ALT_AUDIENCE_DIDS)
|
|
82
89
|
const entrywayJwtPublicKeyHex =
|
|
83
90
|
process.env.BSKY_ENTRYWAY_JWT_PUBLIC_KEY_HEX || undefined
|
|
91
|
+
let liveNowConfig: LiveNowConfig | undefined
|
|
92
|
+
if (process.env.BSKY_LIVE_NOW_CONFIG) {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(process.env.BSKY_LIVE_NOW_CONFIG)
|
|
95
|
+
if (isLiveNowConfig(parsed)) {
|
|
96
|
+
liveNowConfig = parsed
|
|
97
|
+
} else {
|
|
98
|
+
throw new Error('Live Now config failed format validation')
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
log.error({ err }, 'Invalid BSKY_LIVE_NOW_CONFIG')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
84
104
|
const handleResolveNameservers = envList(
|
|
85
105
|
process.env.BSKY_HANDLE_RESOLVE_NAMESERVERS,
|
|
86
106
|
)
|
|
@@ -200,6 +220,7 @@ export class ServerConfig {
|
|
|
200
220
|
serverDid,
|
|
201
221
|
alternateAudienceDids,
|
|
202
222
|
entrywayJwtPublicKeyHex,
|
|
223
|
+
liveNowConfig,
|
|
203
224
|
etcdHosts,
|
|
204
225
|
dataplaneUrls,
|
|
205
226
|
dataplaneUrlsEtcdKeyPrefix,
|
|
@@ -285,6 +306,10 @@ export class ServerConfig {
|
|
|
285
306
|
return this.cfg.entrywayJwtPublicKeyHex
|
|
286
307
|
}
|
|
287
308
|
|
|
309
|
+
get liveNowConfig() {
|
|
310
|
+
return this.cfg.liveNowConfig
|
|
311
|
+
}
|
|
312
|
+
|
|
288
313
|
get etcdHosts() {
|
|
289
314
|
return this.cfg.etcdHosts
|
|
290
315
|
}
|
|
@@ -482,3 +507,17 @@ function envList(str: string | undefined): string[] {
|
|
|
482
507
|
if (str === undefined || str.length === 0) return []
|
|
483
508
|
return str.split(',')
|
|
484
509
|
}
|
|
510
|
+
|
|
511
|
+
function isLiveNowConfig(data: any): data is LiveNowConfig {
|
|
512
|
+
return (
|
|
513
|
+
Array.isArray(data) &&
|
|
514
|
+
data.every(
|
|
515
|
+
(item) =>
|
|
516
|
+
typeof item === 'object' &&
|
|
517
|
+
item !== null &&
|
|
518
|
+
typeof item.did === 'string' &&
|
|
519
|
+
Array.isArray(item.domains) &&
|
|
520
|
+
item.domains.every((domain: any) => typeof domain === 'string'),
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
}
|
|
@@ -31,6 +31,7 @@ import * as Postgate from './plugins/post-gate'
|
|
|
31
31
|
import * as Profile from './plugins/profile'
|
|
32
32
|
import * as Repost from './plugins/repost'
|
|
33
33
|
import * as StarterPack from './plugins/starter-pack'
|
|
34
|
+
import * as Status from './plugins/status'
|
|
34
35
|
import * as Threadgate from './plugins/thread-gate'
|
|
35
36
|
import * as Verification from './plugins/verification'
|
|
36
37
|
import { RecordProcessor } from './processor'
|
|
@@ -53,6 +54,7 @@ export class IndexingService {
|
|
|
53
54
|
labeler: Labeler.PluginType
|
|
54
55
|
chatDeclaration: ChatDeclaration.PluginType
|
|
55
56
|
verification: Verification.PluginType
|
|
57
|
+
status: Status.PluginType
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
constructor(
|
|
@@ -77,6 +79,7 @@ export class IndexingService {
|
|
|
77
79
|
labeler: Labeler.makePlugin(this.db, this.background),
|
|
78
80
|
chatDeclaration: ChatDeclaration.makePlugin(this.db, this.background),
|
|
79
81
|
verification: Verification.makePlugin(this.db, this.background),
|
|
82
|
+
status: Status.makePlugin(this.db, this.background),
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { AtUri } from '@atproto/syntax'
|
|
3
|
+
import * as lex from '../../../../lexicon/lexicons'
|
|
4
|
+
import { BackgroundQueue } from '../../background'
|
|
5
|
+
import { Database } from '../../db'
|
|
6
|
+
import { DatabaseSchema } from '../../db/database-schema'
|
|
7
|
+
import { RecordProcessor } from '../processor'
|
|
8
|
+
|
|
9
|
+
// @NOTE this indexer is a placeholder to ensure it gets indexed in the generic records table
|
|
10
|
+
|
|
11
|
+
const lexId = lex.ids.AppBskyActorStatus
|
|
12
|
+
|
|
13
|
+
const insertFn = async (
|
|
14
|
+
_db: DatabaseSchema,
|
|
15
|
+
uri: AtUri,
|
|
16
|
+
_cid: CID,
|
|
17
|
+
_obj: unknown,
|
|
18
|
+
_timestamp: string,
|
|
19
|
+
): Promise<unknown | null> => {
|
|
20
|
+
if (uri.rkey !== 'self') return null
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const findDuplicate = async (): Promise<AtUri | null> => {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const notifsForInsert = () => {
|
|
29
|
+
return []
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const deleteFn = async (
|
|
33
|
+
_db: DatabaseSchema,
|
|
34
|
+
uri: AtUri,
|
|
35
|
+
): Promise<unknown | null> => {
|
|
36
|
+
if (uri.rkey !== 'self') return null
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const notifsForDelete = () => {
|
|
41
|
+
return { notifs: [], toDelete: [] }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type PluginType = RecordProcessor<unknown, unknown>
|
|
45
|
+
|
|
46
|
+
export const makePlugin = (
|
|
47
|
+
db: Database,
|
|
48
|
+
background: BackgroundQueue,
|
|
49
|
+
): PluginType => {
|
|
50
|
+
const processor = new RecordProcessor(db, background, {
|
|
51
|
+
lexId,
|
|
52
|
+
insertFn,
|
|
53
|
+
findDuplicate,
|
|
54
|
+
deleteFn,
|
|
55
|
+
notifsForInsert,
|
|
56
|
+
notifsForDelete,
|
|
57
|
+
})
|
|
58
|
+
return processor
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default makePlugin
|
|
@@ -14,7 +14,6 @@ import lists from './lists'
|
|
|
14
14
|
import moderation from './moderation'
|
|
15
15
|
import mutes from './mutes'
|
|
16
16
|
import notifs from './notifs'
|
|
17
|
-
import posts from './posts'
|
|
18
17
|
import profile from './profile'
|
|
19
18
|
import quotes from './quotes'
|
|
20
19
|
import records from './records'
|
|
@@ -41,7 +40,6 @@ export default (db: Database, idResolver: IdResolver) =>
|
|
|
41
40
|
...moderation(db),
|
|
42
41
|
...mutes(db),
|
|
43
42
|
...notifs(db),
|
|
44
|
-
...posts(db),
|
|
45
43
|
...profile(db),
|
|
46
44
|
...quotes(db),
|
|
47
45
|
...records(db),
|
|
@@ -25,37 +25,46 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
25
25
|
const profileUris = dids.map(
|
|
26
26
|
(did) => `at://${did}/app.bsky.actor.profile/self`,
|
|
27
27
|
)
|
|
28
|
+
const statusUris = dids.map(
|
|
29
|
+
(did) => `at://${did}/app.bsky.actor.status/self`,
|
|
30
|
+
)
|
|
28
31
|
const chatDeclarationUris = dids.map(
|
|
29
32
|
(did) => `at://${did}/chat.bsky.actor.declaration/self`,
|
|
30
33
|
)
|
|
31
34
|
const { ref } = db.db.dynamic
|
|
32
|
-
const [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
const [
|
|
36
|
+
handlesRes,
|
|
37
|
+
verificationsReceived,
|
|
38
|
+
profiles,
|
|
39
|
+
statuses,
|
|
40
|
+
chatDeclarations,
|
|
41
|
+
] = await Promise.all([
|
|
42
|
+
db.db
|
|
43
|
+
.selectFrom('actor')
|
|
44
|
+
.leftJoin('actor_state', 'actor_state.did', 'actor.did')
|
|
45
|
+
.where('actor.did', 'in', dids)
|
|
46
|
+
.selectAll('actor')
|
|
47
|
+
.select('actor_state.priorityNotifs')
|
|
48
|
+
.select([
|
|
49
|
+
db.db
|
|
50
|
+
.selectFrom('labeler')
|
|
51
|
+
.whereRef('creator', '=', ref('actor.did'))
|
|
52
|
+
.select(sql<true>`${true}`.as('val'))
|
|
53
|
+
.as('isLabeler'),
|
|
54
|
+
])
|
|
55
|
+
.execute(),
|
|
56
|
+
db.db
|
|
57
|
+
.selectFrom('verification')
|
|
58
|
+
.selectAll('verification')
|
|
59
|
+
.innerJoin('actor', 'actor.did', 'verification.creator')
|
|
60
|
+
.where('verification.subject', 'in', dids)
|
|
61
|
+
.where('actor.trustedVerifier', '=', true)
|
|
62
|
+
.orderBy('sortedAt', 'asc')
|
|
63
|
+
.execute(),
|
|
64
|
+
getRecords(db)({ uris: profileUris }),
|
|
65
|
+
getRecords(db)({ uris: statusUris }),
|
|
66
|
+
getRecords(db)({ uris: chatDeclarationUris }),
|
|
67
|
+
])
|
|
59
68
|
|
|
60
69
|
const verificationsBySubjectDid = verificationsReceived.reduce(
|
|
61
70
|
(acc, cur) => {
|
|
@@ -70,6 +79,9 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
70
79
|
const byDid = keyBy(handlesRes, 'did')
|
|
71
80
|
const actors = dids.map((did, i) => {
|
|
72
81
|
const row = byDid.get(did)
|
|
82
|
+
|
|
83
|
+
const status = statuses.records[i]
|
|
84
|
+
|
|
73
85
|
const chatDeclaration = parseRecordBytes(
|
|
74
86
|
chatDeclarations.records[i].record,
|
|
75
87
|
)
|
|
@@ -102,11 +114,15 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
102
114
|
priorityNotifications: row?.priorityNotifs ?? false,
|
|
103
115
|
trustedVerifier: row?.trustedVerifier ?? false,
|
|
104
116
|
verifiedBy,
|
|
117
|
+
statusRecord: status,
|
|
118
|
+
tags: [],
|
|
119
|
+
profileTags: [],
|
|
105
120
|
}
|
|
106
121
|
})
|
|
107
122
|
return { actors }
|
|
108
123
|
},
|
|
109
124
|
|
|
125
|
+
// @TODO handle req.lookupUnidirectional w/ networked handle resolution
|
|
110
126
|
async getDidsByHandles(req) {
|
|
111
127
|
const { handles } = req
|
|
112
128
|
if (handles.length === 0) {
|
|
@@ -25,6 +25,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
25
25
|
getActorChatDeclarationRecords: getRecords(db, ids.ChatBskyActorDeclaration),
|
|
26
26
|
getStarterPackRecords: getRecords(db, ids.AppBskyGraphStarterpack),
|
|
27
27
|
getVerificationRecords: getRecords(db, ids.AppBskyGraphVerification),
|
|
28
|
+
getStatusRecords: getRecords(db, ids.AppBskyActorStatus),
|
|
28
29
|
})
|
|
29
30
|
|
|
30
31
|
export const getRecords =
|
package/src/error.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { XRPCError } from '@atproto/xrpc-server'
|
|
|
3
3
|
import { httpLogger as log } from './logger'
|
|
4
4
|
|
|
5
5
|
export const handler: ErrorRequestHandler = (err, _req, res, next) => {
|
|
6
|
-
log.error(err, 'unexpected internal server error')
|
|
6
|
+
log.error({ err }, 'unexpected internal server error')
|
|
7
7
|
if (res.headersSent) {
|
|
8
8
|
return next(err)
|
|
9
9
|
}
|
package/src/hydration/actor.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mapDefined } from '@atproto/common'
|
|
2
2
|
import { DataPlaneClient } from '../data-plane/client'
|
|
3
3
|
import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile'
|
|
4
|
+
import { Record as StatusRecord } from '../lexicon/types/app/bsky/actor/status'
|
|
4
5
|
import { Record as ChatDeclarationRecord } from '../lexicon/types/chat/bsky/actor/declaration'
|
|
5
6
|
import { VerificationMeta } from '../proto/bsky_pb'
|
|
6
7
|
import {
|
|
@@ -27,6 +28,7 @@ export type Actor = {
|
|
|
27
28
|
priorityNotifications: boolean
|
|
28
29
|
trustedVerifier?: boolean
|
|
29
30
|
verifications: VerificationHydrationState[]
|
|
31
|
+
status?: RecordInfo<StatusRecord>
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export type VerificationHydrationState = {
|
|
@@ -45,6 +47,9 @@ export type ChatDeclaration = RecordInfo<ChatDeclarationRecord>
|
|
|
45
47
|
|
|
46
48
|
export type ChatDeclarations = HydrationMap<ChatDeclaration>
|
|
47
49
|
|
|
50
|
+
export type Status = RecordInfo<StatusRecord>
|
|
51
|
+
export type Statuses = HydrationMap<Status>
|
|
52
|
+
|
|
48
53
|
export type ProfileViewerState = {
|
|
49
54
|
muted?: boolean
|
|
50
55
|
mutedByList?: string
|
|
@@ -88,10 +93,16 @@ export class ActorHydrator {
|
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
|
|
91
|
-
async getDids(
|
|
96
|
+
async getDids(
|
|
97
|
+
handleOrDids: string[],
|
|
98
|
+
opts?: { lookupUnidirectional?: boolean },
|
|
99
|
+
): Promise<(string | undefined)[]> {
|
|
92
100
|
const handles = handleOrDids.filter((actor) => !actor.startsWith('did:'))
|
|
93
101
|
const res = handles.length
|
|
94
|
-
? await this.dataplane.getDidsByHandles({
|
|
102
|
+
? await this.dataplane.getDidsByHandles({
|
|
103
|
+
handles,
|
|
104
|
+
lookupUnidirectional: opts?.lookupUnidirectional,
|
|
105
|
+
})
|
|
95
106
|
: { dids: [] }
|
|
96
107
|
const didByHandle = handles.reduce(
|
|
97
108
|
(acc, cur, i) => {
|
|
@@ -141,6 +152,10 @@ export class ActorHydrator {
|
|
|
141
152
|
? parseRecord<ProfileRecord>(actor.profile, includeTakedowns)
|
|
142
153
|
: undefined
|
|
143
154
|
|
|
155
|
+
const status = actor.statusRecord
|
|
156
|
+
? parseRecord<StatusRecord>(actor.statusRecord, includeTakedowns)
|
|
157
|
+
: undefined
|
|
158
|
+
|
|
144
159
|
const verifications = mapDefined(
|
|
145
160
|
Object.entries(actor.verifiedBy),
|
|
146
161
|
([actorDid, verificationMeta]) => {
|
|
@@ -178,6 +193,7 @@ export class ActorHydrator {
|
|
|
178
193
|
priorityNotifications: actor.priorityNotifications,
|
|
179
194
|
trustedVerifier: actor.trustedVerifier,
|
|
180
195
|
verifications,
|
|
196
|
+
status: status,
|
|
181
197
|
})
|
|
182
198
|
}, new HydrationMap<Actor>())
|
|
183
199
|
}
|
|
@@ -197,6 +213,15 @@ export class ActorHydrator {
|
|
|
197
213
|
}, new HydrationMap<ChatDeclaration>())
|
|
198
214
|
}
|
|
199
215
|
|
|
216
|
+
async getStatus(uris: string[], includeTakedowns = false): Promise<Statuses> {
|
|
217
|
+
if (!uris.length) return new HydrationMap<Status>()
|
|
218
|
+
const res = await this.dataplane.getStatusRecords({ uris })
|
|
219
|
+
return uris.reduce((acc, uri, i) => {
|
|
220
|
+
const record = parseRecord<StatusRecord>(res.records[i], includeTakedowns)
|
|
221
|
+
return acc.set(uri, record ?? null)
|
|
222
|
+
}, new HydrationMap<Status>())
|
|
223
|
+
}
|
|
224
|
+
|
|
200
225
|
// "naive" because this method does not verify the existence of the list itself
|
|
201
226
|
// a later check in the main hydrator will remove list uris that have been deleted or
|
|
202
227
|
// repurposed to "curate lists"
|
package/src/hydration/feed.ts
CHANGED
|
@@ -206,9 +206,15 @@ export class FeedHydrator {
|
|
|
206
206
|
}, new HydrationMap<ThreadContext>())
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
async getPostAggregates(
|
|
209
|
+
async getPostAggregates(
|
|
210
|
+
refs: ItemRef[],
|
|
211
|
+
viewer: string | null,
|
|
212
|
+
): Promise<PostAggs> {
|
|
210
213
|
if (!refs.length) return new HydrationMap<PostAgg>()
|
|
211
|
-
const counts = await this.dataplane.getInteractionCounts({
|
|
214
|
+
const counts = await this.dataplane.getInteractionCounts({
|
|
215
|
+
refs,
|
|
216
|
+
skipCacheForDids: viewer ? [viewer] : undefined,
|
|
217
|
+
})
|
|
212
218
|
return refs.reduce((acc, { uri }, i) => {
|
|
213
219
|
return acc.set(uri, {
|
|
214
220
|
likes: counts.likes[i] ?? 0,
|
|
@@ -250,9 +256,15 @@ export class FeedHydrator {
|
|
|
250
256
|
}, new HydrationMap<FeedGenViewerState>())
|
|
251
257
|
}
|
|
252
258
|
|
|
253
|
-
async getFeedGenAggregates(
|
|
259
|
+
async getFeedGenAggregates(
|
|
260
|
+
refs: ItemRef[],
|
|
261
|
+
viewer: string | null,
|
|
262
|
+
): Promise<FeedGenAggs> {
|
|
254
263
|
if (!refs.length) return new HydrationMap<FeedGenAgg>()
|
|
255
|
-
const counts = await this.dataplane.getInteractionCounts({
|
|
264
|
+
const counts = await this.dataplane.getInteractionCounts({
|
|
265
|
+
refs,
|
|
266
|
+
skipCacheForDids: viewer ? [viewer] : undefined,
|
|
267
|
+
})
|
|
256
268
|
return refs.reduce((acc, { uri }, i) => {
|
|
257
269
|
return acc.set(uri, {
|
|
258
270
|
likes: counts.likes[i] ?? 0,
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
Threadgates,
|
|
38
38
|
} from './feed'
|
|
39
39
|
import {
|
|
40
|
+
BlockEntry,
|
|
40
41
|
Follows,
|
|
41
42
|
GraphHydrator,
|
|
42
43
|
ListAggs,
|
|
@@ -265,7 +266,7 @@ export class Hydrator {
|
|
|
265
266
|
const [state, profileAggs, bidirectionalBlocks] = await Promise.all([
|
|
266
267
|
this.hydrateProfiles(allDids, ctx),
|
|
267
268
|
this.actor.getProfileAggregates(dids),
|
|
268
|
-
this.hydrateBidirectionalBlocks(subjectsToKnownFollowersMap),
|
|
269
|
+
this.hydrateBidirectionalBlocks(subjectsToKnownFollowersMap, ctx),
|
|
269
270
|
])
|
|
270
271
|
const starterPackUriSet = new Set<string>()
|
|
271
272
|
state.actors?.forEach((actor) => {
|
|
@@ -486,12 +487,12 @@ export class Hydrator {
|
|
|
486
487
|
starterPackState,
|
|
487
488
|
postgates,
|
|
488
489
|
] = await Promise.all([
|
|
489
|
-
this.feed.getPostAggregates(allRefs),
|
|
490
|
+
this.feed.getPostAggregates(allRefs, ctx.viewer),
|
|
490
491
|
ctx.viewer
|
|
491
492
|
? this.feed.getPostViewerStates(threadRefs, ctx.viewer)
|
|
492
493
|
: undefined,
|
|
493
494
|
this.label.getLabelsForSubjects(allPostUris, ctx.labelers),
|
|
494
|
-
this.hydratePostBlocks(posts),
|
|
495
|
+
this.hydratePostBlocks(posts, ctx),
|
|
495
496
|
this.hydrateProfiles(allPostUris.map(didFromUri), ctx),
|
|
496
497
|
this.hydrateLists([...nestedListUris, ...threadgateListUris], ctx),
|
|
497
498
|
this.hydrateFeedGens(nestedFeedGenUris, ctx),
|
|
@@ -522,7 +523,10 @@ export class Hydrator {
|
|
|
522
523
|
)
|
|
523
524
|
}
|
|
524
525
|
|
|
525
|
-
private async hydratePostBlocks(
|
|
526
|
+
private async hydratePostBlocks(
|
|
527
|
+
posts: Posts,
|
|
528
|
+
ctx: HydrateCtx,
|
|
529
|
+
): Promise<PostBlocks> {
|
|
526
530
|
const postBlocks = new HydrationMap<PostBlock>()
|
|
527
531
|
const postBlocksPairs = new Map<string, PostBlockPairs>()
|
|
528
532
|
const relationships: RelationshipPair[] = []
|
|
@@ -557,6 +561,7 @@ export class Hydrator {
|
|
|
557
561
|
// replace embed/parent/root pairs with block state
|
|
558
562
|
const blocks = await this.hydrateBidirectionalBlocks(
|
|
559
563
|
pairsToMap(relationships),
|
|
564
|
+
ctx,
|
|
560
565
|
)
|
|
561
566
|
for (const [uri, { embed, parent, root }] of postBlocksPairs) {
|
|
562
567
|
postBlocks.set(uri, {
|
|
@@ -683,7 +688,10 @@ export class Hydrator {
|
|
|
683
688
|
const [feedgens, feedgenAggs, feedgenViewers, profileState, labels] =
|
|
684
689
|
await Promise.all([
|
|
685
690
|
this.feed.getFeedGens(uris, ctx.includeTakedowns),
|
|
686
|
-
this.feed.getFeedGenAggregates(
|
|
691
|
+
this.feed.getFeedGenAggregates(
|
|
692
|
+
uris.map((uri) => ({ uri })),
|
|
693
|
+
ctx.viewer,
|
|
694
|
+
),
|
|
687
695
|
ctx.viewer
|
|
688
696
|
? this.feed.getFeedGenViewerStates(uris, ctx.viewer)
|
|
689
697
|
: undefined,
|
|
@@ -782,6 +790,7 @@ export class Hydrator {
|
|
|
782
790
|
)
|
|
783
791
|
const blocks = await this.hydrateBidirectionalBlocks(
|
|
784
792
|
pairsToMap(listCreatorMemberPairs),
|
|
793
|
+
ctx,
|
|
785
794
|
)
|
|
786
795
|
// sample top list items per starter pack based on their follows
|
|
787
796
|
const listMemberAggs = await this.actor.getProfileAggregates(listMemberDids)
|
|
@@ -839,7 +848,7 @@ export class Hydrator {
|
|
|
839
848
|
pairs.push([authorDid, didFromUri(uri)])
|
|
840
849
|
}
|
|
841
850
|
}
|
|
842
|
-
const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs))
|
|
851
|
+
const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs), ctx)
|
|
843
852
|
const likeBlocks = new HydrationMap<LikeBlock>()
|
|
844
853
|
for (const [uri, like] of likes) {
|
|
845
854
|
if (like) {
|
|
@@ -925,7 +934,10 @@ export class Hydrator {
|
|
|
925
934
|
}
|
|
926
935
|
|
|
927
936
|
// provides partial hydration state within getFollows / getFollowers, mainly for applying rules
|
|
928
|
-
async hydrateFollows(
|
|
937
|
+
async hydrateFollows(
|
|
938
|
+
uris: string[],
|
|
939
|
+
ctx: HydrateCtx,
|
|
940
|
+
): Promise<HydrationState> {
|
|
929
941
|
const follows = await this.graph.getFollows(uris)
|
|
930
942
|
const pairs: RelationshipPair[] = []
|
|
931
943
|
for (const [uri, follow] of follows) {
|
|
@@ -933,7 +945,7 @@ export class Hydrator {
|
|
|
933
945
|
pairs.push([didFromUri(uri), follow.record.subject])
|
|
934
946
|
}
|
|
935
947
|
}
|
|
936
|
-
const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs))
|
|
948
|
+
const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs), ctx)
|
|
937
949
|
const followBlocks = new HydrationMap<FollowBlock>()
|
|
938
950
|
for (const [uri, follow] of follows) {
|
|
939
951
|
if (follow) {
|
|
@@ -950,6 +962,7 @@ export class Hydrator {
|
|
|
950
962
|
|
|
951
963
|
async hydrateBidirectionalBlocks(
|
|
952
964
|
didMap: Map<string, string[]>, // DID -> DID[]
|
|
965
|
+
ctx: HydrateCtx,
|
|
953
966
|
): Promise<BidirectionalBlocks> {
|
|
954
967
|
const pairs: RelationshipPair[] = []
|
|
955
968
|
for (const [source, targets] of didMap) {
|
|
@@ -958,32 +971,56 @@ export class Hydrator {
|
|
|
958
971
|
}
|
|
959
972
|
}
|
|
960
973
|
|
|
961
|
-
const result = new HydrationMap<HydrationMap<boolean>>()
|
|
962
974
|
const blocks = await this.graph.getBidirectionalBlocks(pairs)
|
|
963
|
-
|
|
964
|
-
// lookup list authors to apply takedown status to blocklists
|
|
965
|
-
const listAuthorDids = new Set<string>()
|
|
975
|
+
const listUrisSet = new Set<string>()
|
|
966
976
|
for (const [source, targets] of didMap) {
|
|
967
977
|
for (const target of targets) {
|
|
968
978
|
const block = blocks.get(source, target)
|
|
969
979
|
if (block?.blockListUri) {
|
|
970
|
-
|
|
980
|
+
listUrisSet.add(block.blockListUri)
|
|
971
981
|
}
|
|
972
982
|
}
|
|
973
983
|
}
|
|
984
|
+
const listUris = [...listUrisSet]
|
|
974
985
|
|
|
975
|
-
|
|
986
|
+
// if a list no longer exists or is not a mod list, then remove from block entry
|
|
987
|
+
const listState = await this.hydrateListsBasic(listUris, ctx)
|
|
988
|
+
for (const [source, targets] of didMap) {
|
|
989
|
+
for (const target of targets) {
|
|
990
|
+
const block = blocks.get(source, target)
|
|
991
|
+
if (!isModList(block?.blockListUri, listState)) {
|
|
992
|
+
delete block?.blockListUri
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
976
996
|
|
|
997
|
+
const result: BidirectionalBlocks = new HydrationMap<
|
|
998
|
+
HydrationMap<boolean>
|
|
999
|
+
>()
|
|
977
1000
|
for (const [source, targets] of didMap) {
|
|
978
1001
|
const didBlocks = new HydrationMap<boolean>()
|
|
979
1002
|
for (const target of targets) {
|
|
980
1003
|
const block = blocks.get(source, target)
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1004
|
+
|
|
1005
|
+
// If a list no longer exists or is not a mod list, then remove from block entry.
|
|
1006
|
+
// isModList confirms the list exists in listState, which ensures it wasn't taken down.
|
|
1007
|
+
if (!isModList(block?.blockListUri, listState)) {
|
|
1008
|
+
delete block?.blockListUri
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const blockEntry: BlockEntry = {
|
|
1012
|
+
blockUri: block?.blockUri,
|
|
1013
|
+
blockListUri:
|
|
1014
|
+
block?.blockListUri &&
|
|
1015
|
+
listState.actors?.get(uriToDid(block.blockListUri))
|
|
1016
|
+
? block.blockListUri
|
|
1017
|
+
: undefined,
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
didBlocks.set(
|
|
1021
|
+
target,
|
|
1022
|
+
!!blockEntry.blockUri || !!blockEntry.blockListUri,
|
|
985
1023
|
)
|
|
986
|
-
didBlocks.set(target, isBlocked)
|
|
987
1024
|
}
|
|
988
1025
|
result.set(source, didBlocks)
|
|
989
1026
|
}
|
|
@@ -1002,7 +1039,7 @@ export class Hydrator {
|
|
|
1002
1039
|
const [labelers, labelerAggs, labelerViewers, profileState] =
|
|
1003
1040
|
await Promise.all([
|
|
1004
1041
|
this.label.getLabelers(dids, ctx.includeTakedowns),
|
|
1005
|
-
this.label.getLabelerAggregates(dids),
|
|
1042
|
+
this.label.getLabelerAggregates(dids, ctx.viewer),
|
|
1006
1043
|
ctx.viewer
|
|
1007
1044
|
? this.label.getLabelerViewerStates(dids, ctx.viewer)
|
|
1008
1045
|
: undefined,
|
|
@@ -1093,6 +1130,12 @@ export class Hydrator {
|
|
|
1093
1130
|
uri,
|
|
1094
1131
|
) ?? undefined
|
|
1095
1132
|
)
|
|
1133
|
+
} else if (collection === ids.AppBskyActorStatus) {
|
|
1134
|
+
if (parsed.rkey !== 'self') return
|
|
1135
|
+
return (
|
|
1136
|
+
(await this.actor.getStatus([uri], includeTakedowns)).get(uri) ??
|
|
1137
|
+
undefined
|
|
1138
|
+
)
|
|
1096
1139
|
} else if (collection === ids.AppBskyActorProfile) {
|
|
1097
1140
|
const did = parsed.hostname
|
|
1098
1141
|
const actor = (
|
|
@@ -1251,7 +1294,7 @@ const getListUrisFromThreadgates = (gates: Threadgates) => {
|
|
|
1251
1294
|
}
|
|
1252
1295
|
|
|
1253
1296
|
const isBlocked = (blocks: BidirectionalBlocks, [a, b]: RelationshipPair) => {
|
|
1254
|
-
return blocks.get(a)?.get(b) ??
|
|
1297
|
+
return blocks.get(a)?.get(b) ?? false
|
|
1255
1298
|
}
|
|
1256
1299
|
|
|
1257
1300
|
const pairsToMap = (pairs: RelationshipPair[]): Map<string, string[]> => {
|
package/src/hydration/label.ts
CHANGED
|
@@ -156,9 +156,15 @@ export class LabelHydrator {
|
|
|
156
156
|
}, new HydrationMap<LabelerViewerState>())
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
async getLabelerAggregates(
|
|
159
|
+
async getLabelerAggregates(
|
|
160
|
+
dids: string[],
|
|
161
|
+
viewer: string | null,
|
|
162
|
+
): Promise<LabelerAggs> {
|
|
160
163
|
const refs = dids.map((did) => ({ uri: labelerDidToUri(did) }))
|
|
161
|
-
const counts = await this.dataplane.getInteractionCounts({
|
|
164
|
+
const counts = await this.dataplane.getInteractionCounts({
|
|
165
|
+
refs,
|
|
166
|
+
skipCacheForDids: viewer ? [viewer] : undefined,
|
|
167
|
+
})
|
|
162
168
|
return dids.reduce((acc, did, i) => {
|
|
163
169
|
return acc.set(did, {
|
|
164
170
|
likes: counts.likes[i] ?? 0,
|
package/src/image/server.ts
CHANGED
|
@@ -102,7 +102,7 @@ export function createMiddleware(
|
|
|
102
102
|
// Cache in the background
|
|
103
103
|
cache
|
|
104
104
|
.put(cacheKey, cloneStream(processor))
|
|
105
|
-
.catch((err) => log.error(err, 'failed to cache image'))
|
|
105
|
+
.catch((err) => log.error({ err }, 'failed to cache image'))
|
|
106
106
|
|
|
107
107
|
res.statusCode = 200
|
|
108
108
|
res.setHeader('cache-control', `public, max-age=31536000`) // 1 year
|