@atproto/bsky 0.0.76 → 0.0.78

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.
Files changed (226) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/api/app/bsky/feed/getActorLikes.js +2 -2
  3. package/dist/api/app/bsky/feed/getActorLikes.js.map +1 -1
  4. package/dist/api/app/bsky/feed/getLikes.js +6 -6
  5. package/dist/api/app/bsky/feed/getLikes.js.map +1 -1
  6. package/dist/api/app/bsky/feed/getListFeed.d.ts.map +1 -1
  7. package/dist/api/app/bsky/feed/getListFeed.js +19 -3
  8. package/dist/api/app/bsky/feed/getListFeed.js.map +1 -1
  9. package/dist/api/app/bsky/feed/getPosts.js +4 -4
  10. package/dist/api/app/bsky/feed/getPosts.js.map +1 -1
  11. package/dist/api/app/bsky/feed/getQuotes.d.ts +4 -0
  12. package/dist/api/app/bsky/feed/getQuotes.d.ts.map +1 -0
  13. package/dist/api/app/bsky/feed/getQuotes.js +67 -0
  14. package/dist/api/app/bsky/feed/getQuotes.js.map +1 -0
  15. package/dist/api/app/bsky/feed/getRepostedBy.js +6 -6
  16. package/dist/api/app/bsky/feed/getRepostedBy.js.map +1 -1
  17. package/dist/api/app/bsky/feed/searchPosts.js +4 -4
  18. package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
  19. package/dist/api/app/bsky/graph/getFollowers.js +8 -8
  20. package/dist/api/app/bsky/graph/getFollowers.js.map +1 -1
  21. package/dist/api/app/bsky/graph/getList.js +7 -7
  22. package/dist/api/app/bsky/graph/getList.js.map +1 -1
  23. package/dist/api/app/bsky/notification/listNotifications.d.ts.map +1 -1
  24. package/dist/api/app/bsky/notification/listNotifications.js +29 -8
  25. package/dist/api/app/bsky/notification/listNotifications.js.map +1 -1
  26. package/dist/api/index.d.ts.map +1 -1
  27. package/dist/api/index.js +2 -0
  28. package/dist/api/index.js.map +1 -1
  29. package/dist/data-plane/server/db/database-schema.d.ts +4 -2
  30. package/dist/data-plane/server/db/database-schema.d.ts.map +1 -1
  31. package/dist/data-plane/server/db/migrations/20240723T220700077Z-quotes-post-aggs.d.ts +4 -0
  32. package/dist/data-plane/server/db/migrations/20240723T220700077Z-quotes-post-aggs.d.ts.map +1 -0
  33. package/dist/data-plane/server/db/migrations/20240723T220700077Z-quotes-post-aggs.js +15 -0
  34. package/dist/data-plane/server/db/migrations/20240723T220700077Z-quotes-post-aggs.js.map +1 -0
  35. package/dist/data-plane/server/db/migrations/20240723T220703655Z-quotes.d.ts +4 -0
  36. package/dist/data-plane/server/db/migrations/20240723T220703655Z-quotes.d.ts.map +1 -0
  37. package/dist/data-plane/server/db/migrations/20240723T220703655Z-quotes.js +30 -0
  38. package/dist/data-plane/server/db/migrations/20240723T220703655Z-quotes.js.map +1 -0
  39. package/dist/data-plane/server/db/migrations/20240801T193939827Z-post-gate.d.ts +4 -0
  40. package/dist/data-plane/server/db/migrations/20240801T193939827Z-post-gate.d.ts.map +1 -0
  41. package/dist/data-plane/server/db/migrations/20240801T193939827Z-post-gate.js +20 -0
  42. package/dist/data-plane/server/db/migrations/20240801T193939827Z-post-gate.js.map +1 -0
  43. package/dist/data-plane/server/db/migrations/20240808T224251220Z-post-gate-flags.d.ts +4 -0
  44. package/dist/data-plane/server/db/migrations/20240808T224251220Z-post-gate-flags.d.ts.map +1 -0
  45. package/dist/data-plane/server/db/migrations/20240808T224251220Z-post-gate-flags.js +28 -0
  46. package/dist/data-plane/server/db/migrations/20240808T224251220Z-post-gate-flags.js.map +1 -0
  47. package/dist/data-plane/server/db/migrations/index.d.ts +4 -0
  48. package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
  49. package/dist/data-plane/server/db/migrations/index.js +5 -1
  50. package/dist/data-plane/server/db/migrations/index.js.map +1 -1
  51. package/dist/data-plane/server/db/tables/post-agg.d.ts +1 -0
  52. package/dist/data-plane/server/db/tables/post-agg.d.ts.map +1 -1
  53. package/dist/data-plane/server/db/tables/post-gate.d.ts +14 -0
  54. package/dist/data-plane/server/db/tables/post-gate.d.ts.map +1 -0
  55. package/dist/data-plane/server/db/tables/post-gate.js +4 -0
  56. package/dist/data-plane/server/db/tables/post-gate.js.map +1 -0
  57. package/dist/data-plane/server/db/tables/post.d.ts +3 -0
  58. package/dist/data-plane/server/db/tables/post.d.ts.map +1 -1
  59. package/dist/data-plane/server/db/tables/quote.d.ts +16 -0
  60. package/dist/data-plane/server/db/tables/quote.d.ts.map +1 -0
  61. package/dist/data-plane/server/db/tables/quote.js +4 -0
  62. package/dist/data-plane/server/db/tables/quote.js.map +1 -0
  63. package/dist/data-plane/server/indexing/index.d.ts +2 -0
  64. package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
  65. package/dist/data-plane/server/indexing/index.js +6 -0
  66. package/dist/data-plane/server/indexing/index.js.map +1 -1
  67. package/dist/data-plane/server/indexing/plugins/post-gate.d.ts +10 -0
  68. package/dist/data-plane/server/indexing/plugins/post-gate.d.ts.map +1 -0
  69. package/dist/data-plane/server/indexing/plugins/post-gate.js +101 -0
  70. package/dist/data-plane/server/indexing/plugins/post-gate.js.map +1 -0
  71. package/dist/data-plane/server/indexing/plugins/post.d.ts +2 -0
  72. package/dist/data-plane/server/indexing/plugins/post.d.ts.map +1 -1
  73. package/dist/data-plane/server/indexing/plugins/post.js +122 -15
  74. package/dist/data-plane/server/indexing/plugins/post.js.map +1 -1
  75. package/dist/data-plane/server/indexing/plugins/thread-gate.d.ts.map +1 -1
  76. package/dist/data-plane/server/indexing/plugins/thread-gate.js +12 -0
  77. package/dist/data-plane/server/indexing/plugins/thread-gate.js.map +1 -1
  78. package/dist/data-plane/server/routes/index.d.ts.map +1 -1
  79. package/dist/data-plane/server/routes/index.js +2 -0
  80. package/dist/data-plane/server/routes/index.js.map +1 -1
  81. package/dist/data-plane/server/routes/interactions.d.ts.map +1 -1
  82. package/dist/data-plane/server/routes/interactions.js +2 -1
  83. package/dist/data-plane/server/routes/interactions.js.map +1 -1
  84. package/dist/data-plane/server/routes/quotes.d.ts +6 -0
  85. package/dist/data-plane/server/routes/quotes.d.ts.map +1 -0
  86. package/dist/data-plane/server/routes/quotes.js +27 -0
  87. package/dist/data-plane/server/routes/quotes.js.map +1 -0
  88. package/dist/data-plane/server/routes/records.d.ts.map +1 -1
  89. package/dist/data-plane/server/routes/records.js +11 -1
  90. package/dist/data-plane/server/routes/records.js.map +1 -1
  91. package/dist/data-plane/server/util.d.ts +6 -7
  92. package/dist/data-plane/server/util.d.ts.map +1 -1
  93. package/dist/data-plane/server/util.js +1 -9
  94. package/dist/data-plane/server/util.js.map +1 -1
  95. package/dist/hydration/feed.d.ts +10 -0
  96. package/dist/hydration/feed.d.ts.map +1 -1
  97. package/dist/hydration/feed.js +31 -7
  98. package/dist/hydration/feed.js.map +1 -1
  99. package/dist/hydration/hydrator.d.ts +4 -2
  100. package/dist/hydration/hydrator.d.ts.map +1 -1
  101. package/dist/hydration/hydrator.js +89 -34
  102. package/dist/hydration/hydrator.js.map +1 -1
  103. package/dist/hydration/util.d.ts +0 -1
  104. package/dist/hydration/util.d.ts.map +1 -1
  105. package/dist/hydration/util.js +1 -5
  106. package/dist/hydration/util.js.map +1 -1
  107. package/dist/lexicon/index.d.ts +2 -0
  108. package/dist/lexicon/index.d.ts.map +1 -1
  109. package/dist/lexicon/index.js +4 -0
  110. package/dist/lexicon/index.js.map +1 -1
  111. package/dist/lexicon/lexicons.d.ts +144 -0
  112. package/dist/lexicon/lexicons.d.ts.map +1 -1
  113. package/dist/lexicon/lexicons.js +146 -1
  114. package/dist/lexicon/lexicons.js.map +1 -1
  115. package/dist/lexicon/types/app/bsky/embed/record.d.ts +9 -1
  116. package/dist/lexicon/types/app/bsky/embed/record.d.ts.map +1 -1
  117. package/dist/lexicon/types/app/bsky/embed/record.js +11 -1
  118. package/dist/lexicon/types/app/bsky/embed/record.js.map +1 -1
  119. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +2 -0
  120. package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
  121. package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
  122. package/dist/lexicon/types/app/bsky/feed/getQuotes.d.ts +44 -0
  123. package/dist/lexicon/types/app/bsky/feed/getQuotes.d.ts.map +1 -0
  124. package/dist/lexicon/types/app/bsky/feed/getQuotes.js +3 -0
  125. package/dist/lexicon/types/app/bsky/feed/getQuotes.js.map +1 -0
  126. package/dist/lexicon/types/app/bsky/feed/postgate.d.ts +25 -0
  127. package/dist/lexicon/types/app/bsky/feed/postgate.d.ts.map +1 -0
  128. package/dist/lexicon/types/app/bsky/feed/postgate.js +27 -0
  129. package/dist/lexicon/types/app/bsky/feed/postgate.js.map +1 -0
  130. package/dist/lexicon/types/app/bsky/feed/threadgate.d.ts +2 -0
  131. package/dist/lexicon/types/app/bsky/feed/threadgate.d.ts.map +1 -1
  132. package/dist/lexicon/types/app/bsky/feed/threadgate.js.map +1 -1
  133. package/dist/proto/bsky_connect.d.ts +30 -1
  134. package/dist/proto/bsky_connect.d.ts.map +1 -1
  135. package/dist/proto/bsky_connect.js +29 -0
  136. package/dist/proto/bsky_connect.js.map +1 -1
  137. package/dist/proto/bsky_pb.d.ts +135 -1
  138. package/dist/proto/bsky_pb.d.ts.map +1 -1
  139. package/dist/proto/bsky_pb.js +425 -5
  140. package/dist/proto/bsky_pb.js.map +1 -1
  141. package/dist/util/uris.d.ts +12 -0
  142. package/dist/util/uris.d.ts.map +1 -0
  143. package/dist/util/uris.js +34 -0
  144. package/dist/util/uris.js.map +1 -0
  145. package/dist/views/index.d.ts +8 -2
  146. package/dist/views/index.d.ts.map +1 -1
  147. package/dist/views/index.js +84 -39
  148. package/dist/views/index.js.map +1 -1
  149. package/dist/views/types.d.ts +1 -1
  150. package/dist/views/types.d.ts.map +1 -1
  151. package/dist/views/types.js.map +1 -1
  152. package/dist/views/util.d.ts +11 -1
  153. package/dist/views/util.d.ts.map +1 -1
  154. package/dist/views/util.js +19 -8
  155. package/dist/views/util.js.map +1 -1
  156. package/package.json +4 -4
  157. package/proto/bsky.proto +42 -2
  158. package/src/api/app/bsky/feed/getActorLikes.ts +1 -1
  159. package/src/api/app/bsky/feed/getLikes.ts +1 -1
  160. package/src/api/app/bsky/feed/getListFeed.ts +30 -3
  161. package/src/api/app/bsky/feed/getPosts.ts +1 -1
  162. package/src/api/app/bsky/feed/getQuotes.ts +108 -0
  163. package/src/api/app/bsky/feed/getRepostedBy.ts +1 -1
  164. package/src/api/app/bsky/feed/searchPosts.ts +1 -1
  165. package/src/api/app/bsky/graph/getFollowers.ts +1 -1
  166. package/src/api/app/bsky/graph/getList.ts +5 -5
  167. package/src/api/app/bsky/notification/listNotifications.ts +32 -6
  168. package/src/api/index.ts +2 -0
  169. package/src/data-plane/server/db/database-schema.ts +7 -3
  170. package/src/data-plane/server/db/migrations/20240723T220700077Z-quotes-post-aggs.ts +12 -0
  171. package/src/data-plane/server/db/migrations/20240723T220703655Z-quotes.ts +28 -0
  172. package/src/data-plane/server/db/migrations/20240801T193939827Z-post-gate.ts +17 -0
  173. package/src/data-plane/server/db/migrations/20240808T224251220Z-post-gate-flags.ts +25 -0
  174. package/src/data-plane/server/db/migrations/index.ts +4 -0
  175. package/src/data-plane/server/db/tables/post-agg.ts +1 -0
  176. package/src/data-plane/server/db/tables/post-gate.ts +12 -0
  177. package/src/data-plane/server/db/tables/post.ts +3 -0
  178. package/src/data-plane/server/db/tables/quote.ts +15 -0
  179. package/src/data-plane/server/indexing/index.ts +7 -0
  180. package/src/data-plane/server/indexing/plugins/post-gate.ts +104 -0
  181. package/src/data-plane/server/indexing/plugins/post.ts +151 -16
  182. package/src/data-plane/server/indexing/plugins/thread-gate.ts +12 -0
  183. package/src/data-plane/server/routes/index.ts +2 -0
  184. package/src/data-plane/server/routes/interactions.ts +2 -1
  185. package/src/data-plane/server/routes/quotes.ts +32 -0
  186. package/src/data-plane/server/routes/records.ts +11 -1
  187. package/src/data-plane/server/util.ts +0 -8
  188. package/src/hydration/feed.ts +58 -12
  189. package/src/hydration/hydrator.ts +94 -22
  190. package/src/hydration/util.ts +0 -4
  191. package/src/lexicon/index.ts +12 -0
  192. package/src/lexicon/lexicons.ts +149 -1
  193. package/src/lexicon/types/app/bsky/embed/record.ts +20 -0
  194. package/src/lexicon/types/app/bsky/feed/defs.ts +2 -0
  195. package/src/lexicon/types/app/bsky/feed/getQuotes.ts +54 -0
  196. package/src/lexicon/types/app/bsky/feed/postgate.ts +47 -0
  197. package/src/lexicon/types/app/bsky/feed/threadgate.ts +2 -0
  198. package/src/proto/bsky_connect.ts +35 -0
  199. package/src/proto/bsky_pb.ts +424 -1
  200. package/src/util/uris.ts +31 -0
  201. package/src/views/index.ts +91 -35
  202. package/src/views/types.ts +1 -0
  203. package/src/views/util.ts +37 -7
  204. package/tests/__snapshots__/feed-generation.test.ts.snap +42 -0
  205. package/tests/data-plane/__snapshots__/indexing.test.ts.snap +20 -0
  206. package/tests/data-plane/indexing.test.ts +1 -0
  207. package/tests/postgates.test.ts +186 -0
  208. package/tests/seed/feed-hidden-replies.ts +62 -0
  209. package/tests/seed/postgates.ts +56 -0
  210. package/tests/views/__snapshots__/author-feed.test.ts.snap +65 -0
  211. package/tests/views/__snapshots__/block-lists.test.ts.snap +7 -0
  212. package/tests/views/__snapshots__/blocks.test.ts.snap +11 -0
  213. package/tests/views/__snapshots__/list-feed.test.ts.snap +24 -0
  214. package/tests/views/__snapshots__/lists.test.ts.snap +185 -1
  215. package/tests/views/__snapshots__/mute-lists.test.ts.snap +8 -0
  216. package/tests/views/__snapshots__/mutes.test.ts.snap +6 -0
  217. package/tests/views/__snapshots__/posts.test.ts.snap +15 -0
  218. package/tests/views/__snapshots__/quotes.test.ts.snap +402 -0
  219. package/tests/views/__snapshots__/thread.test.ts.snap +50 -0
  220. package/tests/views/__snapshots__/timeline.test.ts.snap +191 -0
  221. package/tests/views/author-feed.test.ts +3 -9
  222. package/tests/views/feed-hidden-replies.test.ts +246 -0
  223. package/tests/views/feed-view-post.test.ts +501 -0
  224. package/tests/views/list-feed.test.ts +12 -0
  225. package/tests/views/lists.test.ts +83 -18
  226. package/tests/views/quotes.test.ts +105 -0
