@atproto/bsky 0.0.60 → 0.0.62
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 +14 -0
- package/dist/api/app/bsky/graph/getKnownFollowers.d.ts +4 -0
- package/dist/api/app/bsky/graph/getKnownFollowers.d.ts.map +1 -0
- package/dist/api/app/bsky/graph/getKnownFollowers.js +71 -0
- package/dist/api/app/bsky/graph/getKnownFollowers.js.map +1 -0
- package/dist/api/app/bsky/graph/muteThread.d.ts +4 -0
- package/dist/api/app/bsky/graph/muteThread.d.ts.map +1 -0
- package/dist/api/app/bsky/graph/muteThread.js +19 -0
- package/dist/api/app/bsky/graph/muteThread.js.map +1 -0
- package/dist/api/app/bsky/graph/unmuteThread.d.ts +4 -0
- package/dist/api/app/bsky/graph/unmuteThread.d.ts.map +1 -0
- package/dist/api/app/bsky/graph/unmuteThread.js +19 -0
- package/dist/api/app/bsky/graph/unmuteThread.js.map +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +6 -0
- package/dist/api/index.js.map +1 -1
- package/dist/data-plane/bsync/index.d.ts.map +1 -1
- package/dist/data-plane/bsync/index.js +41 -15
- package/dist/data-plane/bsync/index.js.map +1 -1
- package/dist/data-plane/server/db/database-schema.d.ts +2 -1
- package/dist/data-plane/server/db/database-schema.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/20240606T171229898Z-thread-mutes.d.ts +4 -0
- package/dist/data-plane/server/db/migrations/20240606T171229898Z-thread-mutes.d.ts.map +1 -0
- package/dist/data-plane/server/db/migrations/20240606T171229898Z-thread-mutes.js +18 -0
- package/dist/data-plane/server/db/migrations/20240606T171229898Z-thread-mutes.js.map +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/index.js +2 -1
- package/dist/data-plane/server/db/migrations/index.js.map +1 -1
- package/dist/data-plane/server/db/tables/thread-mute.d.ts +10 -0
- package/dist/data-plane/server/db/tables/thread-mute.d.ts.map +1 -0
- package/dist/data-plane/server/db/tables/thread-mute.js +5 -0
- package/dist/data-plane/server/db/tables/thread-mute.js.map +1 -0
- package/dist/data-plane/server/indexing/processor.d.ts +2 -0
- package/dist/data-plane/server/indexing/processor.d.ts.map +1 -1
- package/dist/data-plane/server/indexing/processor.js +28 -1
- package/dist/data-plane/server/indexing/processor.js.map +1 -1
- package/dist/data-plane/server/routes/follows.d.ts.map +1 -1
- package/dist/data-plane/server/routes/follows.js +34 -2
- package/dist/data-plane/server/routes/follows.js.map +1 -1
- package/dist/data-plane/server/routes/mutes.d.ts.map +1 -1
- package/dist/data-plane/server/routes/mutes.js +16 -0
- package/dist/data-plane/server/routes/mutes.js.map +1 -1
- package/dist/data-plane/server/util.d.ts +6 -6
- package/dist/hydration/actor.d.ts +6 -0
- package/dist/hydration/actor.d.ts.map +1 -1
- package/dist/hydration/actor.js +17 -0
- package/dist/hydration/actor.js.map +1 -1
- package/dist/hydration/feed.d.ts +6 -1
- package/dist/hydration/feed.d.ts.map +1 -1
- package/dist/hydration/feed.js +16 -2
- package/dist/hydration/feed.js.map +1 -1
- package/dist/hydration/hydrator.d.ts +2 -1
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +28 -7
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/lexicon/index.d.ts +6 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +12 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +127 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +127 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +9 -0
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js +11 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/getKnownFollowers.d.ts +40 -0
- package/dist/lexicon/types/app/bsky/graph/getKnownFollowers.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/getKnownFollowers.js +3 -0
- package/dist/lexicon/types/app/bsky/graph/getKnownFollowers.js.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.d.ts +29 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.js +3 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.js.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.d.ts +29 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.js +3 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.js.map +1 -0
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +2 -1
- package/dist/logger.js.map +1 -1
- package/dist/proto/bsky_connect.d.ts +39 -1
- package/dist/proto/bsky_connect.d.ts.map +1 -1
- package/dist/proto/bsky_connect.js +38 -0
- package/dist/proto/bsky_connect.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +161 -4
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +512 -12
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/views/index.d.ts +5 -0
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +20 -0
- package/dist/views/index.js.map +1 -1
- package/package.json +5 -5
- package/proto/bsky.proto +47 -2
- package/src/api/app/bsky/graph/getKnownFollowers.ts +119 -0
- package/src/api/app/bsky/graph/muteThread.ts +18 -0
- package/src/api/app/bsky/graph/unmuteThread.ts +18 -0
- package/src/api/index.ts +6 -0
- package/src/data-plane/bsync/index.ts +39 -15
- package/src/data-plane/server/db/database-schema.ts +2 -0
- package/src/data-plane/server/db/migrations/20240606T171229898Z-thread-mutes.ts +15 -0
- package/src/data-plane/server/db/migrations/index.ts +1 -0
- package/src/data-plane/server/db/tables/thread-mute.ts +9 -0
- package/src/data-plane/server/indexing/processor.ts +29 -1
- package/src/data-plane/server/routes/follows.ts +45 -2
- package/src/data-plane/server/routes/mutes.ts +17 -0
- package/src/hydration/actor.ts +30 -0
- package/src/hydration/feed.ts +23 -3
- package/src/hydration/hydrator.ts +36 -7
- package/src/lexicon/index.ts +36 -0
- package/src/lexicon/lexicons.ts +129 -0
- package/src/lexicon/types/app/bsky/actor/defs.ts +20 -0
- package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
- package/src/lexicon/types/app/bsky/graph/getKnownFollowers.ts +50 -0
- package/src/lexicon/types/app/bsky/graph/muteThread.ts +38 -0
- package/src/lexicon/types/app/bsky/graph/unmuteThread.ts +38 -0
- package/src/logger.ts +2 -0
- package/src/proto/bsky_connect.ts +46 -0
- package/src/proto/bsky_pb.ts +540 -8
- package/src/views/index.ts +25 -0
- package/tests/__snapshots__/feed-generation.test.ts.snap +38 -12
- package/tests/data-plane/__snapshots__/indexing.test.ts.snap +73 -37
- package/tests/data-plane/thread-mutes.test.ts +140 -0
- package/tests/label-hydration.test.ts +0 -2
- package/tests/query-labels.test.ts +0 -3
- package/tests/views/__snapshots__/author-feed.test.ts.snap +62 -17
- package/tests/views/__snapshots__/block-lists.test.ts.snap +7 -2
- package/tests/views/__snapshots__/blocks.test.ts.snap +13 -4
- package/tests/views/__snapshots__/list-feed.test.ts.snap +23 -6
- package/tests/views/__snapshots__/mute-lists.test.ts.snap +10 -3
- package/tests/views/__snapshots__/mutes.test.ts.snap +9 -3
- package/tests/views/__snapshots__/posts.test.ts.snap +16 -5
- package/tests/views/__snapshots__/profile.test.ts.snap +230 -10
- package/tests/views/__snapshots__/thread.test.ts.snap +53 -14
- package/tests/views/__snapshots__/timeline.test.ts.snap +213 -64
- package/tests/views/blocks.test.ts +13 -0
- package/tests/views/profile.test.ts +35 -0
- package/tests/views/threadgating.test.ts +19 -19
|
@@ -247,7 +247,8 @@ export class RecordProcessor<T, S> {
|
|
|
247
247
|
}
|
|
248
248
|
for (const chunk of chunkArray(notifs, 500)) {
|
|
249
249
|
runOnCommit.push(async (db) => {
|
|
250
|
-
await
|
|
250
|
+
const filtered = await this.filterNotifsForThreadMutes(chunk)
|
|
251
|
+
await db.db.insertInto('notification').values(filtered).execute()
|
|
251
252
|
})
|
|
252
253
|
}
|
|
253
254
|
// Need to ensure notif deletion always happens before creation, otherwise delete may clobber in a race.
|
|
@@ -256,6 +257,33 @@ export class RecordProcessor<T, S> {
|
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
259
|
|
|
260
|
+
async filterNotifsForThreadMutes(notifs: Notif[]): Promise<Notif[]> {
|
|
261
|
+
const isBlocked = await Promise.all(
|
|
262
|
+
notifs.map((n) => this.isNotifBlockedByThreadMute(n)),
|
|
263
|
+
)
|
|
264
|
+
return notifs.filter((_, i) => !isBlocked[i])
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async isNotifBlockedByThreadMute(notif: Notif): Promise<boolean> {
|
|
268
|
+
const subject = notif.reasonSubject
|
|
269
|
+
if (!subject) return false
|
|
270
|
+
if (subject.startsWith('did:')) return false
|
|
271
|
+
const post = await this.db
|
|
272
|
+
.selectFrom('post')
|
|
273
|
+
.select(['uri', 'replyRoot'])
|
|
274
|
+
.where('uri', '=', subject)
|
|
275
|
+
.executeTakeFirst()
|
|
276
|
+
if (!post) return false
|
|
277
|
+
const threadRoot = post.replyRoot ?? post.uri
|
|
278
|
+
const threadMute = await this.db
|
|
279
|
+
.selectFrom('thread_mute')
|
|
280
|
+
.selectAll()
|
|
281
|
+
.where('mutedByDid', '=', notif.did)
|
|
282
|
+
.where('rootUri', '=', threadRoot)
|
|
283
|
+
.executeTakeFirst()
|
|
284
|
+
return !!threadMute
|
|
285
|
+
}
|
|
286
|
+
|
|
259
287
|
aggregateOnCommit(indexed: S) {
|
|
260
288
|
const { updateAggregates } = this.params
|
|
261
289
|
if (!updateAggregates) return
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { keyBy } from '@atproto/common'
|
|
2
2
|
import { ServiceImpl } from '@connectrpc/connect'
|
|
3
3
|
import { Service } from '../../../proto/bsky_connect'
|
|
4
|
+
import { FollowsFollowing } from '../../../proto/bsky_pb'
|
|
4
5
|
import { Database } from '../db'
|
|
5
6
|
import { TimeCidKeyset, paginate } from '../db/pagination'
|
|
6
7
|
|
|
@@ -92,7 +93,49 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
92
93
|
cursor: keyset.packFromResult(follows),
|
|
93
94
|
}
|
|
94
95
|
},
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Return known followers of a given actor.
|
|
99
|
+
*
|
|
100
|
+
* Example:
|
|
101
|
+
* - Alice follows Bob
|
|
102
|
+
* - Bob follows Dan
|
|
103
|
+
*
|
|
104
|
+
* If Alice (the viewer) looks at Dan's profile (the subject), she should see that Bob follows Dan
|
|
105
|
+
*/
|
|
106
|
+
async getFollowsFollowing(req) {
|
|
107
|
+
const { actorDid: viewerDid, targetDids: subjectDids } = req
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
* 1. Get all the people the Alice is following
|
|
111
|
+
* 2. Get all the people the Dan is followed by
|
|
112
|
+
* 3. Find the intersection
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
const results: FollowsFollowing[] = []
|
|
116
|
+
|
|
117
|
+
for (const subjectDid of subjectDids) {
|
|
118
|
+
const followsReq = db.db
|
|
119
|
+
.selectFrom('follow')
|
|
120
|
+
.where('follow.creator', '=', viewerDid)
|
|
121
|
+
.where(
|
|
122
|
+
'follow.subjectDid',
|
|
123
|
+
'in',
|
|
124
|
+
db.db
|
|
125
|
+
.selectFrom('follow')
|
|
126
|
+
.where('follow.subjectDid', '=', subjectDid)
|
|
127
|
+
.select(['creator']),
|
|
128
|
+
)
|
|
129
|
+
.select(['subjectDid'])
|
|
130
|
+
const rows = await followsReq.execute()
|
|
131
|
+
results.push(
|
|
132
|
+
new FollowsFollowing({
|
|
133
|
+
targetDid: subjectDid,
|
|
134
|
+
dids: rows.map((r) => r.subjectDid),
|
|
135
|
+
}),
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { results }
|
|
97
140
|
},
|
|
98
141
|
})
|
|
@@ -5,6 +5,7 @@ import { ids } from '../../../lexicon/lexicons'
|
|
|
5
5
|
import { Service } from '../../../proto/bsky_connect'
|
|
6
6
|
import { Database } from '../db'
|
|
7
7
|
import { CreatedAtDidKeyset, TimeCidKeyset, paginate } from '../db/pagination'
|
|
8
|
+
import { keyBy } from '@atproto/common'
|
|
8
9
|
|
|
9
10
|
export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
10
11
|
async getActorMutesActor(req) {
|
|
@@ -166,6 +167,22 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
166
167
|
.where('mutedByDid', '=', actorDid)
|
|
167
168
|
.execute()
|
|
168
169
|
},
|
|
170
|
+
|
|
171
|
+
async getThreadMutesOnSubjects(req) {
|
|
172
|
+
const { actorDid, threadRoots } = req
|
|
173
|
+
if (threadRoots.length === 0) {
|
|
174
|
+
return { muted: [] }
|
|
175
|
+
}
|
|
176
|
+
const res = await db.db
|
|
177
|
+
.selectFrom('thread_mute')
|
|
178
|
+
.selectAll()
|
|
179
|
+
.where('mutedByDid', '=', actorDid)
|
|
180
|
+
.where('rootUri', 'in', threadRoots)
|
|
181
|
+
.execute()
|
|
182
|
+
const byRootUri = keyBy(res, 'rootUri')
|
|
183
|
+
const muted = threadRoots.map((uri) => !!byRootUri[uri])
|
|
184
|
+
return { muted }
|
|
185
|
+
},
|
|
169
186
|
})
|
|
170
187
|
|
|
171
188
|
const isListUri = (uri: string) =>
|
package/src/hydration/actor.ts
CHANGED
|
@@ -38,10 +38,16 @@ export type ProfileViewerState = {
|
|
|
38
38
|
blockingByList?: string
|
|
39
39
|
following?: string
|
|
40
40
|
followedBy?: string
|
|
41
|
+
knownFollowers?: {
|
|
42
|
+
count: number
|
|
43
|
+
followers: string[]
|
|
44
|
+
}
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
export type ProfileViewerStates = HydrationMap<ProfileViewerState>
|
|
44
48
|
|
|
49
|
+
export type KnownFollowers = HydrationMap<ProfileViewerState['knownFollowers']>
|
|
50
|
+
|
|
45
51
|
export type ProfileAgg = {
|
|
46
52
|
followers: number
|
|
47
53
|
follows: number
|
|
@@ -171,6 +177,30 @@ export class ActorHydrator {
|
|
|
171
177
|
}, new HydrationMap<ProfileViewerState>())
|
|
172
178
|
}
|
|
173
179
|
|
|
180
|
+
async getKnownFollowers(
|
|
181
|
+
dids: string[],
|
|
182
|
+
viewer: string | null,
|
|
183
|
+
): Promise<KnownFollowers> {
|
|
184
|
+
if (!viewer) return new HydrationMap<ProfileViewerState['knownFollowers']>()
|
|
185
|
+
const { results: knownFollowersResults } =
|
|
186
|
+
await this.dataplane.getFollowsFollowing({
|
|
187
|
+
actorDid: viewer,
|
|
188
|
+
targetDids: dids,
|
|
189
|
+
})
|
|
190
|
+
return dids.reduce((acc, did, i) => {
|
|
191
|
+
const result = knownFollowersResults[i]?.dids
|
|
192
|
+
return acc.set(
|
|
193
|
+
did,
|
|
194
|
+
result && result.length > 0
|
|
195
|
+
? {
|
|
196
|
+
count: result.length,
|
|
197
|
+
followers: result.slice(0, 5),
|
|
198
|
+
}
|
|
199
|
+
: undefined,
|
|
200
|
+
)
|
|
201
|
+
}, new HydrationMap<ProfileViewerState['knownFollowers']>())
|
|
202
|
+
}
|
|
203
|
+
|
|
174
204
|
async getProfileAggregates(dids: string[]): Promise<ProfileAggs> {
|
|
175
205
|
if (!dids.length) return new HydrationMap<ProfileAgg>()
|
|
176
206
|
const counts = await this.dataplane.getCountsForUsers({ dids })
|
package/src/hydration/feed.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from './util'
|
|
14
14
|
import { AtUri } from '@atproto/syntax'
|
|
15
15
|
import { ids } from '../lexicon/lexicons'
|
|
16
|
+
import { dedupeStrs } from '@atproto/common'
|
|
16
17
|
|
|
17
18
|
export type Post = RecordInfo<PostRecord> & { violatesThreadGate: boolean }
|
|
18
19
|
export type Posts = HydrationMap<Post>
|
|
@@ -20,6 +21,7 @@ export type Posts = HydrationMap<Post>
|
|
|
20
21
|
export type PostViewerState = {
|
|
21
22
|
like?: string
|
|
22
23
|
repost?: string
|
|
24
|
+
threadMuted?: boolean
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export type PostViewerStates = HydrationMap<PostViewerState>
|
|
@@ -57,6 +59,7 @@ export type Threadgate = RecordInfo<ThreadgateRecord>
|
|
|
57
59
|
export type Threadgates = HydrationMap<Threadgate>
|
|
58
60
|
|
|
59
61
|
export type ItemRef = { uri: string; cid?: string }
|
|
62
|
+
export type ThreadRef = ItemRef & { threadRoot: string }
|
|
60
63
|
|
|
61
64
|
// @NOTE the feed item types in the protos for author feeds and timelines
|
|
62
65
|
// technically have additional fields, not supported by the mock dataplane.
|
|
@@ -85,11 +88,12 @@ export class FeedHydrator {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
async getPostViewerStates(
|
|
88
|
-
refs:
|
|
91
|
+
refs: ThreadRef[],
|
|
89
92
|
viewer: string,
|
|
90
93
|
): Promise<PostViewerStates> {
|
|
91
94
|
if (!refs.length) return new HydrationMap<PostViewerState>()
|
|
92
|
-
const
|
|
95
|
+
const threadRoots = refs.map((r) => r.threadRoot)
|
|
96
|
+
const [likes, reposts, threadMutesMap] = await Promise.all([
|
|
93
97
|
this.dataplane.getLikesByActorAndSubjects({
|
|
94
98
|
actorDid: viewer,
|
|
95
99
|
refs,
|
|
@@ -98,15 +102,31 @@ export class FeedHydrator {
|
|
|
98
102
|
actorDid: viewer,
|
|
99
103
|
refs,
|
|
100
104
|
}),
|
|
105
|
+
this.getThreadMutes(threadRoots, viewer),
|
|
101
106
|
])
|
|
102
|
-
return refs.reduce((acc, { uri }, i) => {
|
|
107
|
+
return refs.reduce((acc, { uri, threadRoot }, i) => {
|
|
103
108
|
return acc.set(uri, {
|
|
104
109
|
like: parseString(likes.uris[i]),
|
|
105
110
|
repost: parseString(reposts.uris[i]),
|
|
111
|
+
threadMuted: threadMutesMap.get(threadRoot) ?? false,
|
|
106
112
|
})
|
|
107
113
|
}, new HydrationMap<PostViewerState>())
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
private async getThreadMutes(
|
|
117
|
+
threadRoots: string[],
|
|
118
|
+
viewer: string,
|
|
119
|
+
): Promise<Map<string, boolean>> {
|
|
120
|
+
const deduped = dedupeStrs(threadRoots)
|
|
121
|
+
const threadMutes = await this.dataplane.getThreadMutesOnSubjects({
|
|
122
|
+
actorDid: viewer,
|
|
123
|
+
threadRoots: deduped,
|
|
124
|
+
})
|
|
125
|
+
return deduped.reduce((acc, cur, i) => {
|
|
126
|
+
return acc.set(cur, threadMutes.muted[i] ?? false)
|
|
127
|
+
}, new Map<string, boolean>())
|
|
128
|
+
}
|
|
129
|
+
|
|
110
130
|
async getPostAggregates(refs: ItemRef[]): Promise<PostAggs> {
|
|
111
131
|
if (!refs.length) return new HydrationMap<PostAgg>()
|
|
112
132
|
const counts = await this.dataplane.getInteractionCounts({ refs })
|
|
@@ -7,12 +7,14 @@ import { ids } from '../lexicon/lexicons'
|
|
|
7
7
|
import { isMain as isEmbedRecord } from '../lexicon/types/app/bsky/embed/record'
|
|
8
8
|
import { isMain as isEmbedRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia'
|
|
9
9
|
import { isListRule } from '../lexicon/types/app/bsky/feed/threadgate'
|
|
10
|
+
import { hydrationLogger } from '../logger'
|
|
10
11
|
import {
|
|
11
12
|
ActorHydrator,
|
|
12
13
|
ProfileAggs,
|
|
13
14
|
Actors,
|
|
14
15
|
ProfileViewerStates,
|
|
15
16
|
ProfileViewerState,
|
|
17
|
+
KnownFollowers,
|
|
16
18
|
} from './actor'
|
|
17
19
|
import {
|
|
18
20
|
Follows,
|
|
@@ -93,6 +95,7 @@ export type HydrationState = {
|
|
|
93
95
|
labelers?: Labelers
|
|
94
96
|
labelerViewers?: LabelerViewerStates
|
|
95
97
|
labelerAggs?: LabelerAggs
|
|
98
|
+
knownFollowers?: KnownFollowers
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
export type PostBlock = { embed: boolean; reply: boolean }
|
|
@@ -143,6 +146,7 @@ export class Hydrator {
|
|
|
143
146
|
profileViewers?.forEach((item) => {
|
|
144
147
|
removeNonModListsFromProfileViewer(item, listState)
|
|
145
148
|
})
|
|
149
|
+
|
|
146
150
|
return mergeStates(listState, {
|
|
147
151
|
profileViewers,
|
|
148
152
|
ctx,
|
|
@@ -190,13 +194,29 @@ export class Hydrator {
|
|
|
190
194
|
dids: string[],
|
|
191
195
|
ctx: HydrateCtx,
|
|
192
196
|
): Promise<HydrationState> {
|
|
197
|
+
let knownFollowers: KnownFollowers = new HydrationMap()
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
knownFollowers = await this.actor.getKnownFollowers(dids, ctx.viewer)
|
|
201
|
+
} catch (err) {
|
|
202
|
+
hydrationLogger.error(
|
|
203
|
+
{ err },
|
|
204
|
+
'Failed to get known followers for profiles',
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const knownFollowersDids = Array.from(knownFollowers.values())
|
|
209
|
+
.filter(Boolean)
|
|
210
|
+
.flatMap((f) => f!.followers)
|
|
211
|
+
const allDids = Array.from(new Set(dids.concat(knownFollowersDids)))
|
|
193
212
|
const [state, profileAggs] = await Promise.all([
|
|
194
|
-
this.hydrateProfiles(
|
|
213
|
+
this.hydrateProfiles(allDids, ctx),
|
|
195
214
|
this.actor.getProfileAggregates(dids),
|
|
196
215
|
])
|
|
197
216
|
return {
|
|
198
217
|
...state,
|
|
199
218
|
profileAggs,
|
|
219
|
+
knownFollowers,
|
|
200
220
|
}
|
|
201
221
|
}
|
|
202
222
|
|
|
@@ -314,6 +334,16 @@ export class Hydrator {
|
|
|
314
334
|
const posts =
|
|
315
335
|
mergeManyMaps(postsLayer0, postsLayer1, postsLayer2) ?? postsLayer0
|
|
316
336
|
const allPostUris = [...posts.keys()]
|
|
337
|
+
const allRefs = [
|
|
338
|
+
...refs,
|
|
339
|
+
...postUrisLayer1.map(uriToRef), // supports aggregates on embed #viewRecords
|
|
340
|
+
...postUrisLayer2.map(uriToRef),
|
|
341
|
+
]
|
|
342
|
+
const threadRefs = allRefs.map((ref) => ({
|
|
343
|
+
...ref,
|
|
344
|
+
threadRoot: posts.get(ref.uri)?.record.reply?.root.uri ?? ref.uri,
|
|
345
|
+
}))
|
|
346
|
+
|
|
317
347
|
const [
|
|
318
348
|
postAggs,
|
|
319
349
|
postViewers,
|
|
@@ -324,12 +354,10 @@ export class Hydrator {
|
|
|
324
354
|
feedGenState,
|
|
325
355
|
labelerState,
|
|
326
356
|
] = await Promise.all([
|
|
327
|
-
this.feed.getPostAggregates(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
]),
|
|
332
|
-
ctx.viewer ? this.feed.getPostViewerStates(refs, ctx.viewer) : undefined,
|
|
357
|
+
this.feed.getPostAggregates(allRefs),
|
|
358
|
+
ctx.viewer
|
|
359
|
+
? this.feed.getPostViewerStates(threadRefs, ctx.viewer)
|
|
360
|
+
: undefined,
|
|
333
361
|
this.label.getLabelsForSubjects(allPostUris, ctx.labelers),
|
|
334
362
|
this.hydratePostBlocks(posts),
|
|
335
363
|
this.hydrateProfiles(allPostUris.map(didFromUri), ctx),
|
|
@@ -856,6 +884,7 @@ export const mergeStates = (
|
|
|
856
884
|
labelers: mergeMaps(stateA.labelers, stateB.labelers),
|
|
857
885
|
labelerAggs: mergeMaps(stateA.labelerAggs, stateB.labelerAggs),
|
|
858
886
|
labelerViewers: mergeMaps(stateA.labelerViewers, stateB.labelerViewers),
|
|
887
|
+
knownFollowers: mergeMaps(stateA.knownFollowers, stateB.knownFollowers),
|
|
859
888
|
}
|
|
860
889
|
}
|
|
861
890
|
|
package/src/lexicon/index.ts
CHANGED
|
@@ -110,6 +110,7 @@ import * as AppBskyFeedSendInteractions from './types/app/bsky/feed/sendInteract
|
|
|
110
110
|
import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks'
|
|
111
111
|
import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers'
|
|
112
112
|
import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows'
|
|
113
|
+
import * as AppBskyGraphGetKnownFollowers from './types/app/bsky/graph/getKnownFollowers'
|
|
113
114
|
import * as AppBskyGraphGetList from './types/app/bsky/graph/getList'
|
|
114
115
|
import * as AppBskyGraphGetListBlocks from './types/app/bsky/graph/getListBlocks'
|
|
115
116
|
import * as AppBskyGraphGetListMutes from './types/app/bsky/graph/getListMutes'
|
|
@@ -119,8 +120,10 @@ import * as AppBskyGraphGetRelationships from './types/app/bsky/graph/getRelatio
|
|
|
119
120
|
import * as AppBskyGraphGetSuggestedFollowsByActor from './types/app/bsky/graph/getSuggestedFollowsByActor'
|
|
120
121
|
import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor'
|
|
121
122
|
import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList'
|
|
123
|
+
import * as AppBskyGraphMuteThread from './types/app/bsky/graph/muteThread'
|
|
122
124
|
import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor'
|
|
123
125
|
import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList'
|
|
126
|
+
import * as AppBskyGraphUnmuteThread from './types/app/bsky/graph/unmuteThread'
|
|
124
127
|
import * as AppBskyLabelerGetServices from './types/app/bsky/labeler/getServices'
|
|
125
128
|
import * as AppBskyNotificationGetUnreadCount from './types/app/bsky/notification/getUnreadCount'
|
|
126
129
|
import * as AppBskyNotificationListNotifications from './types/app/bsky/notification/listNotifications'
|
|
@@ -1473,6 +1476,17 @@ export class AppBskyGraphNS {
|
|
|
1473
1476
|
return this._server.xrpc.method(nsid, cfg)
|
|
1474
1477
|
}
|
|
1475
1478
|
|
|
1479
|
+
getKnownFollowers<AV extends AuthVerifier>(
|
|
1480
|
+
cfg: ConfigOf<
|
|
1481
|
+
AV,
|
|
1482
|
+
AppBskyGraphGetKnownFollowers.Handler<ExtractAuth<AV>>,
|
|
1483
|
+
AppBskyGraphGetKnownFollowers.HandlerReqCtx<ExtractAuth<AV>>
|
|
1484
|
+
>,
|
|
1485
|
+
) {
|
|
1486
|
+
const nsid = 'app.bsky.graph.getKnownFollowers' // @ts-ignore
|
|
1487
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1476
1490
|
getList<AV extends AuthVerifier>(
|
|
1477
1491
|
cfg: ConfigOf<
|
|
1478
1492
|
AV,
|
|
@@ -1572,6 +1586,17 @@ export class AppBskyGraphNS {
|
|
|
1572
1586
|
return this._server.xrpc.method(nsid, cfg)
|
|
1573
1587
|
}
|
|
1574
1588
|
|
|
1589
|
+
muteThread<AV extends AuthVerifier>(
|
|
1590
|
+
cfg: ConfigOf<
|
|
1591
|
+
AV,
|
|
1592
|
+
AppBskyGraphMuteThread.Handler<ExtractAuth<AV>>,
|
|
1593
|
+
AppBskyGraphMuteThread.HandlerReqCtx<ExtractAuth<AV>>
|
|
1594
|
+
>,
|
|
1595
|
+
) {
|
|
1596
|
+
const nsid = 'app.bsky.graph.muteThread' // @ts-ignore
|
|
1597
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1575
1600
|
unmuteActor<AV extends AuthVerifier>(
|
|
1576
1601
|
cfg: ConfigOf<
|
|
1577
1602
|
AV,
|
|
@@ -1593,6 +1618,17 @@ export class AppBskyGraphNS {
|
|
|
1593
1618
|
const nsid = 'app.bsky.graph.unmuteActorList' // @ts-ignore
|
|
1594
1619
|
return this._server.xrpc.method(nsid, cfg)
|
|
1595
1620
|
}
|
|
1621
|
+
|
|
1622
|
+
unmuteThread<AV extends AuthVerifier>(
|
|
1623
|
+
cfg: ConfigOf<
|
|
1624
|
+
AV,
|
|
1625
|
+
AppBskyGraphUnmuteThread.Handler<ExtractAuth<AV>>,
|
|
1626
|
+
AppBskyGraphUnmuteThread.HandlerReqCtx<ExtractAuth<AV>>
|
|
1627
|
+
>,
|
|
1628
|
+
) {
|
|
1629
|
+
const nsid = 'app.bsky.graph.unmuteThread' // @ts-ignore
|
|
1630
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
1631
|
+
}
|
|
1596
1632
|
}
|
|
1597
1633
|
|
|
1598
1634
|
export class AppBskyLabelerNS {
|
package/src/lexicon/lexicons.ts
CHANGED
|
@@ -4107,6 +4107,29 @@ export const schemaDict = {
|
|
|
4107
4107
|
type: 'string',
|
|
4108
4108
|
format: 'at-uri',
|
|
4109
4109
|
},
|
|
4110
|
+
knownFollowers: {
|
|
4111
|
+
type: 'ref',
|
|
4112
|
+
ref: 'lex:app.bsky.actor.defs#knownFollowers',
|
|
4113
|
+
},
|
|
4114
|
+
},
|
|
4115
|
+
},
|
|
4116
|
+
knownFollowers: {
|
|
4117
|
+
type: 'object',
|
|
4118
|
+
description: "The subject's followers whom you also follow",
|
|
4119
|
+
required: ['count', 'followers'],
|
|
4120
|
+
properties: {
|
|
4121
|
+
count: {
|
|
4122
|
+
type: 'integer',
|
|
4123
|
+
},
|
|
4124
|
+
followers: {
|
|
4125
|
+
type: 'array',
|
|
4126
|
+
minLength: 0,
|
|
4127
|
+
maxLength: 5,
|
|
4128
|
+
items: {
|
|
4129
|
+
type: 'ref',
|
|
4130
|
+
ref: 'lex:app.bsky.actor.defs#profileViewBasic',
|
|
4131
|
+
},
|
|
4132
|
+
},
|
|
4110
4133
|
},
|
|
4111
4134
|
},
|
|
4112
4135
|
preferences: {
|
|
@@ -5079,6 +5102,9 @@ export const schemaDict = {
|
|
|
5079
5102
|
type: 'string',
|
|
5080
5103
|
format: 'at-uri',
|
|
5081
5104
|
},
|
|
5105
|
+
threadMuted: {
|
|
5106
|
+
type: 'boolean',
|
|
5107
|
+
},
|
|
5082
5108
|
replyDisabled: {
|
|
5083
5109
|
type: 'boolean',
|
|
5084
5110
|
},
|
|
@@ -7101,6 +7127,59 @@ export const schemaDict = {
|
|
|
7101
7127
|
},
|
|
7102
7128
|
},
|
|
7103
7129
|
},
|
|
7130
|
+
AppBskyGraphGetKnownFollowers: {
|
|
7131
|
+
lexicon: 1,
|
|
7132
|
+
id: 'app.bsky.graph.getKnownFollowers',
|
|
7133
|
+
defs: {
|
|
7134
|
+
main: {
|
|
7135
|
+
type: 'query',
|
|
7136
|
+
description:
|
|
7137
|
+
'Enumerates accounts which follow a specified account (actor) and are followed by the viewer.',
|
|
7138
|
+
parameters: {
|
|
7139
|
+
type: 'params',
|
|
7140
|
+
required: ['actor'],
|
|
7141
|
+
properties: {
|
|
7142
|
+
actor: {
|
|
7143
|
+
type: 'string',
|
|
7144
|
+
format: 'at-identifier',
|
|
7145
|
+
},
|
|
7146
|
+
limit: {
|
|
7147
|
+
type: 'integer',
|
|
7148
|
+
minimum: 1,
|
|
7149
|
+
maximum: 100,
|
|
7150
|
+
default: 50,
|
|
7151
|
+
},
|
|
7152
|
+
cursor: {
|
|
7153
|
+
type: 'string',
|
|
7154
|
+
},
|
|
7155
|
+
},
|
|
7156
|
+
},
|
|
7157
|
+
output: {
|
|
7158
|
+
encoding: 'application/json',
|
|
7159
|
+
schema: {
|
|
7160
|
+
type: 'object',
|
|
7161
|
+
required: ['subject', 'followers'],
|
|
7162
|
+
properties: {
|
|
7163
|
+
subject: {
|
|
7164
|
+
type: 'ref',
|
|
7165
|
+
ref: 'lex:app.bsky.actor.defs#profileView',
|
|
7166
|
+
},
|
|
7167
|
+
cursor: {
|
|
7168
|
+
type: 'string',
|
|
7169
|
+
},
|
|
7170
|
+
followers: {
|
|
7171
|
+
type: 'array',
|
|
7172
|
+
items: {
|
|
7173
|
+
type: 'ref',
|
|
7174
|
+
ref: 'lex:app.bsky.actor.defs#profileView',
|
|
7175
|
+
},
|
|
7176
|
+
},
|
|
7177
|
+
},
|
|
7178
|
+
},
|
|
7179
|
+
},
|
|
7180
|
+
},
|
|
7181
|
+
},
|
|
7182
|
+
},
|
|
7104
7183
|
AppBskyGraphGetList: {
|
|
7105
7184
|
lexicon: 1,
|
|
7106
7185
|
id: 'app.bsky.graph.getList',
|
|
@@ -7599,6 +7678,30 @@ export const schemaDict = {
|
|
|
7599
7678
|
},
|
|
7600
7679
|
},
|
|
7601
7680
|
},
|
|
7681
|
+
AppBskyGraphMuteThread: {
|
|
7682
|
+
lexicon: 1,
|
|
7683
|
+
id: 'app.bsky.graph.muteThread',
|
|
7684
|
+
defs: {
|
|
7685
|
+
main: {
|
|
7686
|
+
type: 'procedure',
|
|
7687
|
+
description:
|
|
7688
|
+
'Mutes a thread preventing notifications from the thread and any of its children. Mutes are private in Bluesky. Requires auth.',
|
|
7689
|
+
input: {
|
|
7690
|
+
encoding: 'application/json',
|
|
7691
|
+
schema: {
|
|
7692
|
+
type: 'object',
|
|
7693
|
+
required: ['root'],
|
|
7694
|
+
properties: {
|
|
7695
|
+
root: {
|
|
7696
|
+
type: 'string',
|
|
7697
|
+
format: 'at-uri',
|
|
7698
|
+
},
|
|
7699
|
+
},
|
|
7700
|
+
},
|
|
7701
|
+
},
|
|
7702
|
+
},
|
|
7703
|
+
},
|
|
7704
|
+
},
|
|
7602
7705
|
AppBskyGraphUnmuteActor: {
|
|
7603
7706
|
lexicon: 1,
|
|
7604
7707
|
id: 'app.bsky.graph.unmuteActor',
|
|
@@ -7645,6 +7748,29 @@ export const schemaDict = {
|
|
|
7645
7748
|
},
|
|
7646
7749
|
},
|
|
7647
7750
|
},
|
|
7751
|
+
AppBskyGraphUnmuteThread: {
|
|
7752
|
+
lexicon: 1,
|
|
7753
|
+
id: 'app.bsky.graph.unmuteThread',
|
|
7754
|
+
defs: {
|
|
7755
|
+
main: {
|
|
7756
|
+
type: 'procedure',
|
|
7757
|
+
description: 'Unmutes the specified thread. Requires auth.',
|
|
7758
|
+
input: {
|
|
7759
|
+
encoding: 'application/json',
|
|
7760
|
+
schema: {
|
|
7761
|
+
type: 'object',
|
|
7762
|
+
required: ['root'],
|
|
7763
|
+
properties: {
|
|
7764
|
+
root: {
|
|
7765
|
+
type: 'string',
|
|
7766
|
+
format: 'at-uri',
|
|
7767
|
+
},
|
|
7768
|
+
},
|
|
7769
|
+
},
|
|
7770
|
+
},
|
|
7771
|
+
},
|
|
7772
|
+
},
|
|
7773
|
+
},
|
|
7648
7774
|
AppBskyLabelerDefs: {
|
|
7649
7775
|
lexicon: 1,
|
|
7650
7776
|
id: 'app.bsky.labeler.defs',
|
|
@@ -9523,6 +9649,7 @@ export const ids = {
|
|
|
9523
9649
|
AppBskyGraphGetBlocks: 'app.bsky.graph.getBlocks',
|
|
9524
9650
|
AppBskyGraphGetFollowers: 'app.bsky.graph.getFollowers',
|
|
9525
9651
|
AppBskyGraphGetFollows: 'app.bsky.graph.getFollows',
|
|
9652
|
+
AppBskyGraphGetKnownFollowers: 'app.bsky.graph.getKnownFollowers',
|
|
9526
9653
|
AppBskyGraphGetList: 'app.bsky.graph.getList',
|
|
9527
9654
|
AppBskyGraphGetListBlocks: 'app.bsky.graph.getListBlocks',
|
|
9528
9655
|
AppBskyGraphGetListMutes: 'app.bsky.graph.getListMutes',
|
|
@@ -9536,8 +9663,10 @@ export const ids = {
|
|
|
9536
9663
|
AppBskyGraphListitem: 'app.bsky.graph.listitem',
|
|
9537
9664
|
AppBskyGraphMuteActor: 'app.bsky.graph.muteActor',
|
|
9538
9665
|
AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList',
|
|
9666
|
+
AppBskyGraphMuteThread: 'app.bsky.graph.muteThread',
|
|
9539
9667
|
AppBskyGraphUnmuteActor: 'app.bsky.graph.unmuteActor',
|
|
9540
9668
|
AppBskyGraphUnmuteActorList: 'app.bsky.graph.unmuteActorList',
|
|
9669
|
+
AppBskyGraphUnmuteThread: 'app.bsky.graph.unmuteThread',
|
|
9541
9670
|
AppBskyLabelerDefs: 'app.bsky.labeler.defs',
|
|
9542
9671
|
AppBskyLabelerGetServices: 'app.bsky.labeler.getServices',
|
|
9543
9672
|
AppBskyLabelerService: 'app.bsky.labeler.service',
|
|
@@ -133,6 +133,7 @@ export interface ViewerState {
|
|
|
133
133
|
blockingByList?: AppBskyGraphDefs.ListViewBasic
|
|
134
134
|
following?: string
|
|
135
135
|
followedBy?: string
|
|
136
|
+
knownFollowers?: KnownFollowers
|
|
136
137
|
[k: string]: unknown
|
|
137
138
|
}
|
|
138
139
|
|
|
@@ -148,6 +149,25 @@ export function validateViewerState(v: unknown): ValidationResult {
|
|
|
148
149
|
return lexicons.validate('app.bsky.actor.defs#viewerState', v)
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
/** The subject's followers whom you also follow */
|
|
153
|
+
export interface KnownFollowers {
|
|
154
|
+
count: number
|
|
155
|
+
followers: ProfileViewBasic[]
|
|
156
|
+
[k: string]: unknown
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function isKnownFollowers(v: unknown): v is KnownFollowers {
|
|
160
|
+
return (
|
|
161
|
+
isObj(v) &&
|
|
162
|
+
hasProp(v, '$type') &&
|
|
163
|
+
v.$type === 'app.bsky.actor.defs#knownFollowers'
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function validateKnownFollowers(v: unknown): ValidationResult {
|
|
168
|
+
return lexicons.validate('app.bsky.actor.defs#knownFollowers', v)
|
|
169
|
+
}
|
|
170
|
+
|
|
151
171
|
export type Preferences = (
|
|
152
172
|
| AdultContentPref
|
|
153
173
|
| ContentLabelPref
|