@atproto/bsky 0.0.43 → 0.0.44
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 +7 -0
- package/dist/api/app/bsky/feed/getAuthorFeed.d.ts +1 -0
- package/dist/api/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getAuthorFeed.js +84 -4
- package/dist/api/app/bsky/feed/getAuthorFeed.js.map +1 -1
- package/dist/api/app/bsky/notification/registerPush.js.map +1 -1
- package/dist/auth-verifier.d.ts.map +1 -1
- package/dist/auth-verifier.js.map +1 -1
- package/dist/cache/read-through.d.ts.map +1 -1
- package/dist/cache/read-through.js.map +1 -1
- package/dist/data-plane/server/db/pagination.d.ts.map +1 -1
- package/dist/data-plane/server/db/pagination.js.map +1 -1
- package/dist/data-plane/server/index.d.ts.map +1 -1
- package/dist/data-plane/server/index.js.map +1 -1
- package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
- package/dist/data-plane/server/indexing/index.js.map +1 -1
- package/dist/data-plane/server/subscription/index.d.ts.map +1 -1
- package/dist/data-plane/server/subscription/index.js.map +1 -1
- package/dist/data-plane/server/subscription/util.d.ts.map +1 -1
- package/dist/data-plane/server/subscription/util.js.map +1 -1
- package/dist/hydration/actor.d.ts.map +1 -1
- package/dist/hydration/actor.js.map +1 -1
- package/dist/hydration/graph.d.ts.map +1 -1
- package/dist/hydration/graph.js.map +1 -1
- package/dist/image/server.d.ts.map +1 -1
- package/dist/image/server.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +14 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +15 -1
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/getRecord.d.ts +1 -1
- package/dist/lexicon/types/com/atproto/sync/getRecord.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/api/app/bsky/feed/getAuthorFeed.ts +80 -5
- package/src/api/app/bsky/notification/registerPush.ts +2 -2
- package/src/auth-verifier.ts +4 -1
- package/src/cache/read-through.ts +13 -7
- package/src/data-plane/server/db/pagination.ts +4 -1
- package/src/data-plane/server/index.ts +4 -1
- package/src/data-plane/server/indexing/index.ts +10 -7
- package/src/data-plane/server/subscription/index.ts +2 -3
- package/src/data-plane/server/subscription/util.ts +4 -1
- package/src/hydration/actor.ts +10 -7
- package/src/hydration/graph.ts +8 -5
- package/src/image/server.ts +4 -1
- package/src/lexicon/lexicons.ts +16 -1
- package/src/lexicon/types/com/atproto/sync/getRecord.ts +1 -1
- package/tests/_util.ts +4 -4
- package/tests/views/author-feed.test.ts +41 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/bsky",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.44",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Reference implementation of app.bsky App View (Bluesky API)",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@atproto/lexicon": "^0.4.0",
|
|
47
47
|
"@atproto/repo": "^0.4.0",
|
|
48
48
|
"@atproto/syntax": "^0.3.0",
|
|
49
|
-
"@atproto/xrpc-server": "^0.5.
|
|
49
|
+
"@atproto/xrpc-server": "^0.5.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@bufbuild/buf": "^1.28.1",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"ts-node": "^10.8.2",
|
|
64
64
|
"@atproto/api": "^0.12.2",
|
|
65
65
|
"@atproto/lex-cli": "^0.4.0",
|
|
66
|
-
"@atproto/pds": "^0.4.
|
|
66
|
+
"@atproto/pds": "^0.4.13",
|
|
67
67
|
"@atproto/xrpc": "^0.5.0"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
@@ -15,7 +15,7 @@ import { Views } from '../../../../views'
|
|
|
15
15
|
import { DataPlaneClient } from '../../../../data-plane'
|
|
16
16
|
import { parseString } from '../../../../hydration/util'
|
|
17
17
|
import { Actor } from '../../../../hydration/actor'
|
|
18
|
-
import { FeedItem } from '../../../../hydration/feed'
|
|
18
|
+
import { FeedItem, Post } from '../../../../hydration/feed'
|
|
19
19
|
import { FeedType } from '../../../../proto/bsky_pb'
|
|
20
20
|
|
|
21
21
|
export default function (server: Server, ctx: AppContext) {
|
|
@@ -77,7 +77,7 @@ export const skeleton = async (inputs: {
|
|
|
77
77
|
throw new InvalidRequestError('Profile not found')
|
|
78
78
|
}
|
|
79
79
|
if (clearlyBadCursor(params.cursor)) {
|
|
80
|
-
return { actor, items: [] }
|
|
80
|
+
return { actor, filter: params.filter, items: [] }
|
|
81
81
|
}
|
|
82
82
|
const res = await ctx.dataplane.getAuthorFeed({
|
|
83
83
|
actorDid: did,
|
|
@@ -87,6 +87,7 @@ export const skeleton = async (inputs: {
|
|
|
87
87
|
})
|
|
88
88
|
return {
|
|
89
89
|
actor,
|
|
90
|
+
filter: params.filter,
|
|
90
91
|
items: res.items.map((item) => ({
|
|
91
92
|
post: { uri: item.uri, cid: item.cid || undefined },
|
|
92
93
|
repost: item.repost
|
|
@@ -129,14 +130,30 @@ const noBlocksOrMutedReposts = (inputs: {
|
|
|
129
130
|
'BlockedByActor',
|
|
130
131
|
)
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
+
|
|
134
|
+
const checkBlocksAndMutes = (item: FeedItem) => {
|
|
133
135
|
const bam = ctx.views.feedItemBlocksAndMutes(item, hydration)
|
|
134
136
|
return (
|
|
135
137
|
!bam.authorBlocked &&
|
|
136
138
|
!bam.originatorBlocked &&
|
|
137
|
-
!
|
|
139
|
+
(!bam.authorMuted || bam.originatorMuted) // repost of muted content
|
|
138
140
|
)
|
|
139
|
-
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (skeleton.filter === 'posts_and_author_threads') {
|
|
144
|
+
// ensure replies are only included if the feed contains all
|
|
145
|
+
// replies up to the thread root (i.e. a complete self-thread.)
|
|
146
|
+
const selfThread = new SelfThreadTracker(skeleton.items, hydration)
|
|
147
|
+
skeleton.items = skeleton.items.filter((item) => {
|
|
148
|
+
return (
|
|
149
|
+
checkBlocksAndMutes(item) &&
|
|
150
|
+
(item.repost || selfThread.ok(item.post.uri))
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
} else {
|
|
154
|
+
skeleton.items = skeleton.items.filter(checkBlocksAndMutes)
|
|
155
|
+
}
|
|
156
|
+
|
|
140
157
|
return skeleton
|
|
141
158
|
}
|
|
142
159
|
|
|
@@ -165,5 +182,63 @@ type Params = QueryParams & {
|
|
|
165
182
|
type Skeleton = {
|
|
166
183
|
actor: Actor
|
|
167
184
|
items: FeedItem[]
|
|
185
|
+
filter: QueryParams['filter']
|
|
168
186
|
cursor?: string
|
|
169
187
|
}
|
|
188
|
+
|
|
189
|
+
class SelfThreadTracker {
|
|
190
|
+
feedUris = new Set<string>()
|
|
191
|
+
cache = new Map<string, boolean>()
|
|
192
|
+
|
|
193
|
+
constructor(
|
|
194
|
+
items: FeedItem[],
|
|
195
|
+
private hydration: HydrationState,
|
|
196
|
+
) {
|
|
197
|
+
items.forEach((item) => {
|
|
198
|
+
if (!item.repost) {
|
|
199
|
+
this.feedUris.add(item.post.uri)
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
ok(uri: string, loop = new Set<string>()) {
|
|
205
|
+
// if we've already checked this uri, pull from the cache
|
|
206
|
+
if (this.cache.has(uri)) {
|
|
207
|
+
return this.cache.get(uri) ?? false
|
|
208
|
+
}
|
|
209
|
+
// loop detection
|
|
210
|
+
if (loop.has(uri)) {
|
|
211
|
+
this.cache.set(uri, false)
|
|
212
|
+
return false
|
|
213
|
+
} else {
|
|
214
|
+
loop.add(uri)
|
|
215
|
+
}
|
|
216
|
+
// cache through the result
|
|
217
|
+
const result = this._ok(uri, loop)
|
|
218
|
+
this.cache.set(uri, result)
|
|
219
|
+
return result
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private _ok(uri: string, loop: Set<string>): boolean {
|
|
223
|
+
// must be in the feed to be in a self-thread
|
|
224
|
+
if (!this.feedUris.has(uri)) {
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
// must be hydratable to be part of self-thread
|
|
228
|
+
const post = this.hydration.posts?.get(uri)
|
|
229
|
+
if (!post) {
|
|
230
|
+
return false
|
|
231
|
+
}
|
|
232
|
+
// root posts (no parent) are trivial case of self-thread
|
|
233
|
+
const parentUri = getParentUri(post)
|
|
234
|
+
if (parentUri === null) {
|
|
235
|
+
return true
|
|
236
|
+
}
|
|
237
|
+
// recurse w/ cache: this post is in a self-thread if its parent is.
|
|
238
|
+
return this.ok(parentUri, loop)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function getParentUri(post: Post) {
|
|
243
|
+
return post.record.reply?.parent.uri ?? null
|
|
244
|
+
}
|
package/src/auth-verifier.ts
CHANGED
|
@@ -64,7 +64,10 @@ export class AuthVerifier {
|
|
|
64
64
|
public modServiceDid: string
|
|
65
65
|
private adminPasses: Set<string>
|
|
66
66
|
|
|
67
|
-
constructor(
|
|
67
|
+
constructor(
|
|
68
|
+
public dataplane: DataPlaneClient,
|
|
69
|
+
opts: AuthVerifierOpts,
|
|
70
|
+
) {
|
|
68
71
|
this.ownDid = opts.ownDid
|
|
69
72
|
this.modServiceDid = opts.modServiceDid
|
|
70
73
|
this.adminPasses = new Set(opts.adminPasses)
|
|
@@ -14,7 +14,10 @@ export type CacheOptions<T> = {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class ReadThroughCache<T> {
|
|
17
|
-
constructor(
|
|
17
|
+
constructor(
|
|
18
|
+
public redis: Redis,
|
|
19
|
+
public opts: CacheOptions<T>,
|
|
20
|
+
) {}
|
|
18
21
|
|
|
19
22
|
private async _fetchMany(keys: string[]): Promise<Record<string, T | null>> {
|
|
20
23
|
let result: Record<string, T | null> = {}
|
|
@@ -142,10 +145,13 @@ export class ReadThroughCache<T> {
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
const removeNulls = <T>(obj: Record<string, T | null>): Record<string, T> => {
|
|
145
|
-
return Object.entries(obj).reduce(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
return Object.entries(obj).reduce(
|
|
149
|
+
(acc, [key, val]) => {
|
|
150
|
+
if (val !== null) {
|
|
151
|
+
acc[key] = val
|
|
152
|
+
}
|
|
153
|
+
return acc
|
|
154
|
+
},
|
|
155
|
+
{} as Record<string, T>,
|
|
156
|
+
)
|
|
151
157
|
}
|
|
@@ -23,7 +23,10 @@ export type LabeledResult = {
|
|
|
23
23
|
* ↳ SQL Condition
|
|
24
24
|
*/
|
|
25
25
|
export abstract class GenericKeyset<R, LR extends LabeledResult> {
|
|
26
|
-
constructor(
|
|
26
|
+
constructor(
|
|
27
|
+
public primary: DbRef,
|
|
28
|
+
public secondary: DbRef,
|
|
29
|
+
) {}
|
|
27
30
|
abstract labelResult(result: R): LR
|
|
28
31
|
abstract labeledResultToCursor(labeled: LR): Cursor
|
|
29
32
|
abstract cursorToLabeledResult(cursor: Cursor): LR
|
|
@@ -9,7 +9,10 @@ import { IdResolver, MemoryCache } from '@atproto/identity'
|
|
|
9
9
|
export { RepoSubscription } from './subscription'
|
|
10
10
|
|
|
11
11
|
export class DataPlaneServer {
|
|
12
|
-
constructor(
|
|
12
|
+
constructor(
|
|
13
|
+
public server: http.Server,
|
|
14
|
+
public idResolver: IdResolver,
|
|
15
|
+
) {}
|
|
13
16
|
|
|
14
17
|
static async create(db: Database, port: number, plcUrl?: string) {
|
|
15
18
|
const app = express()
|
|
@@ -206,13 +206,16 @@ export class IndexingService {
|
|
|
206
206
|
.where('did', '=', did)
|
|
207
207
|
.select(['uri', 'cid'])
|
|
208
208
|
.execute()
|
|
209
|
-
return res.reduce(
|
|
210
|
-
acc
|
|
211
|
-
uri
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
return res.reduce(
|
|
210
|
+
(acc, cur) => {
|
|
211
|
+
acc[cur.uri] = {
|
|
212
|
+
uri: new AtUri(cur.uri),
|
|
213
|
+
cid: CID.parse(cur.cid),
|
|
214
|
+
}
|
|
215
|
+
return acc
|
|
216
|
+
},
|
|
217
|
+
{} as Record<string, { uri: AtUri; cid: CID }>,
|
|
218
|
+
)
|
|
216
219
|
}
|
|
217
220
|
|
|
218
221
|
async setCommitLastSeen(
|
|
@@ -134,9 +134,8 @@ export class RepoSubscription {
|
|
|
134
134
|
return
|
|
135
135
|
}
|
|
136
136
|
if (msg.rebase) {
|
|
137
|
-
const needsReindex =
|
|
138
|
-
root
|
|
139
|
-
)
|
|
137
|
+
const needsReindex =
|
|
138
|
+
await this.indexingSvc.checkCommitNeedsIndexing(root)
|
|
140
139
|
if (needsReindex) {
|
|
141
140
|
await this.indexingSvc.indexRepo(msg.repo, rootCid.toString())
|
|
142
141
|
}
|
|
@@ -88,7 +88,10 @@ export class ConsecutiveList<T> {
|
|
|
88
88
|
|
|
89
89
|
export class ConsecutiveItem<T> {
|
|
90
90
|
isComplete = false
|
|
91
|
-
constructor(
|
|
91
|
+
constructor(
|
|
92
|
+
private consecutive: ConsecutiveList<T>,
|
|
93
|
+
public value: T,
|
|
94
|
+
) {}
|
|
92
95
|
|
|
93
96
|
complete() {
|
|
94
97
|
this.isComplete = true
|
package/src/hydration/actor.ts
CHANGED
|
@@ -61,13 +61,16 @@ export class ActorHydrator {
|
|
|
61
61
|
const res = handles.length
|
|
62
62
|
? await this.dataplane.getDidsByHandles({ handles })
|
|
63
63
|
: { dids: [] }
|
|
64
|
-
const didByHandle = handles.reduce(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
const didByHandle = handles.reduce(
|
|
65
|
+
(acc, cur, i) => {
|
|
66
|
+
const did = res.dids[i]
|
|
67
|
+
if (did && did.length > 0) {
|
|
68
|
+
return acc.set(cur, did)
|
|
69
|
+
}
|
|
70
|
+
return acc
|
|
71
|
+
},
|
|
72
|
+
new Map() as Map<string, string>,
|
|
73
|
+
)
|
|
71
74
|
return handleOrDids.map((id) =>
|
|
72
75
|
id.startsWith('did:') ? id : didByHandle.get(id),
|
|
73
76
|
)
|
package/src/hydration/graph.ts
CHANGED
|
@@ -28,11 +28,14 @@ export type Block = RecordInfo<BlockRecord>
|
|
|
28
28
|
export type RelationshipPair = [didA: string, didB: string]
|
|
29
29
|
|
|
30
30
|
const dedupePairs = (pairs: RelationshipPair[]): RelationshipPair[] => {
|
|
31
|
-
const mapped = pairs.reduce(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
const mapped = pairs.reduce(
|
|
32
|
+
(acc, cur) => {
|
|
33
|
+
const sorted = ([...cur] as RelationshipPair).sort()
|
|
34
|
+
acc[sorted.join('-')] = sorted
|
|
35
|
+
return acc
|
|
36
|
+
},
|
|
37
|
+
{} as Record<string, RelationshipPair>,
|
|
38
|
+
)
|
|
36
39
|
return Object.values(mapped)
|
|
37
40
|
}
|
|
38
41
|
|
package/src/image/server.ts
CHANGED
|
@@ -29,7 +29,10 @@ export class ImageProcessingServer {
|
|
|
29
29
|
app: Express = express()
|
|
30
30
|
uriBuilder: ImageUriBuilder
|
|
31
31
|
|
|
32
|
-
constructor(
|
|
32
|
+
constructor(
|
|
33
|
+
public cfg: ServerConfig,
|
|
34
|
+
public cache: BlobCache,
|
|
35
|
+
) {
|
|
33
36
|
this.uriBuilder = new ImageUriBuilder('')
|
|
34
37
|
this.app.get('*', this.handler.bind(this))
|
|
35
38
|
this.app.use(errorMiddleware)
|
package/src/lexicon/lexicons.ts
CHANGED
|
@@ -1067,6 +1067,8 @@ export const schemaDict = {
|
|
|
1067
1067
|
},
|
|
1068
1068
|
reason: {
|
|
1069
1069
|
type: 'string',
|
|
1070
|
+
maxGraphemes: 2000,
|
|
1071
|
+
maxLength: 20000,
|
|
1070
1072
|
description:
|
|
1071
1073
|
'Additional context about the content and violation.',
|
|
1072
1074
|
},
|
|
@@ -2438,9 +2440,11 @@ export const schemaDict = {
|
|
|
2438
2440
|
properties: {
|
|
2439
2441
|
privacyPolicy: {
|
|
2440
2442
|
type: 'string',
|
|
2443
|
+
format: 'uri',
|
|
2441
2444
|
},
|
|
2442
2445
|
termsOfService: {
|
|
2443
2446
|
type: 'string',
|
|
2447
|
+
format: 'uri',
|
|
2444
2448
|
},
|
|
2445
2449
|
},
|
|
2446
2450
|
},
|
|
@@ -3052,7 +3056,8 @@ export const schemaDict = {
|
|
|
3052
3056
|
commit: {
|
|
3053
3057
|
type: 'string',
|
|
3054
3058
|
format: 'cid',
|
|
3055
|
-
description:
|
|
3059
|
+
description:
|
|
3060
|
+
'DEPRECATED: referenced a repo commit by CID, and retrieved record as of that commit',
|
|
3056
3061
|
},
|
|
3057
3062
|
},
|
|
3058
3063
|
},
|
|
@@ -3621,6 +3626,7 @@ export const schemaDict = {
|
|
|
3621
3626
|
},
|
|
3622
3627
|
avatar: {
|
|
3623
3628
|
type: 'string',
|
|
3629
|
+
format: 'uri',
|
|
3624
3630
|
},
|
|
3625
3631
|
associated: {
|
|
3626
3632
|
type: 'ref',
|
|
@@ -3663,6 +3669,7 @@ export const schemaDict = {
|
|
|
3663
3669
|
},
|
|
3664
3670
|
avatar: {
|
|
3665
3671
|
type: 'string',
|
|
3672
|
+
format: 'uri',
|
|
3666
3673
|
},
|
|
3667
3674
|
associated: {
|
|
3668
3675
|
type: 'ref',
|
|
@@ -3709,9 +3716,11 @@ export const schemaDict = {
|
|
|
3709
3716
|
},
|
|
3710
3717
|
avatar: {
|
|
3711
3718
|
type: 'string',
|
|
3719
|
+
format: 'uri',
|
|
3712
3720
|
},
|
|
3713
3721
|
banner: {
|
|
3714
3722
|
type: 'string',
|
|
3723
|
+
format: 'uri',
|
|
3715
3724
|
},
|
|
3716
3725
|
followersCount: {
|
|
3717
3726
|
type: 'integer',
|
|
@@ -4388,6 +4397,7 @@ export const schemaDict = {
|
|
|
4388
4397
|
},
|
|
4389
4398
|
thumb: {
|
|
4390
4399
|
type: 'string',
|
|
4400
|
+
format: 'uri',
|
|
4391
4401
|
},
|
|
4392
4402
|
},
|
|
4393
4403
|
},
|
|
@@ -4468,11 +4478,13 @@ export const schemaDict = {
|
|
|
4468
4478
|
properties: {
|
|
4469
4479
|
thumb: {
|
|
4470
4480
|
type: 'string',
|
|
4481
|
+
format: 'uri',
|
|
4471
4482
|
description:
|
|
4472
4483
|
'Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View.',
|
|
4473
4484
|
},
|
|
4474
4485
|
fullsize: {
|
|
4475
4486
|
type: 'string',
|
|
4487
|
+
format: 'uri',
|
|
4476
4488
|
description:
|
|
4477
4489
|
'Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View.',
|
|
4478
4490
|
},
|
|
@@ -4886,6 +4898,7 @@ export const schemaDict = {
|
|
|
4886
4898
|
},
|
|
4887
4899
|
avatar: {
|
|
4888
4900
|
type: 'string',
|
|
4901
|
+
format: 'uri',
|
|
4889
4902
|
},
|
|
4890
4903
|
likeCount: {
|
|
4891
4904
|
type: 'integer',
|
|
@@ -6201,6 +6214,7 @@ export const schemaDict = {
|
|
|
6201
6214
|
},
|
|
6202
6215
|
avatar: {
|
|
6203
6216
|
type: 'string',
|
|
6217
|
+
format: 'uri',
|
|
6204
6218
|
},
|
|
6205
6219
|
labels: {
|
|
6206
6220
|
type: 'array',
|
|
@@ -6258,6 +6272,7 @@ export const schemaDict = {
|
|
|
6258
6272
|
},
|
|
6259
6273
|
avatar: {
|
|
6260
6274
|
type: 'string',
|
|
6275
|
+
format: 'uri',
|
|
6261
6276
|
},
|
|
6262
6277
|
labels: {
|
|
6263
6278
|
type: 'array',
|
package/tests/_util.ts
CHANGED
|
@@ -178,16 +178,16 @@ export const stripViewerFromPost = (postUnknown: unknown): PostView => {
|
|
|
178
178
|
post.embed && isViewRecord(post.embed.record)
|
|
179
179
|
? post.embed.record // Record from record embed
|
|
180
180
|
: post.embed?.['record'] && isViewRecord(post.embed['record']['record'])
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
? post.embed['record']['record'] // Record from record-with-media embed
|
|
182
|
+
: undefined
|
|
183
183
|
if (recordEmbed) {
|
|
184
184
|
recordEmbed.author = stripViewer(recordEmbed.author)
|
|
185
185
|
recordEmbed.embeds?.forEach((deepEmbed) => {
|
|
186
186
|
const deepRecordEmbed = isViewRecord(deepEmbed.record)
|
|
187
187
|
? deepEmbed.record // Record from record embed
|
|
188
188
|
: deepEmbed['record'] && isViewRecord(deepEmbed['record']['record'])
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
? deepEmbed['record']['record'] // Record from record-with-media embed
|
|
190
|
+
: undefined
|
|
191
191
|
if (deepRecordEmbed) {
|
|
192
192
|
deepRecordEmbed.author = stripViewer(deepRecordEmbed.author)
|
|
193
193
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import AtpAgent from '@atproto/api'
|
|
1
|
+
import AtpAgent, { AtUri } from '@atproto/api'
|
|
2
2
|
import { TestNetwork, SeedClient, authorFeedSeed } from '@atproto/dev-env'
|
|
3
3
|
import { forSnapshot, paginateAll, stripViewerFromPost } from '../_util'
|
|
4
|
-
import { isRecord } from '../../src/lexicon/types/app/bsky/feed/post'
|
|
4
|
+
import { ReplyRef, isRecord } from '../../src/lexicon/types/app/bsky/feed/post'
|
|
5
5
|
import { isView as isEmbedRecordWithMedia } from '../../src/lexicon/types/app/bsky/embed/recordWithMedia'
|
|
6
6
|
import { isView as isImageEmbed } from '../../src/lexicon/types/app/bsky/embed/images'
|
|
7
|
+
import { isPostView } from '../../src/lexicon/types/app/bsky/feed/defs'
|
|
7
8
|
|
|
8
9
|
describe('pds author feed views', () => {
|
|
9
10
|
let network: TestNetwork
|
|
@@ -282,13 +283,48 @@ describe('pds author feed views', () => {
|
|
|
282
283
|
filter: 'posts_and_author_threads',
|
|
283
284
|
})
|
|
284
285
|
|
|
285
|
-
expect(eveFeed.feed.length).toEqual(
|
|
286
|
+
expect(eveFeed.feed.length).toEqual(6)
|
|
286
287
|
expect(
|
|
287
288
|
eveFeed.feed.some(({ post }) => {
|
|
288
|
-
|
|
289
|
+
const replyByEve =
|
|
289
290
|
isRecord(post.record) && post.record.reply && post.author.did === eve
|
|
290
|
-
|
|
291
|
+
return replyByEve
|
|
292
|
+
}),
|
|
293
|
+
).toBeTruthy()
|
|
294
|
+
// does not include eve's replies to fred, even within her own thread.
|
|
295
|
+
expect(
|
|
296
|
+
eveFeed.feed.every(({ post, reply }) => {
|
|
297
|
+
if (!post || !isRecord(post.record) || !post.record.reply) {
|
|
298
|
+
return true // not a reply
|
|
299
|
+
}
|
|
300
|
+
const replyToEve = isReplyTo(post.record.reply, eve)
|
|
301
|
+
const replyToReplyByEve =
|
|
302
|
+
reply &&
|
|
303
|
+
isPostView(reply.parent) &&
|
|
304
|
+
isRecord(reply.parent.record) &&
|
|
305
|
+
(!reply.parent.record.reply ||
|
|
306
|
+
isReplyTo(reply.parent.record.reply, eve))
|
|
307
|
+
return replyToEve && replyToReplyByEve
|
|
308
|
+
}),
|
|
309
|
+
).toBeTruthy()
|
|
310
|
+
// reposts are preserved
|
|
311
|
+
expect(
|
|
312
|
+
eveFeed.feed.some(({ post, reason }) => {
|
|
313
|
+
const repostOfOther =
|
|
314
|
+
reason && isRecord(post.record) && post.author.did !== eve
|
|
315
|
+
return repostOfOther
|
|
291
316
|
}),
|
|
292
317
|
).toBeTruthy()
|
|
293
318
|
})
|
|
294
319
|
})
|
|
320
|
+
|
|
321
|
+
function isReplyTo(reply: ReplyRef, did: string) {
|
|
322
|
+
return (
|
|
323
|
+
getDidFromUri(reply.root.uri) === did &&
|
|
324
|
+
getDidFromUri(reply.parent.uri) === did
|
|
325
|
+
)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function getDidFromUri(uri: string) {
|
|
329
|
+
return new AtUri(uri).hostname
|
|
330
|
+
}
|