@@ -0,0 +1,28 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('quote')
6
+ .addColumn('uri', 'varchar', (col) => col.primaryKey())
7
+ .addColumn('cid', 'varchar', (col) => col.notNull())
8
+ .addColumn('subject', 'varchar', (col) => col.notNull())
9
+ .addColumn('subjectCid', 'varchar', (col) => col.notNull())
10
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
11
+ .addColumn('indexedAt', 'varchar', (col) => col.notNull())
12
+ .addColumn('sortAt', 'varchar', (col) =>
13
+ col
14
+ .generatedAlwaysAs(sql`least("createdAt", "indexedAt")`)
15
+ .stored()
16
+ .notNull(),
17
+ )
18
+ .execute()
19
+ await db.schema
20
+ .createIndex('quote_subject_cursor_idx')
21
+ .on('quote')
22
+ .columns(['subject', 'sortAt', 'cid'])
23
+ .execute()
24
+ }
25
+
26
+ export async function down(db: Kysely<unknown>): Promise<void> {
27
+ await db.schema.dropTable('quote').execute()
28
+ }
@@ -0,0 +1,17 @@
1
+ import { Kysely } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('post_gate')
6
+ .addColumn('uri', 'varchar', (col) => col.primaryKey())
7
+ .addColumn('cid', 'varchar', (col) => col.notNull())
8
+ .addColumn('creator', 'varchar', (col) => col.notNull())
9
+ .addColumn('postUri', 'varchar', (col) => col.notNull().unique())
10
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
11
+ .addColumn('indexedAt', 'varchar', (col) => col.notNull())
12
+ .execute()
13
+ }
14
+
15
+ export async function down(db: Kysely<unknown>): Promise<void> {
16
+ await db.schema.dropTable('post_gate').execute()
17
+ }
@@ -0,0 +1,25 @@
1
+ import { Kysely } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .alterTable('post')
6
+ .addColumn('violatesEmbeddingRules', 'boolean')
7
+ .execute()
8
+ await db.schema
9
+ .alterTable('post')
10
+ .addColumn('hasThreadGate', 'boolean')
11
+ .execute()
12
+ await db.schema
13
+ .alterTable('post')
14
+ .addColumn('hasPostGate', 'boolean')
15
+ .execute()
16
+ }
17
+
18
+ export async function down(db: Kysely<unknown>): Promise<void> {
19
+ await db.schema
20
+ .alterTable('post')
21
+ .dropColumn('violatesEmbeddingRules')
22
+ .execute()
23
+ await db.schema.alterTable('post').dropColumn('hasThreadGate').execute()
24
+ await db.schema.alterTable('post').dropColumn('hasPostGate').execute()
25
+ }
@@ -38,3 +38,7 @@ export * as _20240530T170337073Z from './20240530T170337073Z-account-deactivatio
38
38
  export * as _20240606T171229898Z from './20240606T171229898Z-thread-mutes'
