@atproto/bsky 0.0.152 → 0.0.154
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 +16 -0
- package/dist/api/app/bsky/unspecced/getPostThreadHiddenV2.js +2 -1
- package/dist/api/app/bsky/unspecced/getPostThreadHiddenV2.js.map +1 -1
- package/dist/api/com/atproto/repo/getRecord.d.ts.map +1 -1
- package/dist/api/com/atproto/repo/getRecord.js +1 -1
- package/dist/api/com/atproto/repo/getRecord.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/data-plane/server/db/migrations/20250528T221913281Z-add-record-tags.d.ts +4 -0
- package/dist/data-plane/server/db/migrations/20250528T221913281Z-add-record-tags.d.ts.map +1 -0
- package/dist/data-plane/server/db/migrations/20250528T221913281Z-add-record-tags.js +11 -0
- package/dist/data-plane/server/db/migrations/20250528T221913281Z-add-record-tags.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/record.d.ts +1 -0
- package/dist/data-plane/server/db/tables/record.d.ts.map +1 -1
- package/dist/data-plane/server/db/tables/record.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/hydration/feed.d.ts +1 -0
- package/dist/hydration/feed.d.ts.map +1 -1
- package/dist/hydration/feed.js +2 -0
- package/dist/hydration/feed.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +112 -122
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +66 -66
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts +33 -0
- package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/defs.js +36 -0
- package/dist/lexicon/types/app/bsky/unspecced/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2.d.ts +5 -13
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2.js +0 -9
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2.js.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.d.ts +2 -29
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.js +0 -36
- package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +4 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +16 -0
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/proto/bsync_connect.d.ts +19 -1
- package/dist/proto/bsync_connect.d.ts.map +1 -1
- package/dist/proto/bsync_connect.js +18 -0
- package/dist/proto/bsync_connect.js.map +1 -1
- package/dist/proto/bsync_pb.d.ts +150 -0
- package/dist/proto/bsync_pb.d.ts.map +1 -1
- package/dist/proto/bsync_pb.js +401 -1
- package/dist/proto/bsync_pb.js.map +1 -1
- package/dist/views/index.d.ts +6 -1
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +68 -27
- package/dist/views/index.js.map +1 -1
- package/dist/views/threads-v2.d.ts +9 -5
- package/dist/views/threads-v2.d.ts.map +1 -1
- package/dist/views/threads-v2.js +50 -25
- package/dist/views/threads-v2.js.map +1 -1
- package/package.json +7 -7
- package/proto/bsky.proto +1 -0
- package/src/api/app/bsky/unspecced/getPostThreadHiddenV2.ts +2 -1
- package/src/api/com/atproto/repo/getRecord.ts +4 -1
- package/src/config.ts +15 -0
- package/src/data-plane/server/db/migrations/20250528T221913281Z-add-record-tags.ts +9 -0
- package/src/data-plane/server/db/migrations/index.ts +1 -0
- package/src/data-plane/server/db/tables/record.ts +1 -0
- package/src/data-plane/server/routes/records.ts +1 -0
- package/src/hydration/feed.ts +3 -0
- package/src/index.ts +2 -0
- package/src/lexicon/lexicons.ts +72 -71
- package/src/lexicon/types/app/bsky/unspecced/defs.ts +73 -0
- package/src/lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2.ts +5 -22
- package/src/lexicon/types/app/bsky/unspecced/getPostThreadV2.ts +5 -72
- package/src/proto/bsky_pb.ts +12 -0
- package/src/proto/bsync_connect.ts +22 -0
- package/src/proto/bsync_pb.ts +355 -0
- package/src/views/index.ts +102 -58
- package/src/views/threads-v2.ts +88 -60
- package/tests/seed/thread-v2.ts +131 -32
- package/tests/views/__snapshots__/thread-v2.test.ts.snap +69 -23
- package/tests/views/thread-v2.test.ts +173 -69
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/views/index.ts
CHANGED
|
@@ -76,6 +76,7 @@ import {
|
|
|
76
76
|
} from '../util/uris'
|
|
77
77
|
import {
|
|
78
78
|
ThreadHiddenAnchorPostNode,
|
|
79
|
+
ThreadHiddenItemValuePost,
|
|
79
80
|
ThreadHiddenPostNode,
|
|
80
81
|
ThreadItemValueBlocked,
|
|
81
82
|
ThreadItemValueNoUnauthenticated,
|
|
@@ -130,11 +131,15 @@ export class Views {
|
|
|
130
131
|
public imgUriBuilder: ImageUriBuilder = this.opts.imgUriBuilder
|
|
131
132
|
public videoUriBuilder: VideoUriBuilder = this.opts.videoUriBuilder
|
|
132
133
|
public indexedAtEpoch: Date | undefined = this.opts.indexedAtEpoch
|
|
134
|
+
private threadTagsBumpDown: readonly string[] = this.opts.threadTagsBumpDown
|
|
135
|
+
private threadTagsHide: readonly string[] = this.opts.threadTagsHide
|
|
133
136
|
constructor(
|
|
134
137
|
private opts: {
|
|
135
138
|
imgUriBuilder: ImageUriBuilder
|
|
136
139
|
videoUriBuilder: VideoUriBuilder
|
|
137
140
|
indexedAtEpoch: Date | undefined
|
|
141
|
+
threadTagsBumpDown: readonly string[]
|
|
142
|
+
threadTagsHide: readonly string[]
|
|
138
143
|
},
|
|
139
144
|
) {}
|
|
140
145
|
|
|
@@ -1248,6 +1253,7 @@ export class Views {
|
|
|
1248
1253
|
below,
|
|
1249
1254
|
depth: 1,
|
|
1250
1255
|
branchingFactor,
|
|
1256
|
+
prioritizeFollowedUsers,
|
|
1251
1257
|
},
|
|
1252
1258
|
state,
|
|
1253
1259
|
)
|
|
@@ -1263,19 +1269,21 @@ export class Views {
|
|
|
1263
1269
|
repliesAllowance: Infinity, // While we don't have pagination.
|
|
1264
1270
|
uri: anchorUri,
|
|
1265
1271
|
}),
|
|
1272
|
+
tags: post.tags,
|
|
1266
1273
|
hasOPLike: !!state.threadContexts?.get(postView.uri)?.like,
|
|
1267
1274
|
parent,
|
|
1268
1275
|
replies,
|
|
1269
1276
|
}
|
|
1270
1277
|
}
|
|
1271
1278
|
|
|
1272
|
-
const thread = sortTrimFlattenThreadTree
|
|
1279
|
+
const thread = sortTrimFlattenThreadTree(anchorTree, {
|
|
1273
1280
|
opDid,
|
|
1274
1281
|
branchingFactor,
|
|
1275
1282
|
sort,
|
|
1276
1283
|
prioritizeFollowedUsers,
|
|
1277
1284
|
viewer: state.ctx?.viewer ?? null,
|
|
1278
|
-
|
|
1285
|
+
threadTagsBumpDown: this.threadTagsBumpDown,
|
|
1286
|
+
threadTagsHide: this.threadTagsHide,
|
|
1279
1287
|
})
|
|
1280
1288
|
|
|
1281
1289
|
return {
|
|
@@ -1392,6 +1400,7 @@ export class Views {
|
|
|
1392
1400
|
postView,
|
|
1393
1401
|
uri,
|
|
1394
1402
|
}),
|
|
1403
|
+
tags: post.tags,
|
|
1395
1404
|
hasOPLike: !!state.threadContexts?.get(postView.uri)?.like,
|
|
1396
1405
|
parent,
|
|
1397
1406
|
replies: undefined,
|
|
@@ -1410,6 +1419,7 @@ export class Views {
|
|
|
1410
1419
|
below,
|
|
1411
1420
|
depth,
|
|
1412
1421
|
branchingFactor,
|
|
1422
|
+
prioritizeFollowedUsers,
|
|
1413
1423
|
}: {
|
|
1414
1424
|
parentUri: string
|
|
1415
1425
|
isOPThread: boolean
|
|
@@ -1419,6 +1429,7 @@ export class Views {
|
|
|
1419
1429
|
below: number
|
|
1420
1430
|
depth: number
|
|
1421
1431
|
branchingFactor: number
|
|
1432
|
+
prioritizeFollowedUsers: boolean
|
|
1422
1433
|
},
|
|
1423
1434
|
state: HydrationState,
|
|
1424
1435
|
): { replies: ThreadTreeVisible[] | undefined; hasHiddenReplies: boolean } {
|
|
@@ -1430,23 +1441,22 @@ export class Views {
|
|
|
1430
1441
|
const childrenUris = childrenByParentUri[parentUri] ?? []
|
|
1431
1442
|
let hasHiddenReplies = false
|
|
1432
1443
|
const replies = mapDefined(childrenUris, (uri) => {
|
|
1433
|
-
const replyInclusion = this.checkThreadV2ReplyInclusion(
|
|
1444
|
+
const replyInclusion = this.checkThreadV2ReplyInclusion({
|
|
1434
1445
|
uri,
|
|
1435
1446
|
rootUri,
|
|
1436
1447
|
state,
|
|
1437
|
-
)
|
|
1448
|
+
})
|
|
1438
1449
|
if (!replyInclusion) {
|
|
1439
1450
|
return undefined
|
|
1440
1451
|
}
|
|
1441
|
-
const { authorDid, postView } = replyInclusion
|
|
1452
|
+
const { authorDid, post, postView } = replyInclusion
|
|
1442
1453
|
|
|
1443
1454
|
// Hidden.
|
|
1444
|
-
const {
|
|
1445
|
-
{ rootUri, uri },
|
|
1455
|
+
const { isHidden } = this.isHiddenThreadPost(
|
|
1456
|
+
{ post, postView, prioritizeFollowedUsers, rootUri, uri },
|
|
1446
1457
|
state,
|
|
1447
1458
|
)
|
|
1448
|
-
|
|
1449
|
-
if (hiddenByThreadgate || mutedByViewer) {
|
|
1459
|
+
if (isHidden) {
|
|
1450
1460
|
// Only care about anchor replies
|
|
1451
1461
|
if (depth === 1) {
|
|
1452
1462
|
hasHiddenReplies = true
|
|
@@ -1466,6 +1476,7 @@ export class Views {
|
|
|
1466
1476
|
below,
|
|
1467
1477
|
depth: depth + 1,
|
|
1468
1478
|
branchingFactor,
|
|
1479
|
+
prioritizeFollowedUsers,
|
|
1469
1480
|
},
|
|
1470
1481
|
state,
|
|
1471
1482
|
)
|
|
@@ -1482,6 +1493,7 @@ export class Views {
|
|
|
1482
1493
|
repliesAllowance,
|
|
1483
1494
|
uri,
|
|
1484
1495
|
}),
|
|
1496
|
+
tags: post.tags,
|
|
1485
1497
|
hasOPLike: !!state.threadContexts?.get(postView.uri)?.like,
|
|
1486
1498
|
parent: undefined,
|
|
1487
1499
|
replies: nestedReplies,
|
|
@@ -1520,11 +1532,13 @@ export class Views {
|
|
|
1520
1532
|
uri,
|
|
1521
1533
|
depth,
|
|
1522
1534
|
value: {
|
|
1523
|
-
$type: 'app.bsky.unspecced.
|
|
1535
|
+
$type: 'app.bsky.unspecced.defs#threadItemPost',
|
|
1524
1536
|
post: postView,
|
|
1525
1537
|
moreParents: moreParents ?? false,
|
|
1526
1538
|
moreReplies,
|
|
1527
1539
|
opThread: isOPThread,
|
|
1540
|
+
hiddenByThreadgate: false, // Hidden posts are handled by threadHiddenV2
|
|
1541
|
+
mutedByViewer: false, // Hidden posts are handled by threadHiddenV2
|
|
1528
1542
|
},
|
|
1529
1543
|
}
|
|
1530
1544
|
}
|
|
@@ -1540,7 +1554,7 @@ export class Views {
|
|
|
1540
1554
|
uri,
|
|
1541
1555
|
depth,
|
|
1542
1556
|
value: {
|
|
1543
|
-
$type: 'app.bsky.unspecced.
|
|
1557
|
+
$type: 'app.bsky.unspecced.defs#threadItemNoUnauthenticated',
|
|
1544
1558
|
},
|
|
1545
1559
|
}
|
|
1546
1560
|
}
|
|
@@ -1556,7 +1570,7 @@ export class Views {
|
|
|
1556
1570
|
uri,
|
|
1557
1571
|
depth,
|
|
1558
1572
|
value: {
|
|
1559
|
-
$type: 'app.bsky.unspecced.
|
|
1573
|
+
$type: 'app.bsky.unspecced.defs#threadItemNotFound',
|
|
1560
1574
|
},
|
|
1561
1575
|
}
|
|
1562
1576
|
}
|
|
@@ -1576,7 +1590,7 @@ export class Views {
|
|
|
1576
1590
|
uri,
|
|
1577
1591
|
depth,
|
|
1578
1592
|
value: {
|
|
1579
|
-
$type: 'app.bsky.unspecced.
|
|
1593
|
+
$type: 'app.bsky.unspecced.defs#threadItemBlocked',
|
|
1580
1594
|
author: {
|
|
1581
1595
|
did: authorDid,
|
|
1582
1596
|
viewer: this.blockedProfileViewer(authorDid, state),
|
|
@@ -1591,9 +1605,11 @@ export class Views {
|
|
|
1591
1605
|
{
|
|
1592
1606
|
below,
|
|
1593
1607
|
branchingFactor,
|
|
1608
|
+
prioritizeFollowedUsers,
|
|
1594
1609
|
}: {
|
|
1595
1610
|
below: number
|
|
1596
1611
|
branchingFactor: number
|
|
1612
|
+
prioritizeFollowedUsers: boolean
|
|
1597
1613
|
},
|
|
1598
1614
|
): ThreadHiddenItem[] {
|
|
1599
1615
|
const { anchor: anchorUri, uris } = skeleton
|
|
@@ -1628,6 +1644,7 @@ export class Views {
|
|
|
1628
1644
|
childrenByParentUri,
|
|
1629
1645
|
below,
|
|
1630
1646
|
depth: 1,
|
|
1647
|
+
prioritizeFollowedUsers,
|
|
1631
1648
|
},
|
|
1632
1649
|
state,
|
|
1633
1650
|
),
|
|
@@ -1638,7 +1655,8 @@ export class Views {
|
|
|
1638
1655
|
branchingFactor,
|
|
1639
1656
|
prioritizeFollowedUsers: false,
|
|
1640
1657
|
viewer: state.ctx?.viewer ?? null,
|
|
1641
|
-
|
|
1658
|
+
threadTagsBumpDown: this.threadTagsBumpDown,
|
|
1659
|
+
threadTagsHide: this.threadTagsHide,
|
|
1642
1660
|
})
|
|
1643
1661
|
}
|
|
1644
1662
|
|
|
@@ -1649,12 +1667,14 @@ export class Views {
|
|
|
1649
1667
|
childrenByParentUri,
|
|
1650
1668
|
below,
|
|
1651
1669
|
depth,
|
|
1670
|
+
prioritizeFollowedUsers,
|
|
1652
1671
|
}: {
|
|
1653
1672
|
parentUri: string
|
|
1654
1673
|
rootUri: string
|
|
1655
1674
|
childrenByParentUri: Record<string, string[]>
|
|
1656
1675
|
below: number
|
|
1657
1676
|
depth: number
|
|
1677
|
+
prioritizeFollowedUsers: boolean
|
|
1658
1678
|
},
|
|
1659
1679
|
state: HydrationState,
|
|
1660
1680
|
): ThreadHiddenPostNode[] | undefined {
|
|
@@ -1665,23 +1685,23 @@ export class Views {
|
|
|
1665
1685
|
|
|
1666
1686
|
const childrenUris = childrenByParentUri[parentUri] ?? []
|
|
1667
1687
|
return mapDefined(childrenUris, (uri) => {
|
|
1668
|
-
const replyInclusion = this.checkThreadV2ReplyInclusion(
|
|
1688
|
+
const replyInclusion = this.checkThreadV2ReplyInclusion({
|
|
1669
1689
|
uri,
|
|
1670
1690
|
rootUri,
|
|
1671
1691
|
state,
|
|
1672
|
-
)
|
|
1692
|
+
})
|
|
1673
1693
|
if (!replyInclusion) {
|
|
1674
1694
|
return undefined
|
|
1675
1695
|
}
|
|
1676
|
-
const { postView } = replyInclusion
|
|
1696
|
+
const { post, postView } = replyInclusion
|
|
1677
1697
|
|
|
1678
1698
|
// Hidden.
|
|
1679
|
-
const { hiddenByThreadgate, mutedByViewer } =
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
if (
|
|
1699
|
+
const { isHidden, hiddenByThreadgate, mutedByViewer } =
|
|
1700
|
+
this.isHiddenThreadPost(
|
|
1701
|
+
{ post, postView, rootUri, prioritizeFollowedUsers, uri },
|
|
1702
|
+
state,
|
|
1703
|
+
)
|
|
1704
|
+
if (isHidden) {
|
|
1685
1705
|
// Only show hidden anchor replies, not all hidden.
|
|
1686
1706
|
if (depth > 1) {
|
|
1687
1707
|
return undefined
|
|
@@ -1699,23 +1719,23 @@ export class Views {
|
|
|
1699
1719
|
childrenByParentUri,
|
|
1700
1720
|
below,
|
|
1701
1721
|
depth: depth + 1,
|
|
1722
|
+
prioritizeFollowedUsers,
|
|
1702
1723
|
},
|
|
1703
1724
|
state,
|
|
1704
1725
|
)
|
|
1705
1726
|
|
|
1706
|
-
const item = this.threadHiddenV2ItemPost(
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
state,
|
|
1714
|
-
)
|
|
1727
|
+
const item = this.threadHiddenV2ItemPost({
|
|
1728
|
+
depth,
|
|
1729
|
+
hiddenByThreadgate,
|
|
1730
|
+
mutedByViewer,
|
|
1731
|
+
postView,
|
|
1732
|
+
uri,
|
|
1733
|
+
})
|
|
1715
1734
|
|
|
1716
1735
|
const tree: ThreadHiddenPostNode = {
|
|
1717
1736
|
type: 'hiddenPost',
|
|
1718
1737
|
item: item,
|
|
1738
|
+
tags: post.tags,
|
|
1719
1739
|
replies,
|
|
1720
1740
|
}
|
|
1721
1741
|
|
|
@@ -1739,42 +1759,47 @@ export class Views {
|
|
|
1739
1759
|
}
|
|
1740
1760
|
}
|
|
1741
1761
|
|
|
1742
|
-
private threadHiddenV2ItemPost(
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
): ThreadHiddenPostNode['item'] {
|
|
1756
|
-
const { hiddenByThreadgate, mutedByViewer } = this.isHiddenThreadPost(
|
|
1757
|
-
{ rootUri, uri },
|
|
1758
|
-
state,
|
|
1759
|
-
)
|
|
1760
|
-
|
|
1762
|
+
private threadHiddenV2ItemPost({
|
|
1763
|
+
depth,
|
|
1764
|
+
hiddenByThreadgate,
|
|
1765
|
+
mutedByViewer,
|
|
1766
|
+
postView,
|
|
1767
|
+
uri,
|
|
1768
|
+
}: {
|
|
1769
|
+
depth: number
|
|
1770
|
+
hiddenByThreadgate: boolean
|
|
1771
|
+
mutedByViewer: boolean
|
|
1772
|
+
postView: PostView
|
|
1773
|
+
uri: string
|
|
1774
|
+
}): ThreadHiddenItemValuePost {
|
|
1761
1775
|
const base = this.threadHiddenV2ItemPostAnchor({ depth, uri })
|
|
1762
1776
|
return {
|
|
1763
1777
|
...base,
|
|
1764
1778
|
value: {
|
|
1765
|
-
$type: 'app.bsky.unspecced.
|
|
1779
|
+
$type: 'app.bsky.unspecced.defs#threadItemPost',
|
|
1766
1780
|
post: postView,
|
|
1767
1781
|
hiddenByThreadgate,
|
|
1768
1782
|
mutedByViewer,
|
|
1783
|
+
moreParents: false, // Hidden replies don't have parents.
|
|
1784
|
+
moreReplies: 0, // Hidden replies don't have replies hydrated.
|
|
1785
|
+
opThread: false, // Hidden replies don't contain OP threads.
|
|
1769
1786
|
},
|
|
1770
1787
|
}
|
|
1771
1788
|
}
|
|
1772
1789
|
|
|
1773
|
-
private checkThreadV2ReplyInclusion(
|
|
1774
|
-
uri
|
|
1775
|
-
rootUri
|
|
1776
|
-
state
|
|
1777
|
-
|
|
1790
|
+
private checkThreadV2ReplyInclusion({
|
|
1791
|
+
uri,
|
|
1792
|
+
rootUri,
|
|
1793
|
+
state,
|
|
1794
|
+
}: {
|
|
1795
|
+
uri: string
|
|
1796
|
+
rootUri: string
|
|
1797
|
+
state: HydrationState
|
|
1798
|
+
}): {
|
|
1799
|
+
authorDid: string
|
|
1800
|
+
post: Post
|
|
1801
|
+
postView: PostView
|
|
1802
|
+
} | null {
|
|
1778
1803
|
// Not found.
|
|
1779
1804
|
const post = state.posts?.get(uri)
|
|
1780
1805
|
if (post?.violatesThreadGate) {
|
|
@@ -1806,24 +1831,41 @@ export class Views {
|
|
|
1806
1831
|
return null
|
|
1807
1832
|
}
|
|
1808
1833
|
|
|
1809
|
-
return { authorDid, postView }
|
|
1834
|
+
return { authorDid, post, postView }
|
|
1810
1835
|
}
|
|
1811
1836
|
|
|
1812
1837
|
private isHiddenThreadPost(
|
|
1813
1838
|
{
|
|
1839
|
+
post,
|
|
1840
|
+
postView,
|
|
1841
|
+
prioritizeFollowedUsers,
|
|
1814
1842
|
rootUri,
|
|
1815
1843
|
uri,
|
|
1816
1844
|
}: {
|
|
1845
|
+
post: Post
|
|
1846
|
+
postView: PostView
|
|
1847
|
+
prioritizeFollowedUsers: boolean
|
|
1817
1848
|
rootUri: string
|
|
1818
1849
|
uri: string
|
|
1819
1850
|
},
|
|
1820
1851
|
state: HydrationState,
|
|
1821
1852
|
): {
|
|
1853
|
+
isHidden: boolean
|
|
1854
|
+
hiddenByTag: boolean
|
|
1822
1855
|
hiddenByThreadgate: boolean
|
|
1823
1856
|
mutedByViewer: boolean
|
|
1824
1857
|
} {
|
|
1858
|
+
const opDid = creatorFromUri(rootUri)
|
|
1825
1859
|
const authorDid = creatorFromUri(uri)
|
|
1826
1860
|
|
|
1861
|
+
const showBecauseFollowing =
|
|
1862
|
+
prioritizeFollowedUsers && !!postView.author.viewer?.following
|
|
1863
|
+
const hiddenByTag =
|
|
1864
|
+
authorDid !== opDid &&
|
|
1865
|
+
authorDid !== state.ctx?.viewer &&
|
|
1866
|
+
!showBecauseFollowing &&
|
|
1867
|
+
this.threadTagsHide.some((t) => post.tags.has(t))
|
|
1868
|
+
|
|
1827
1869
|
const hiddenByThreadgate =
|
|
1828
1870
|
state.ctx?.viewer !== authorDid &&
|
|
1829
1871
|
this.replyIsHiddenByThreadgate(uri, rootUri, state)
|
|
@@ -1831,6 +1873,8 @@ export class Views {
|
|
|
1831
1873
|
const mutedByViewer = this.viewerMuteExists(authorDid, state)
|
|
1832
1874
|
|
|
1833
1875
|
return {
|
|
1876
|
+
isHidden: hiddenByTag || hiddenByThreadgate || mutedByViewer,
|
|
1877
|
+
hiddenByTag,
|
|
1834
1878
|
hiddenByThreadgate,
|
|
1835
1879
|
mutedByViewer,
|
|
1836
1880
|
}
|
package/src/views/threads-v2.ts
CHANGED
|
@@ -2,17 +2,15 @@ import { asPredicate } from '@atproto/api'
|
|
|
2
2
|
import { HydrateCtx } from '../hydration/hydrator'
|
|
3
3
|
import { validateRecord as validatePostRecord } from '../lexicon/types/app/bsky/feed/post'
|
|
4
4
|
import {
|
|
5
|
-
ThreadHiddenItem,
|
|
6
|
-
ThreadHiddenItemPost,
|
|
7
|
-
isThreadHiddenItemPost,
|
|
8
|
-
} from '../lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2'
|
|
9
|
-
import {
|
|
10
|
-
QueryParams as GetPostThreadV2QueryParams,
|
|
11
|
-
ThreadItem,
|
|
12
5
|
ThreadItemBlocked,
|
|
13
6
|
ThreadItemNoUnauthenticated,
|
|
14
7
|
ThreadItemNotFound,
|
|
15
8
|
ThreadItemPost,
|
|
9
|
+
} from '../lexicon/types/app/bsky/unspecced/defs'
|
|
10
|
+
import { ThreadHiddenItem } from '../lexicon/types/app/bsky/unspecced/getPostThreadHiddenV2'
|
|
11
|
+
import {
|
|
12
|
+
QueryParams as GetPostThreadV2QueryParams,
|
|
13
|
+
ThreadItem,
|
|
16
14
|
} from '../lexicon/types/app/bsky/unspecced/getPostThreadV2'
|
|
17
15
|
import { $Typed } from '../lexicon/util'
|
|
18
16
|
|
|
@@ -59,6 +57,7 @@ type ThreadNotFoundNode = {
|
|
|
59
57
|
type ThreadPostNode = {
|
|
60
58
|
type: 'post'
|
|
61
59
|
item: ThreadItemValuePost
|
|
60
|
+
tags: Set<string>
|
|
62
61
|
hasOPLike: boolean
|
|
63
62
|
parent: ThreadTree | undefined
|
|
64
63
|
replies: ThreadTree[] | undefined
|
|
@@ -72,7 +71,7 @@ type ThreadHiddenItemValue<T extends ThreadHiddenItem['value']> = Omit<
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
export type ThreadHiddenItemValuePost = ThreadHiddenItemValue<
|
|
75
|
-
$Typed<
|
|
74
|
+
$Typed<ThreadItemPost>
|
|
76
75
|
>
|
|
77
76
|
|
|
78
77
|
// This is an intermediary type that doesn't map to the views.
|
|
@@ -87,6 +86,7 @@ export type ThreadHiddenAnchorPostNode = {
|
|
|
87
86
|
export type ThreadHiddenPostNode = {
|
|
88
87
|
type: 'hiddenPost'
|
|
89
88
|
item: ThreadHiddenItemValuePost
|
|
89
|
+
tags: Set<string>
|
|
90
90
|
replies: ThreadHiddenPostNode[] | undefined
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -107,21 +107,23 @@ export type ThreadTreeHidden = ThreadHiddenAnchorPostNode | ThreadHiddenPostNode
|
|
|
107
107
|
export type ThreadTree = ThreadTreeVisible | ThreadTreeHidden
|
|
108
108
|
|
|
109
109
|
/** This function mutates the tree parameter. */
|
|
110
|
-
export function sortTrimFlattenThreadTree
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
export function sortTrimFlattenThreadTree(
|
|
111
|
+
anchorTree: ThreadTree,
|
|
112
|
+
options: SortTrimFlattenOptions,
|
|
113
|
+
) {
|
|
113
114
|
const sortedAnchorTree = sortTrimThreadTree(anchorTree, options)
|
|
114
115
|
|
|
115
|
-
return flattenTree
|
|
116
|
+
return flattenTree(sortedAnchorTree)
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
type SortTrimFlattenOptions = {
|
|
119
120
|
branchingFactor: GetPostThreadV2QueryParams['branchingFactor']
|
|
120
|
-
fetchedAt: number
|
|
121
121
|
opDid: string
|
|
122
122
|
prioritizeFollowedUsers: boolean
|
|
123
123
|
sort?: GetPostThreadV2QueryParams['sort']
|
|
124
124
|
viewer: HydrateCtx['viewer']
|
|
125
|
+
threadTagsBumpDown: readonly string[]
|
|
126
|
+
threadTagsHide: readonly string[]
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
const isPostRecord = asPredicate(validatePostRecord)
|
|
@@ -136,15 +138,6 @@ function sortTrimThreadTree(
|
|
|
136
138
|
}
|
|
137
139
|
const node: ThreadNodeWithReplies = n
|
|
138
140
|
|
|
139
|
-
const {
|
|
140
|
-
branchingFactor,
|
|
141
|
-
fetchedAt,
|
|
142
|
-
opDid,
|
|
143
|
-
prioritizeFollowedUsers,
|
|
144
|
-
sort,
|
|
145
|
-
viewer,
|
|
146
|
-
} = opts
|
|
147
|
-
|
|
148
141
|
if (node.replies) {
|
|
149
142
|
node.replies.sort((an: ThreadTree, bn: ThreadTree) => {
|
|
150
143
|
if (!isPostNode(an)) {
|
|
@@ -168,19 +161,10 @@ function sortTrimThreadTree(
|
|
|
168
161
|
|
|
169
162
|
// Trimming: after sorting, apply branching factor to all levels of replies except the anchor direct replies.
|
|
170
163
|
if (node.item.depth !== 0) {
|
|
171
|
-
node.replies = node.replies.slice(0, branchingFactor)
|
|
164
|
+
node.replies = node.replies.slice(0, opts.branchingFactor)
|
|
172
165
|
}
|
|
173
166
|
|
|
174
|
-
node.replies.forEach((reply) =>
|
|
175
|
-
sortTrimThreadTree(reply, {
|
|
176
|
-
branchingFactor,
|
|
177
|
-
fetchedAt,
|
|
178
|
-
opDid,
|
|
179
|
-
prioritizeFollowedUsers,
|
|
180
|
-
sort,
|
|
181
|
-
viewer,
|
|
182
|
-
}),
|
|
183
|
-
)
|
|
167
|
+
node.replies.forEach((reply) => sortTrimThreadTree(reply, opts))
|
|
184
168
|
}
|
|
185
169
|
|
|
186
170
|
return node
|
|
@@ -191,19 +175,30 @@ function applyBumping(
|
|
|
191
175
|
bNode: ThreadMaybeHiddenPostNode,
|
|
192
176
|
opts: SortTrimFlattenOptions,
|
|
193
177
|
): number | null {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
178
|
+
if (!isPostNode(aNode)) {
|
|
179
|
+
return null
|
|
180
|
+
}
|
|
181
|
+
if (!isPostNode(bNode)) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const {
|
|
186
|
+
opDid,
|
|
187
|
+
prioritizeFollowedUsers,
|
|
188
|
+
viewer,
|
|
189
|
+
threadTagsBumpDown,
|
|
190
|
+
threadTagsHide,
|
|
191
|
+
} = opts
|
|
197
192
|
|
|
198
193
|
type BumpDirection = 'up' | 'down'
|
|
199
|
-
type BumpPredicateFn = (i:
|
|
194
|
+
type BumpPredicateFn = (i: ThreadMaybeHiddenPostNode) => boolean
|
|
200
195
|
|
|
201
196
|
const maybeBump = (
|
|
202
197
|
bump: BumpDirection,
|
|
203
198
|
predicateFn: BumpPredicateFn,
|
|
204
199
|
): number | null => {
|
|
205
|
-
const aPredicate = predicateFn(
|
|
206
|
-
const bPredicate = predicateFn(
|
|
200
|
+
const aPredicate = predicateFn(aNode)
|
|
201
|
+
const bPredicate = predicateFn(bNode)
|
|
207
202
|
if (aPredicate && bPredicate) {
|
|
208
203
|
return applySorting(aNode, bNode, opts)
|
|
209
204
|
} else if (aPredicate) {
|
|
@@ -218,21 +213,56 @@ function applyBumping(
|
|
|
218
213
|
// Bumps-up applied first make the item appear higher in the list than later bumps-up.
|
|
219
214
|
// Bumps-down applied first make the item appear lower in the list than later bumps-down.
|
|
220
215
|
const bumps: [BumpDirection, BumpPredicateFn][] = [
|
|
216
|
+
/*
|
|
217
|
+
General bumps.
|
|
218
|
+
*/
|
|
221
219
|
// OP replies.
|
|
222
|
-
['up', (i) => i.post.author.did === opDid],
|
|
220
|
+
['up', (i) => i.item.value.post.author.did === opDid],
|
|
223
221
|
// Viewer replies.
|
|
224
|
-
['up', (i) => i.post.author.did === viewer],
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
222
|
+
['up', (i) => i.item.value.post.author.did === viewer],
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
Bumps within visible replies.
|
|
226
|
+
*/
|
|
227
|
+
// Followers posts.
|
|
228
|
+
[
|
|
229
|
+
'up',
|
|
230
|
+
(i) =>
|
|
231
|
+
i.type === 'post' &&
|
|
232
|
+
prioritizeFollowedUsers &&
|
|
233
|
+
!!i.item.value.post.author.viewer?.following,
|
|
234
|
+
],
|
|
235
|
+
// Bump-down tags.
|
|
236
|
+
[
|
|
237
|
+
'down',
|
|
238
|
+
(i) => i.type === 'post' && threadTagsBumpDown.some((t) => i.tags.has(t)),
|
|
239
|
+
],
|
|
229
240
|
// Pushpin-only.
|
|
230
241
|
[
|
|
231
242
|
'down',
|
|
232
|
-
(i) =>
|
|
243
|
+
(i) =>
|
|
244
|
+
i.type === 'post' &&
|
|
245
|
+
isPostRecord(i.item.value.post.record) &&
|
|
246
|
+
i.item.value.post.record.text.trim() === '📌',
|
|
233
247
|
],
|
|
234
|
-
|
|
235
|
-
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
Bumps within hidden replies.
|
|
251
|
+
This determines the order of hidden replies:
|
|
252
|
+
1. hidden by threadgate.
|
|
253
|
+
2. hidden by tags.
|
|
254
|
+
3. muted by viewer.
|
|
255
|
+
*/
|
|
256
|
+
// Muted account by the viewer.
|
|
257
|
+
['down', (i) => i.type === 'hiddenPost' && i.item.value.mutedByViewer],
|
|
258
|
+
// Hidden by tags.
|
|
259
|
+
[
|
|
260
|
+
'down',
|
|
261
|
+
(i) =>
|
|
262
|
+
i.type === 'hiddenPost' && threadTagsHide.some((t) => i.tags.has(t)),
|
|
263
|
+
],
|
|
264
|
+
// Hidden by threadgate.
|
|
265
|
+
['down', (i) => i.type === 'hiddenPost' && i.item.value.hiddenByThreadgate],
|
|
236
266
|
]
|
|
237
267
|
|
|
238
268
|
for (const [bump, predicateFn] of bumps) {
|
|
@@ -279,13 +309,11 @@ function topSortValue(likeCount: number, hasOPLike: boolean): number {
|
|
|
279
309
|
return Math.log(3 + likeCount) * (hasOPLike ? 1.45 : 1.0)
|
|
280
310
|
}
|
|
281
311
|
|
|
282
|
-
function flattenTree
|
|
283
|
-
tree: ThreadTree,
|
|
284
|
-
): TItem[] {
|
|
312
|
+
function flattenTree(tree: ThreadTree) {
|
|
285
313
|
return [
|
|
286
314
|
// All parents above.
|
|
287
315
|
...Array.from(
|
|
288
|
-
flattenInDirection
|
|
316
|
+
flattenInDirection({
|
|
289
317
|
tree,
|
|
290
318
|
direction: 'up',
|
|
291
319
|
}),
|
|
@@ -293,11 +321,11 @@ function flattenTree<TItem extends ThreadItem | ThreadHiddenItem>(
|
|
|
293
321
|
|
|
294
322
|
// The anchor.
|
|
295
323
|
// In the case of hidden replies, the anchor item itself is undefined.
|
|
296
|
-
...(tree.item.value ?
|
|
324
|
+
...(tree.item.value ? [tree.item] : []),
|
|
297
325
|
|
|
298
326
|
// All replies below.
|
|
299
327
|
...Array.from(
|
|
300
|
-
flattenInDirection
|
|
328
|
+
flattenInDirection({
|
|
301
329
|
tree,
|
|
302
330
|
direction: 'down',
|
|
303
331
|
}),
|
|
@@ -305,18 +333,18 @@ function flattenTree<TItem extends ThreadItem | ThreadHiddenItem>(
|
|
|
305
333
|
]
|
|
306
334
|
}
|
|
307
335
|
|
|
308
|
-
function* flattenInDirection
|
|
336
|
+
function* flattenInDirection({
|
|
309
337
|
tree,
|
|
310
338
|
direction,
|
|
311
339
|
}: {
|
|
312
340
|
tree: ThreadTree
|
|
313
341
|
direction: 'up' | 'down'
|
|
314
|
-
})
|
|
342
|
+
}) {
|
|
315
343
|
if (tree.type === 'noUnauthenticated') {
|
|
316
344
|
if (direction === 'up') {
|
|
317
345
|
if (tree.parent) {
|
|
318
346
|
// Unfold all parents above.
|
|
319
|
-
yield* flattenTree
|
|
347
|
+
yield* flattenTree(tree.parent)
|
|
320
348
|
}
|
|
321
349
|
}
|
|
322
350
|
}
|
|
@@ -325,13 +353,13 @@ function* flattenInDirection<TItem extends ThreadItem | ThreadHiddenItem>({
|
|
|
325
353
|
if (direction === 'up') {
|
|
326
354
|
if (tree.parent) {
|
|
327
355
|
// Unfold all parents above.
|
|
328
|
-
yield* flattenTree
|
|
356
|
+
yield* flattenTree(tree.parent)
|
|
329
357
|
}
|
|
330
358
|
} else {
|
|
331
359
|
// Unfold all replies below.
|
|
332
360
|
if (tree.replies?.length) {
|
|
333
361
|
for (const reply of tree.replies) {
|
|
334
|
-
yield* flattenTree
|
|
362
|
+
yield* flattenTree(reply)
|
|
335
363
|
}
|
|
336
364
|
}
|
|
337
365
|
}
|
|
@@ -343,7 +371,7 @@ function* flattenInDirection<TItem extends ThreadItem | ThreadHiddenItem>({
|
|
|
343
371
|
// Unfold all replies below.
|
|
344
372
|
if (tree.replies?.length) {
|
|
345
373
|
for (const reply of tree.replies) {
|
|
346
|
-
yield* flattenTree
|
|
374
|
+
yield* flattenTree(reply)
|
|
347
375
|
}
|
|
348
376
|
}
|
|
349
377
|
}
|