39
39
  export * as _20240606T222548219Z from './20240606T222548219Z-starter-packs'
40
40
  export * as _20240719T203853939Z from './20240719T203853939Z-priority-notifs'
41
+ export * as _20240723T220700077Z from './20240723T220700077Z-quotes-post-aggs'
42
+ export * as _20240723T220703655Z from './20240723T220703655Z-quotes'
43
+ export * as _20240801T193939827Z from './20240801T193939827Z-post-gate'
44
+ export * as _20240808T224251220Z from './20240808T224251220Z-post-gate-flags'
@@ -7,6 +7,7 @@ export interface PostAgg {
7
7
  likeCount: Generated<number>
8
8
  replyCount: Generated<number>
9
9
  repostCount: Generated<number>
10
+ quoteCount: Generated<number>
10
11
  }
11
12
 
12
13
  export type PartialDB = {
@@ -0,0 +1,12 @@
1
+ const tableName = 'post_gate'
2
+
3
+ export interface Postgate {
4
+ uri: string
5
+ cid: string
6
+ creator: string
7
+ postUri: string
8
+ createdAt: string
9
+ indexedAt: string
10
+ }
11
+
12
+ export type PartialDB = { [tableName]: Postgate }
@@ -15,6 +15,9 @@ export interface Post {
15
15
  tags: string[] | null
16
16
  invalidReplyRoot: boolean | null
17
17
  violatesThreadGate: boolean | null
18
+ violatesEmbeddingRules: boolean | null
19
+ hasThreadGate: boolean | null
20
+ hasPostGate: boolean | null
18
21
  createdAt: string
19
22
  indexedAt: string
20
23
  sortAt: GeneratedAlways<string>
@@ -0,0 +1,15 @@
1
+ import { GeneratedAlways } from 'kysely'
2
+
3
+ const tableName = 'quote'
4
+
5
+ export interface Quote {
6
+ uri: string
7
+ cid: string
8
+ subject: string
9
+ subjectCid: string
10
+ createdAt: string
11
+ indexedAt: string
12
+ sortAt: GeneratedAlways<string>
13
+ }
14
+
15
+ export type PartialDB = { [tableName]: Quote }
@@ -17,6 +17,7 @@ import { Database } from '../db'
17
17
  import { Actor } from '../db/tables/actor'
18
18
  import * as Post from './plugins/post'
19
19
  import * as Threadgate from './plugins/thread-gate'
20
+ import * as Postgate from './plugins/post-gate'
20
21
  import * as Like from './plugins/like'
21
22
  import * as Repost from './plugins/repost'
22
23
  import * as Follow from './plugins/follow'
@@ -38,6 +39,7 @@ export class IndexingService {
38
39
  records: {
39
40
  post: Post.PluginType
40
41
  threadGate: Threadgate.PluginType
42
+ postGate: Postgate.PluginType
41
43
  like: Like.PluginType
42
44
  repost: Repost.PluginType
43
45
  follow: Follow.PluginType
@@ -60,6 +62,7 @@ export class IndexingService {
60
62
  this.records = {
61
63
  post: Post.makePlugin(this.db, this.background),
62
64
  threadGate: Threadgate.makePlugin(this.db, this.background),
65
+ postGate: Postgate.makePlugin(this.db, this.background),
63
66
  like: Like.makePlugin(this.db, this.background),
64
67
  repost: Repost.makePlugin(this.db, this.background),
65
68
  follow: Follow.makePlugin(this.db, this.background),
@@ -365,6 +368,10 @@ export class IndexingService {
365
368
  .deleteFrom('thread_gate')
366
369
  .where('creator', '=', did)
367
370
  .execute()
371
+ await this.db.db
372
+ .deleteFrom('post_gate')
373
+ .where('creator', '=', did)
374
+ .execute()
368
375
  // notifications
369
376
  await this.db.db
370
377
  .deleteFrom('notification')
@@ -0,0 +1,104 @@
1
+ import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax'
2
+ import { InvalidRequestError } from '@atproto/xrpc-server'
3
+ import { CID } from 'multiformats/cid'
4
+ import * as Postgate from '../../../../lexicon/types/app/bsky/feed/postgate'
5
+ import * as lex from '../../../../lexicon/lexicons'
6
+ import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema'
7
+ import { Database } from '../../db'
8
+ import RecordProcessor from '../processor'
9
+ import { BackgroundQueue } from '../../background'
10
+
11
+ const lexId = lex.ids.AppBskyFeedPostgate
12
+ type IndexedGate = DatabaseSchemaType['post_gate']
13
+
14
+ const insertFn = async (
15
+ db: DatabaseSchema,
16
+ uri: AtUri,
17
+ cid: CID,
18
+ obj: Postgate.Record,
19
+ timestamp: string,
20
+ ): Promise<IndexedGate | null> => {
21
+ const postUri = new AtUri(obj.post)
22
+ if (postUri.host !== uri.host || postUri.rkey !== uri.rkey) {
23
+ throw new InvalidRequestError(
24
+ 'Creator and rkey of post gate does not match its post',
25
+ )
26
+ }
27
+ const inserted = await db
28
+ .insertInto('post_gate')
29
+ .values({
30
+ uri: uri.toString(),
31
+ cid: cid.toString(),
32
+ creator: uri.host,
33
+ postUri: obj.post,
34
+ createdAt: normalizeDatetimeAlways(obj.createdAt),
35
+ indexedAt: timestamp,
36
+ })
37
+ .onConflict((oc) => oc.doNothing())
38
+ .returningAll()
39
+ .executeTakeFirst()
40
+ await db
41
+ .updateTable('post')
42
+ .where('uri', '=', postUri.toString())
43
+ .set({ hasPostGate: true })
44
+ .executeTakeFirst()
45
+ return inserted || null
46
+ }
47
+
48
+ const findDuplicate = async (
49
+ db: DatabaseSchema,
50
+ _uri: AtUri,
51
+ obj: Postgate.Record,
52
+ ): Promise<AtUri | null> => {
53
+ const found = await db
54
+ .selectFrom('post_gate')
55
+ .where('postUri', '=', obj.post)
56
+ .selectAll()
57
+ .executeTakeFirst()
58
+ return found ? new AtUri(found.uri) : null
59
+ }
60
+
61
+ const notifsForInsert = () => {
62
+ return []
63
+ }
64
+
65
+ const deleteFn = async (
66
+ db: DatabaseSchema,
67
+ uri: AtUri,
68
+ ): Promise<IndexedGate | null> => {
69
+ const deleted = await db
70
+ .deleteFrom('post_gate')
71
+ .where('uri', '=', uri.toString())
72
+ .returningAll()
73
+ .executeTakeFirst()
74
+ if (deleted) {
75
+ await db
76
+ .updateTable('post')
77
+ .where('uri', '=', deleted.postUri)
78
+ .set({ hasPostGate: false })
79
+ .executeTakeFirst()
80
+ }
81
+ return deleted || null
82
+ }
83
+
84
+ const notifsForDelete = () => {
85
+ return { notifs: [], toDelete: [] }
86
+ }
87
+
88
+ export type PluginType = RecordProcessor<Postgate.Record, IndexedGate>
89
+
90
+ export const makePlugin = (
91
+ db: Database,
92
+ background: BackgroundQueue,
93
+ ): PluginType => {
94
+ return new RecordProcessor(db, background, {
95
+ lexId,
96
+ insertFn,
97
+ findDuplicate,
98
+ deleteFn,
99
+ notifsForInsert,
100
+ notifsForDelete,
101
+ })
102
+ }
103
+
104
+ export default makePlugin
@@ -7,6 +7,7 @@ import {
7
7
  ReplyRef,
8
8
  } from '../../../../lexicon/types/app/bsky/feed/post'
9
9
  import { Record as GateRecord } from '../../../../lexicon/types/app/bsky/feed/threadgate'
10
+ import { Record as PostgateRecord } from '../../../../lexicon/types/app/bsky/feed/postgate'
10
11
  import { isMain as isEmbedImage } from '../../../../lexicon/types/app/bsky/embed/images'
11
12
  import { isMain as isEmbedExternal } from '../../../../lexicon/types/app/bsky/embed/external'
12
13
  import { isMain as isEmbedRecord } from '../../../../lexicon/types/app/bsky/embed/record'
@@ -26,9 +27,14 @@ import {
26
27
  getDescendentsQb,
27
28
  invalidReplyRoot as checkInvalidReplyRoot,
28
29
  violatesThreadGate as checkViolatesThreadGate,
29
- postToThreadgateUri,
30
30
  } from '../../util'
31
31
  import { BackgroundQueue } from '../../background'
32
+ import { parsePostgate } from '../../../../views/util'
33
+ import {
34
+ postUriToThreadgateUri,
35
+ postUriToPostgateUri,
36
+ uriToDid,
37
+ } from '../../../../util/uris'
32
38
 
33
39
  type Notif = Insertable<Notification>
34
40
  type Post = Selectable<DatabaseSchemaType['post']>
@@ -52,6 +58,7 @@ type IndexedPost = {
52
58
  embeds?: (PostEmbedImage[] | PostEmbedExternal | PostEmbedRecord)[]
53
59
  ancestors?: PostAncestor[]
54
60
  descendents?: PostDescendent[]
61
+ threadgate?: GateRecord
55
62
  }
56
63
 
57
64
  const lexId = lex.ids.AppBskyFeedPost
@@ -168,6 +175,7 @@ const insertFn = async (
168
175
  await db.insertInto('post_embed_external').values(externalEmbed).execute()
169
176
  } else if (isEmbedRecord(postEmbed)) {
170
177
  const { record } = postEmbed
178
+ const embedUri = new AtUri(record.uri)
171
179
  const recordEmbed = {
172
180
  postUri: uri.toString(),
173
181
  embedUri: record.uri,
@@ -175,9 +183,59 @@ const insertFn = async (
175
183
  }
176
184
  embeds.push(recordEmbed)
177
185
  await db.insertInto('post_embed_record').values(recordEmbed).execute()
186
+
187
+ if (embedUri.collection === lex.ids.AppBskyFeedPost) {
188
+ const quote = {
189
+ uri: uri.toString(),
190
+ cid: cid.toString(),
191
+ subject: record.uri,
192
+ subjectCid: record.cid,
193
+ createdAt: normalizeDatetimeAlways(obj.createdAt),
194
+ indexedAt: timestamp,
195
+ }
196
+ await db
197
+ .insertInto('quote')
198
+ .values(quote)
199
+ .onConflict((oc) => oc.doNothing())
200
+ .returningAll()
201
+ .executeTakeFirst()
202
+
203
+ const quoteCountQb = db
204
+ .insertInto('post_agg')
205
+ .values({
206
+ uri: record.uri.toString(),
207
+ quoteCount: db
208
+ .selectFrom('quote')
209
+ .where('quote.subjectCid', '=', record.cid.toString())
210
+ .select(countAll.as('count')),
211
+ })
212
+ .onConflict((oc) =>
213
+ oc
214
+ .column('uri')
215
+ .doUpdateSet({ quoteCount: excluded(db, 'quoteCount') }),
216
+ )
217
+ await quoteCountQb.execute()
218
+
219
+ const { violatesEmbeddingRules } = await validatePostEmbed(
220
+ db,
221
+ embedUri.toString(),
222
+ uri.toString(),
223
+ )
224
+ Object.assign(insertedPost, {
225
+ violatesEmbeddingRules: violatesEmbeddingRules,
226
+ })
227
+ if (violatesEmbeddingRules) {
228
+ await db
229
+ .updateTable('post')
230
+ .where('uri', '=', insertedPost.uri)
231
+ .set({ violatesEmbeddingRules: violatesEmbeddingRules })
232
+ .executeTakeFirst()
233
+ }
234
+ }
178
235
  }
179
236
  }
180
237
 
238
+ const threadgate = await getThreadgateRecord(db, post.replyRoot || post.uri)
181
239
  const ancestors = await getAncestorsAndSelfQb(db, {
182
240
  uri: post.uri,
183
241
  parentHeight: REPLY_NOTIF_DEPTH,
@@ -200,6 +258,7 @@ const insertFn = async (
200
258
  embeds,
201
259
  ancestors,
202
260
  descendents,
261
+ threadgate,
203
262
  }
204
263
  }
205
264
 
@@ -228,19 +287,22 @@ const notifsForInsert = (obj: IndexedPost) => {
228
287
  })
229
288
  }
230
289
  }
231
- for (const embed of obj.embeds ?? []) {
232
- if ('embedUri' in embed) {
233
- const embedUri = new AtUri(embed.embedUri)
234
- if (embedUri.collection === lex.ids.AppBskyFeedPost) {
235
- maybeNotify({
236
- did: embedUri.host,
237
- reason: 'quote',
238
- reasonSubject: embedUri.toString(),
239
- author: obj.post.creator,
240
- recordUri: obj.post.uri,
241
- recordCid: obj.post.cid,
242
- sortAt: obj.post.sortAt,
243
- })
290
+
291
+ if (!obj.post.violatesEmbeddingRules) {
292
+ for (const embed of obj.embeds ?? []) {
293
+ if ('embedUri' in embed) {
294
+ const embedUri = new AtUri(embed.embedUri)
295
+ if (embedUri.collection === lex.ids.AppBskyFeedPost) {
296
+ maybeNotify({
297
+ did: embedUri.host,
298
+ reason: 'quote',
299
+ reasonSubject: embedUri.toString(),
300
+ author: obj.post.creator,
301
+ recordUri: obj.post.uri,
302
+ recordCid: obj.post.cid,
303
+ sortAt: obj.post.sortAt,
304
+ })
305
+ }
244
306
  }
245
307
  }
246
308
  }
@@ -250,6 +312,8 @@ const notifsForInsert = (obj: IndexedPost) => {
250
312
  return notifs
251
313
  }
252
314
 
315
+ const threadgateHiddenReplies = obj.threadgate?.hiddenReplies || []
316
+
253
317
  // reply notifications
254
318
 
255
319
  for (const ancestor of obj.ancestors ?? []) {
@@ -265,6 +329,8 @@ const notifsForInsert = (obj: IndexedPost) => {
265
329
  recordCid: obj.post.cid,
266
330
  sortAt: obj.post.sortAt,
267
331
  })
332
+ // found hidden reply, don't notify any higher ancestors
333
+ if (threadgateHiddenReplies.includes(ancestorUri.toString())) break
268
334
  }
269
335
  }
270
336
 
@@ -304,6 +370,7 @@ const deleteFn = async (
304
370
  .executeTakeFirst(),
305
371
  db.deleteFrom('feed_item').where('postUri', '=', uriStr).executeTakeFirst(),
306
372
  ])
373
+ await db.deleteFrom('quote').where('subject', '=', uriStr).execute()
307
374
  const deletedEmbeds: (
308
375
  | PostEmbedImage[]
309
376
  | PostEmbedExternal
@@ -333,7 +400,27 @@ const deleteFn = async (
333
400
  deletedEmbeds.push(deletedExternals)
334
401
  }
335
402
  if (deletedPosts) {
403
+ const embedUri = new AtUri(deletedPosts.embedUri)
336
404
  deletedEmbeds.push(deletedPosts)
405
+
406
+ if (embedUri.collection === lex.ids.AppBskyFeedPost) {
407
+ await db.deleteFrom('quote').where('uri', '=', uriStr).execute()
408
+ await db
409
+ .insertInto('post_agg')
410
+ .values({
411
+ uri: deletedPosts.embedUri,
412
+ quoteCount: db
413
+ .selectFrom('quote')
414
+ .where('quote.subjectCid', '=', deletedPosts.embedCid.toString())
415
+ .select(countAll.as('count')),
416
+ })
417
+ .onConflict((oc) =>
418
+ oc
419
+ .column('uri')
420
+ .doUpdateSet({ quoteCount: excluded(db, 'quoteCount') }),
421
+ )
422
+ .execute()
423
+ }
337
424
  }
338
425
  return deleted
339
426
  ? {
@@ -434,7 +521,7 @@ async function validateReply(
434
521
  const violatesThreadGate = await checkViolatesThreadGate(
435
522
  db,
436
523
  creator,
437
- new AtUri(reply.root.uri).hostname,
524
+ uriToDid(reply.root.uri),
438
525
  replyRefs.root?.record ?? null,
439
526
  replyRefs.gate?.record ?? null,
440
527
  )
@@ -444,10 +531,58 @@ async function validateReply(
444
531
  }
445
532
  }
446
533
 
534
+ async function getThreadgateRecord(db: DatabaseSchema, postUri: string) {
535
+ const threadgateRecordUri = postUriToThreadgateUri(postUri)
536
+ const results = await db
537
+ .selectFrom('record')
538
+ .where('record.uri', '=', threadgateRecordUri)
539
+ .selectAll()
540
+ .execute()
541
+ const threadgateRecord = results.find(
542
+ (ref) => ref.uri === threadgateRecordUri,
543
+ )
544
+ if (threadgateRecord) {
545
+ return jsonStringToLex(threadgateRecord.json) as GateRecord
546
+ }
547
+ }
548
+
549
+ async function validatePostEmbed(
550
+ db: DatabaseSchema,
551
+ embedUri: string,
552
+ parentUri: string,
553
+ ) {
554
+ const postgateRecordUri = postUriToPostgateUri(embedUri)
555
+ const postgateRecord = await db
556
+ .selectFrom('record')
557
+ .where('record.uri', '=', postgateRecordUri)
558
+ .selectAll()
559
+ .executeTakeFirst()
560
+ if (!postgateRecord) {
561
+ return {
562
+ violatesEmbeddingRules: false,
563
+ }
564
+ }
565
+ const {
566
+ embeddingRules: { canEmbed },
567
+ } = parsePostgate({
568
+ gate: jsonStringToLex(postgateRecord.json) as PostgateRecord,
569
+ viewerDid: uriToDid(parentUri),
570
+ authorDid: uriToDid(embedUri),
571
+ })
572
+ if (canEmbed) {
573
+ return {
574
+ violatesEmbeddingRules: false,
575
+ }
576
+ }
577
+ return {
578
+ violatesEmbeddingRules: true,
579
+ }
580
+ }
581
+
447
582
  async function getReplyRefs(db: DatabaseSchema, reply: ReplyRef) {
448
583
  const replyRoot = reply.root.uri
449
584
  const replyParent = reply.parent.uri
450
- const replyGate = postToThreadgateUri(replyRoot)
585
+ const replyGate = postUriToThreadgateUri(replyRoot)
451
586
  const results = await db
452
587
  .selectFrom('record')
453
588
  .where('record.uri', 'in', [replyRoot, replyGate, replyParent])
@@ -37,6 +37,11 @@ const insertFn = async (
37
37
  .onConflict((oc) => oc.doNothing())
38
38
  .returningAll()
39
39
  .executeTakeFirst()
40
+ await db
41
+ .updateTable('post')
42
+ .where('uri', '=', postUri.toString())
43
+ .set({ hasThreadGate: true })
44
+ .executeTakeFirst()
40
45
  return inserted || null
41
46
  }
42
47
 
@@ -66,6 +71,13 @@ const deleteFn = async (
66
71
  .where('uri', '=', uri.toString())
67
72
  .returningAll()
68
73
  .executeTakeFirst()
74
+ if (deleted) {
75
+ await db
76
+ .updateTable('post')
77
+ .where('uri', '=', deleted.postUri)
78
+ .set({ hasThreadGate: false })
79
+ .executeTakeFirst()
80
+ }
69
81
  return deleted || null
70
82
  }
71
83
 
@@ -15,6 +15,7 @@ import mutes from './mutes'
15
15
  import notifs from './notifs'
16
16
  import posts from './posts'
17
17
  import profile from './profile'
18
+ import quotes from './quotes'
18
19
  import records from './records'
19
20
  import relationships from './relationships'
20
21
  import reposts from './reposts'
@@ -42,6 +43,7 @@ export default (db: Database, idResolver: IdResolver) =>
42
43
  ...notifs(db),
43
44
  ...posts(db),
44
45
  ...profile(db),
46
+ ...quotes(db),
45
47
  ...records(db),
46
48
  ...relationships(db),
47
49
  ...reposts(db),
@@ -8,7 +8,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
8
8
  async getInteractionCounts(req) {
9
9
  const uris = req.refs.map((ref) => ref.uri)
10
10
  if (uris.length === 0) {
11
- return { likes: [], replies: [], reposts: [] }
11
+ return { likes: [], replies: [], reposts: [], quotes: [] }
12
12
  }
13
13
  const res = await db.db
14
14
  .selectFrom('post_agg')
@@ -20,6 +20,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
20
20
  likes: uris.map((uri) => byUri[uri]?.likeCount ?? 0),
21
21
  replies: uris.map((uri) => byUri[uri]?.replyCount ?? 0),
22
22
  reposts: uris.map((uri) => byUri[uri]?.repostCount ?? 0),
23
+ quotes: uris.map((uri) => byUri[uri]?.quoteCount ?? 0),
23
24
  }
24
25
  },
25
26
  async getCountsForUsers(req) {
@@ -0,0 +1,32 @@
1
+ import { ServiceImpl } from '@connectrpc/connect'
2
+ import { Service } from '../../../proto/bsky_connect'
3
+ import { Database } from '../db'
4
+ import { paginate, TimeCidKeyset } from '../db/pagination'
5
+
6
+ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
7
+ async getQuotesBySubjectSorted(req) {
8
+ const { subject, cursor, limit } = req
9
+ const { ref } = db.db.dynamic
10
+
11
+ if (!subject?.uri) return { uris: [] }
12
+
13
+ let builder = db.db
14
+ .selectFrom('quote')
15
+ .where('quote.subject', '=', subject.uri)
16
+ .select(['quote.uri', 'quote.cid', 'quote.sortAt'])
17
+
18
+ const keyset = new TimeCidKeyset(ref('quote.sortAt'), ref('quote.cid'))
19
+ builder = paginate(builder, {
20
+ limit,
21
+ cursor,
22
+ keyset,
23
+ })
24
+
25
+ const quotes = await builder.execute()
26
+
27
+ return {
28
+ uris: quotes.map((q) => q.uri),
29
+ cursor: keyset.packFromResult(quotes),
30
+ }
31
+ },
32
+ })
@@ -20,6 +20,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
20
20
  getProfileRecords: getRecords(db, ids.AppBskyActorProfile),
21
21
  getRepostRecords: getRecords(db, ids.AppBskyFeedRepost),
22
22
  getThreadGateRecords: getRecords(db, ids.AppBskyFeedThreadgate),
23
+ getPostgateRecords: getRecords(db, ids.AppBskyFeedPostgate),
23
24
  getLabelerRecords: getRecords(db, ids.AppBskyLabelerService),
24
25
  getActorChatDeclarationRecords: getRecords(db, ids.ChatBskyActorDeclaration),
25
26
  getStarterPackRecords: getRecords(db, ids.AppBskyGraphStarterpack),
@@ -74,7 +75,13 @@ export const getPostRecords = (db: Database) => {
74
75
  ? await db.db
75
76
  .selectFrom('post')
76
77
  .where('uri', 'in', req.uris)
77
- .select(['uri', 'violatesThreadGate'])
78
+ .select([
79
+ 'uri',
80
+ 'violatesThreadGate',
81
+ 'violatesEmbeddingRules',
82
+ 'hasThreadGate',
83
+ 'hasPostGate',
84
+ ])
78
85
  .execute()
79
86
  : [],
80
87
  ])
@@ -82,6 +89,9 @@ export const getPostRecords = (db: Database) => {
82
89
  const meta = req.uris.map((uri) => {
83
90
  return new PostRecordMeta({
84
91
  violatesThreadGate: !!byKey[uri]?.violatesThreadGate,
92
+ violatesEmbeddingRules: !!byKey[uri]?.violatesEmbeddingRules,
93
+ hasThreadGate: !!byKey[uri]?.hasThreadGate,
94
+ hasPostGate: !!byKey[uri]?.hasPostGate,
85
95
  })
86
96
  })
87
97
  return { records, meta }
@@ -1,6 +1,4 @@
1
1
  import { sql } from 'kysely'
2
- import { AtUri } from '@atproto/syntax'
3
- import { ids } from '../../lexicon/lexicons'
4
2
  import {
5
3
  Record as PostRecord,
6
4
  ReplyRef,
@@ -144,9 +142,3 @@ export const violatesThreadGate = async (
144
142
 
145
143
  return true
146
144
  }
147
-
148
- export const postToThreadgateUri = (postUri: string) => {
149
- const gateUri = new AtUri(postUri)
150
- gateUri.collection = ids.AppBskyFeedThreadgate
151
- return gateUri.toString()
152
- }