@atproto/dev-env 0.0.0-spaces-alpha-20260818163953

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 (187) hide show
  1. package/CHANGELOG.md +3424 -0
  2. package/LICENSE.txt +7 -0
  3. package/README.md +39 -0
  4. package/bin.js +7 -0
  5. package/dist/bin-multi-pds.d.ts +2 -0
  6. package/dist/bin-multi-pds.d.ts.map +1 -0
  7. package/dist/bin-multi-pds.js +130 -0
  8. package/dist/bin-multi-pds.js.map +1 -0
  9. package/dist/bin.d.ts +2 -0
  10. package/dist/bin.d.ts.map +1 -0
  11. package/dist/bin.js +56 -0
  12. package/dist/bin.js.map +1 -0
  13. package/dist/bsky.d.ts +27 -0
  14. package/dist/bsky.d.ts.map +1 -0
  15. package/dist/bsky.js +165 -0
  16. package/dist/bsky.js.map +1 -0
  17. package/dist/bsync.d.ts +22 -0
  18. package/dist/bsync.d.ts.map +1 -0
  19. package/dist/bsync.js +54 -0
  20. package/dist/bsync.js.map +1 -0
  21. package/dist/const.d.ts +4 -0
  22. package/dist/const.d.ts.map +1 -0
  23. package/dist/const.js +4 -0
  24. package/dist/const.js.map +1 -0
  25. package/dist/env.d.ts +2 -0
  26. package/dist/env.d.ts.map +1 -0
  27. package/dist/env.js +10 -0
  28. package/dist/env.js.map +1 -0
  29. package/dist/feed-gen.d.ts +15 -0
  30. package/dist/feed-gen.d.ts.map +1 -0
  31. package/dist/feed-gen.js +73 -0
  32. package/dist/feed-gen.js.map +1 -0
  33. package/dist/index.d.ts +17 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +17 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/introspect.d.ts +22 -0
  38. package/dist/introspect.d.ts.map +1 -0
  39. package/dist/introspect.js +65 -0
  40. package/dist/introspect.js.map +1 -0
  41. package/dist/mock/data.d.ts +3 -0
  42. package/dist/mock/data.d.ts.map +1 -0
  43. package/dist/mock/data.js +46 -0
  44. package/dist/mock/data.js.map +1 -0
  45. package/dist/mock/img/blur-hash-avatar-b64.d.ts +3 -0
  46. package/dist/mock/img/blur-hash-avatar-b64.d.ts.map +1 -0
  47. package/dist/mock/img/blur-hash-avatar-b64.js +2 -0
  48. package/dist/mock/img/blur-hash-avatar-b64.js.map +1 -0
  49. package/dist/mock/img/labeled-img-b64.d.ts +3 -0
  50. package/dist/mock/img/labeled-img-b64.d.ts.map +1 -0
  51. package/dist/mock/img/labeled-img-b64.js +2 -0
  52. package/dist/mock/img/labeled-img-b64.js.map +1 -0
  53. package/dist/mock/index.d.ts +3 -0
  54. package/dist/mock/index.d.ts.map +1 -0
  55. package/dist/mock/index.js +574 -0
  56. package/dist/mock/index.js.map +1 -0
  57. package/dist/moderator-client.d.ts +58 -0
  58. package/dist/moderator-client.d.ts.map +1 -0
  59. package/dist/moderator-client.js +110 -0
  60. package/dist/moderator-client.js.map +1 -0
  61. package/dist/network-no-appview.d.ts +21 -0
  62. package/dist/network-no-appview.d.ts.map +1 -0
  63. package/dist/network-no-appview.js +74 -0
  64. package/dist/network-no-appview.js.map +1 -0
  65. package/dist/network.d.ts +32 -0
  66. package/dist/network.d.ts.map +1 -0
  67. package/dist/network.js +190 -0
  68. package/dist/network.js.map +1 -0
  69. package/dist/ozone.d.ts +31 -0
  70. package/dist/ozone.d.ts.map +1 -0
  71. package/dist/ozone.js +254 -0
  72. package/dist/ozone.js.map +1 -0
  73. package/dist/pds.d.ts +23 -0
  74. package/dist/pds.d.ts.map +1 -0
  75. package/dist/pds.js +97 -0
  76. package/dist/pds.js.map +1 -0
  77. package/dist/plc.d.ts +14 -0
  78. package/dist/plc.d.ts.map +1 -0
  79. package/dist/plc.js +28 -0
  80. package/dist/plc.js.map +1 -0
  81. package/dist/seed/author-feed.d.ts +4 -0
  82. package/dist/seed/author-feed.d.ts.map +1 -0
  83. package/dist/seed/author-feed.js +50 -0
  84. package/dist/seed/author-feed.js.map +1 -0
  85. package/dist/seed/basic.d.ts +16 -0
  86. package/dist/seed/basic.d.ts.map +1 -0
  87. package/dist/seed/basic.js +113 -0
  88. package/dist/seed/basic.js.map +1 -0
  89. package/dist/seed/client.d.ts +151 -0
  90. package/dist/seed/client.d.ts.map +1 -0
  91. package/dist/seed/client.js +365 -0
  92. package/dist/seed/client.js.map +1 -0
  93. package/dist/seed/follows.d.ts +4 -0
  94. package/dist/seed/follows.d.ts.map +1 -0
  95. package/dist/seed/follows.js +55 -0
  96. package/dist/seed/follows.js.map +1 -0
  97. package/dist/seed/index.d.ts +11 -0
  98. package/dist/seed/index.d.ts.map +1 -0
  99. package/dist/seed/index.js +11 -0
  100. package/dist/seed/index.js.map +1 -0
  101. package/dist/seed/likes.d.ts +4 -0
  102. package/dist/seed/likes.d.ts.map +1 -0
  103. package/dist/seed/likes.js +37 -0
  104. package/dist/seed/likes.js.map +1 -0
  105. package/dist/seed/quotes.d.ts +4 -0
  106. package/dist/seed/quotes.d.ts.map +1 -0
  107. package/dist/seed/quotes.js +21 -0
  108. package/dist/seed/quotes.js.map +1 -0
  109. package/dist/seed/reposts.d.ts +4 -0
  110. package/dist/seed/reposts.d.ts.map +1 -0
  111. package/dist/seed/reposts.js +17 -0
  112. package/dist/seed/reposts.js.map +1 -0
  113. package/dist/seed/thread-v2.d.ts +227 -0
  114. package/dist/seed/thread-v2.d.ts.map +1 -0
  115. package/dist/seed/thread-v2.js +755 -0
  116. package/dist/seed/thread-v2.js.map +1 -0
  117. package/dist/seed/users-bulk.d.ts +3 -0
  118. package/dist/seed/users-bulk.d.ts.map +1 -0
  119. package/dist/seed/users-bulk.js +224 -0
  120. package/dist/seed/users-bulk.js.map +1 -0
  121. package/dist/seed/users.d.ts +3 -0
  122. package/dist/seed/users.d.ts.map +1 -0
  123. package/dist/seed/users.js +45 -0
  124. package/dist/seed/users.js.map +1 -0
  125. package/dist/seed/verifications.d.ts +5 -0
  126. package/dist/seed/verifications.d.ts.map +1 -0
  127. package/dist/seed/verifications.js +164 -0
  128. package/dist/seed/verifications.js.map +1 -0
  129. package/dist/service-profile-lexicon.d.ts +11 -0
  130. package/dist/service-profile-lexicon.d.ts.map +1 -0
  131. package/dist/service-profile-lexicon.js +115 -0
  132. package/dist/service-profile-lexicon.js.map +1 -0
  133. package/dist/service-profile-ozone.d.ts +18 -0
  134. package/dist/service-profile-ozone.d.ts.map +1 -0
  135. package/dist/service-profile-ozone.js +333 -0
  136. package/dist/service-profile-ozone.js.map +1 -0
  137. package/dist/service-profile.d.ts +24 -0
  138. package/dist/service-profile.d.ts.map +1 -0
  139. package/dist/service-profile.js +61 -0
  140. package/dist/service-profile.js.map +1 -0
  141. package/dist/types.d.ts +61 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +2 -0
  144. package/dist/types.js.map +1 -0
  145. package/dist/util.d.ts +14 -0
  146. package/dist/util.d.ts.map +1 -0
  147. package/dist/util.js +86 -0
  148. package/dist/util.js.map +1 -0
  149. package/package.json +69 -0
  150. package/src/bin-multi-pds.ts +166 -0
  151. package/src/bin.ts +64 -0
  152. package/src/bsky.ts +198 -0
  153. package/src/bsync.ts +66 -0
  154. package/src/const.ts +3 -0
  155. package/src/env.ts +9 -0
  156. package/src/feed-gen.ts +96 -0
  157. package/src/index.ts +17 -0
  158. package/src/introspect.ts +91 -0
  159. package/src/mock/data.ts +46 -0
  160. package/src/mock/img/blur-hash-avatar-b64.ts +1 -0
  161. package/src/mock/img/labeled-img-b64.ts +1 -0
  162. package/src/mock/index.ts +702 -0
  163. package/src/moderator-client.ts +267 -0
  164. package/src/network-no-appview.ts +89 -0
  165. package/src/network.ts +243 -0
  166. package/src/ozone.ts +287 -0
  167. package/src/pds.ts +118 -0
  168. package/src/plc.ts +33 -0
  169. package/src/seed/author-feed.ts +98 -0
  170. package/src/seed/basic.ts +189 -0
  171. package/src/seed/client.ts +676 -0
  172. package/src/seed/follows.ts +57 -0
  173. package/src/seed/index.ts +11 -0
  174. package/src/seed/likes.ts +43 -0
  175. package/src/seed/quotes.ts +53 -0
  176. package/src/seed/reposts.ts +18 -0
  177. package/src/seed/thread-v2.ts +925 -0
  178. package/src/seed/users-bulk.ts +229 -0
  179. package/src/seed/users.ts +64 -0
  180. package/src/seed/verifications.ts +254 -0
  181. package/src/service-profile-lexicon.ts +127 -0
  182. package/src/service-profile-ozone.ts +375 -0
  183. package/src/service-profile.ts +95 -0
  184. package/src/types.ts +68 -0
  185. package/src/util.ts +118 -0
  186. package/tsconfig.build.json +25 -0
  187. package/tsconfig.json +4 -0
@@ -0,0 +1,702 @@
1
+ import {
2
+ type AtpAgent,
3
+ COM_ATPROTO_MODERATION,
4
+ ToolsOzoneQueueCreateQueue,
5
+ } from '@atproto/api'
6
+ import type { Database } from '@atproto/bsky'
7
+ import { AtUri, type AtUriString } from '@atproto/syntax'
8
+ import { EXAMPLE_LABELER, RecordRef, type TestNetwork } from '../index.js'
9
+ import { postTexts, replyTexts } from './data.js'
10
+ import blurHashB64 from './img/blur-hash-avatar-b64.js'
11
+ import labeledImgB64 from './img/labeled-img-b64.js'
12
+
13
+ // NOTE
14
+ // deterministic date generator
15
+ // we use this to ensure the mock dataset is always the same
16
+ // which is very useful when testing
17
+ // (not everything is currently deterministic but it could be)
18
+ function* dateGen(): Generator<string, never> {
19
+ let start = 1657846031914
20
+ while (true) {
21
+ yield new Date(start).toISOString()
22
+ start += 1e3
23
+ }
24
+ }
25
+
26
+ export async function generateMockSetup(env: TestNetwork) {
27
+ const date = dateGen()
28
+
29
+ const rand = (n: number) => Math.floor(Math.random() * n)
30
+ const picka = <T>(arr: Array<T>): T => {
31
+ if (arr.length) {
32
+ return arr[rand(arr.length)] || arr[0]
33
+ }
34
+ throw new Error('Not found')
35
+ }
36
+
37
+ const users = [
38
+ {
39
+ email: 'alice@test.com',
40
+ handle: `alice.test`,
41
+ password: 'hunter2',
42
+ displayName: 'Alice',
43
+ description: 'Test user 0',
44
+ },
45
+ {
46
+ email: 'bob@test.com',
47
+ handle: `bob.test`,
48
+ password: 'hunter2',
49
+ displayName: 'Bob',
50
+ description: 'Test user 1',
51
+ },
52
+ {
53
+ email: 'carla@test.com',
54
+ handle: `carla.test`,
55
+ password: 'hunter2',
56
+ displayName: 'Carla',
57
+ description: 'Test user 2',
58
+ },
59
+ {
60
+ email: 'triage@test.com',
61
+ handle: 'triage.test',
62
+ password: 'triage-pass',
63
+ },
64
+ {
65
+ email: 'mod@test.com',
66
+ handle: 'mod.test',
67
+ password: 'mod-pass',
68
+ },
69
+ {
70
+ email: 'admin-mod@test.com',
71
+ handle: 'admin-mod.test',
72
+ password: 'admin-mod-pass',
73
+ },
74
+ {
75
+ email: 'labeler@test.com',
76
+ handle: 'labeler.test',
77
+ password: 'hunter2',
78
+ displayName: 'Test Labeler',
79
+ description: 'Labeling things across the atmosphere',
80
+ },
81
+ ]
82
+
83
+ const userAgents = await Promise.all(
84
+ users.map(async (user) => {
85
+ const agent: AtpAgent = env.pds.getAgent()
86
+ await agent.createAccount(user)
87
+ agent.assertAuthenticated()
88
+ if (user.displayName || user.description) {
89
+ await agent.app.bsky.actor.profile.create(
90
+ { repo: agent.did },
91
+ {
92
+ displayName: user.displayName,
93
+ description: user.description,
94
+ },
95
+ )
96
+ }
97
+ return agent
98
+ }),
99
+ )
100
+
101
+ const [alice, bob, carla, triage, mod, adminMod, labeler] = userAgents
102
+
103
+ // Create chat declarations for all users
104
+ for (const user of userAgents) {
105
+ await user.chat.bsky.actor.declaration.create(
106
+ { repo: user.did },
107
+ { allowIncoming: 'all' },
108
+ )
109
+ }
110
+
111
+ // Add moderator roles
112
+ await env.ozone.addTriageDid(triage.did)
113
+ await env.ozone.addModeratorDid(mod.did)
114
+ await env.ozone.addAdminDid(adminMod.did)
115
+
116
+ // Create report queues
117
+ const ozoneAgent = env.ozone.getAgent()
118
+ const adminHeaders = async (nsid: string) =>
119
+ env.ozone.modHeaders(nsid, 'admin')
120
+
121
+ const createQueue = async (input: {
122
+ name: string
123
+ subjectTypes: string[]
124
+ reportTypes: string[]
125
+ collection?: string
126
+ }): Promise<void> => {
127
+ try {
128
+ await ozoneAgent.tools.ozone.queue.createQueue(input, {
129
+ encoding: 'application/json',
130
+ headers: await adminHeaders('tools.ozone.queue.createQueue'),
131
+ })
132
+ } catch (err) {
133
+ if (!(err instanceof ToolsOzoneQueueCreateQueue.ConflictingQueueError)) {
134
+ throw err
135
+ }
136
+ }
137
+ }
138
+
139
+ await Promise.all([
140
+ createQueue({
141
+ name: 'Spammy Accounts',
142
+ subjectTypes: ['account'],
143
+ reportTypes: [COM_ATPROTO_MODERATION.DefsReasonSpam],
144
+ }),
145
+ createQueue({
146
+ name: 'Threatening Accounts',
147
+ subjectTypes: ['account'],
148
+ reportTypes: ['tools.ozone.report.defs#reasonViolenceThreats'],
149
+ }),
150
+ createQueue({
151
+ name: 'Spammy Posts',
152
+ subjectTypes: ['record'],
153
+ reportTypes: [COM_ATPROTO_MODERATION.DefsReasonSpam],
154
+ collection: 'app.bsky.feed.post',
155
+ }),
156
+ ])
157
+
158
+ // Report one user (random)
159
+ const reporter = picka(userAgents)
160
+ await reporter.com.atproto.moderation.createReport({
161
+ reasonType: picka([
162
+ COM_ATPROTO_MODERATION.DefsReasonSpam,
163
+ COM_ATPROTO_MODERATION.DefsReasonOther,
164
+ ]),
165
+ reason: picka(["Didn't look right to me", undefined, undefined]),
166
+ subject: {
167
+ $type: 'com.atproto.admin.defs#repoRef',
168
+ did: picka(userAgents).did,
169
+ },
170
+ })
171
+
172
+ // Reports that target queues
173
+ await alice.com.atproto.moderation.createReport({
174
+ reasonType: COM_ATPROTO_MODERATION.DefsReasonSpam,
175
+ reason: 'This account is spamming',
176
+ subject: { $type: 'com.atproto.admin.defs#repoRef', did: bob.did },
177
+ })
178
+ await bob.com.atproto.moderation.createReport({
179
+ reasonType: 'tools.ozone.report.defs#reasonViolenceThreats',
180
+ reason: 'Threatened me',
181
+ subject: {
182
+ $type: 'com.atproto.admin.defs#repoRef',
183
+ did: carla.did,
184
+ },
185
+ })
186
+
187
+ // everybody follows everybody
188
+ const follow = async (author: AtpAgent, subject: AtpAgent) => {
189
+ await author.app.bsky.graph.follow.create(
190
+ { repo: author.assertDid },
191
+ {
192
+ subject: subject.assertDid,
193
+ createdAt: date.next().value,
194
+ },
195
+ )
196
+ }
197
+ await follow(alice, bob)
198
+ await follow(alice, carla)
199
+ await follow(bob, alice)
200
+ await follow(bob, carla)
201
+ await follow(carla, alice)
202
+ await follow(carla, bob)
203
+
204
+ // a set of posts and reposts
205
+ const posts: { uri: string; cid: string }[] = []
206
+ for (let i = 0; i < postTexts.length; i++) {
207
+ const author = picka(userAgents)
208
+ const post = await author.app.bsky.feed.post.create(
209
+ { repo: author.did },
210
+ {
211
+ text: postTexts[i],
212
+ createdAt: date.next().value,
213
+ },
214
+ )
215
+ posts.push(post)
216
+ if (rand(10) === 0) {
217
+ const reposter = picka(userAgents)
218
+ await reposter.app.bsky.feed.repost.create(
219
+ { repo: reposter.did },
220
+ {
221
+ subject: picka(posts),
222
+ createdAt: date.next().value,
223
+ },
224
+ )
225
+ }
226
+ if (rand(6) === 0) {
227
+ const reporter = picka(userAgents)
228
+ await reporter.com.atproto.moderation.createReport({
229
+ reasonType: picka([
230
+ COM_ATPROTO_MODERATION.DefsReasonSpam,
231
+ COM_ATPROTO_MODERATION.DefsReasonOther,
232
+ ]),
233
+ reason: picka(["Didn't look right to me", undefined, undefined]),
234
+ subject: {
235
+ $type: 'com.atproto.repo.strongRef',
236
+ uri: post.uri,
237
+ cid: post.cid,
238
+ },
239
+ })
240
+ }
241
+ }
242
+
243
+ // Spam post report
244
+ if (posts.length > 0) {
245
+ await carla.com.atproto.moderation.createReport({
246
+ reasonType: COM_ATPROTO_MODERATION.DefsReasonSpam,
247
+ reason: 'This post is spam',
248
+ subject: {
249
+ $type: 'com.atproto.repo.strongRef',
250
+ uri: posts[0].uri,
251
+ cid: posts[0].cid,
252
+ },
253
+ })
254
+ }
255
+
256
+ // Route all reports to queues
257
+ await env.ozone.daemon.ctx.queueRouter.routeReports()
258
+
259
+ // make some naughty posts & label them
260
+ const file = Buffer.from(labeledImgB64, 'base64')
261
+ const uploadedImg = await bob.com.atproto.repo.uploadBlob(file, {
262
+ encoding: 'image/png',
263
+ })
264
+ const labeledPost = await bob.app.bsky.feed.post.create(
265
+ { repo: bob.assertDid },
266
+ {
267
+ text: 'naughty post',
268
+ embed: {
269
+ $type: 'app.bsky.embed.images',
270
+ images: [
271
+ {
272
+ image: uploadedImg.data.blob,
273
+ alt: 'naughty naughty',
274
+ },
275
+ ],
276
+ },
277
+ createdAt: date.next().value,
278
+ },
279
+ )
280
+
281
+ const filteredPost = await bob.app.bsky.feed.post.create(
282
+ { repo: bob.assertDid },
283
+ {
284
+ text: 'really bad post should be deleted',
285
+ createdAt: date.next().value,
286
+ },
287
+ )
288
+
289
+ await createLabel(env.bsky.db, {
290
+ uri: labeledPost.uri,
291
+ cid: labeledPost.cid,
292
+ val: 'nudity',
293
+ })
294
+ await createLabel(env.bsky.db, {
295
+ uri: filteredPost.uri,
296
+ cid: filteredPost.cid,
297
+ val: 'dmca-violation',
298
+ })
299
+
300
+ // post with a gallery of images
301
+ const galleryItems: Array<any> = []
302
+ for (let i = 0; i < 10; i++) {
303
+ galleryItems.push({
304
+ $type: 'app.bsky.embed.gallery#image',
305
+ image: uploadedImg.data.blob,
306
+ alt: 'naughty ' + (i + 1),
307
+ aspectRatio: {
308
+ $type: 'app.bsky.embed.defs#aspectRatio',
309
+ width: 10,
310
+ height: 10,
311
+ },
312
+ })
313
+ }
314
+ const galleryPost = await bob.app.bsky.feed.post.create(
315
+ { repo: bob.assertDid },
316
+ {
317
+ text: 'look at my cool pics',
318
+ embed: {
319
+ $type: 'app.bsky.embed.gallery',
320
+ items: galleryItems,
321
+ },
322
+ createdAt: date.next().value,
323
+ },
324
+ )
325
+ posts.push(galleryPost)
326
+
327
+ // a set of replies
328
+ for (let i = 0; i < 100; i++) {
329
+ const targetUri = picka(posts).uri
330
+ const urip = new AtUri(targetUri)
331
+ const target = await alice.app.bsky.feed.post.get({
332
+ repo: urip.host,
333
+ rkey: urip.rkey,
334
+ })
335
+ const author = picka(userAgents)
336
+ try {
337
+ const post = await author.app.bsky.feed.post.create(
338
+ { repo: author.did },
339
+ {
340
+ text: picka(replyTexts),
341
+ reply: {
342
+ root: target.value.reply?.root ?? target,
343
+ parent: target,
344
+ },
345
+ createdAt: date.next().value,
346
+ },
347
+ )
348
+
349
+ posts.push(post)
350
+ } catch (err) {
351
+ // @TODO Investigate why this sometimes fails.
352
+ console.error('Failed to create reply', err)
353
+ }
354
+ }
355
+
356
+ // a set of likes
357
+ for (const post of posts) {
358
+ for (const user of userAgents) {
359
+ if (rand(3) === 0) {
360
+ await user.app.bsky.feed.like.create(
361
+ { repo: user.did },
362
+ {
363
+ subject: post,
364
+ createdAt: date.next().value,
365
+ },
366
+ )
367
+ }
368
+ }
369
+ }
370
+
371
+ // a couple feed generators that returns some posts
372
+ const fg1Uri = AtUri.make(
373
+ alice.assertDid,
374
+ 'app.bsky.feed.generator',
375
+ 'alice-favs',
376
+ )
377
+ const fg1 = await env.createFeedGen({
378
+ [fg1Uri.toString()]: async () => {
379
+ const feed = posts
380
+ .filter(() => rand(2) === 0)
381
+ .map((post) => ({ post: post.uri as AtUriString }))
382
+ return {
383
+ encoding: 'application/json',
384
+ body: {
385
+ feed,
386
+ },
387
+ }
388
+ },
389
+ })
390
+ const avatarImg = Buffer.from(blurHashB64, 'base64')
391
+ const avatarRes = await alice.com.atproto.repo.uploadBlob(avatarImg, {
392
+ encoding: 'image/png',
393
+ })
394
+ const fgAliceRes = await alice.app.bsky.feed.generator.create(
395
+ { repo: alice.assertDid, rkey: fg1Uri.rkey },
396
+ {
397
+ did: fg1.did,
398
+ displayName: 'alices feed',
399
+ description: 'all my fav stuff',
400
+ avatar: avatarRes.data.blob,
401
+ createdAt: date.next().value,
402
+ },
403
+ )
404
+
405
+ await alice.app.bsky.feed.post.create(
406
+ { repo: alice.assertDid },
407
+ {
408
+ text: 'check out my algorithm!',
409
+ embed: {
410
+ $type: 'app.bsky.embed.record',
411
+ record: fgAliceRes,
412
+ },
413
+ createdAt: date.next().value,
414
+ },
415
+ )
416
+ for (const user of [alice, bob, carla]) {
417
+ await user.app.bsky.feed.like.create(
418
+ { repo: user.did },
419
+ {
420
+ subject: fgAliceRes,
421
+ createdAt: date.next().value,
422
+ },
423
+ )
424
+ }
425
+
426
+ const fg2Uri = AtUri.make(
427
+ bob.assertDid,
428
+ 'app.bsky.feed.generator',
429
+ 'bob-redux',
430
+ )
431
+ const fg2 = await env.createFeedGen({
432
+ [fg2Uri.toString()]: async () => {
433
+ const feed = posts
434
+ .filter(() => rand(2) === 0)
435
+ .map((post) => ({ post: post.uri as AtUriString }))
436
+ return {
437
+ encoding: 'application/json',
438
+ body: {
439
+ feed,
440
+ },
441
+ }
442
+ },
443
+ })
444
+ const fgBobRes = await bob.app.bsky.feed.generator.create(
445
+ { repo: bob.assertDid, rkey: fg2Uri.rkey },
446
+ {
447
+ did: fg2.did,
448
+ displayName: 'Bobby boy hot new algo',
449
+ createdAt: date.next().value,
450
+ },
451
+ )
452
+
453
+ await alice.app.bsky.feed.post.create(
454
+ { repo: alice.assertDid },
455
+ {
456
+ text: `bobs feed is neat too`,
457
+ embed: {
458
+ $type: 'app.bsky.embed.record',
459
+ record: fgBobRes,
460
+ },
461
+ createdAt: date.next().value,
462
+ },
463
+ )
464
+
465
+ const fg3Uri = AtUri.make(
466
+ carla.assertDid,
467
+ 'app.bsky.feed.generator',
468
+ 'carla-intr-algo',
469
+ )
470
+ const fg3 = await env.createFeedGen({
471
+ [fg3Uri.toString()]: async () => {
472
+ const feed = posts
473
+ .filter(() => rand(2) === 0)
474
+ .map((post) => ({ post: post.uri as AtUriString }))
475
+ return {
476
+ encoding: 'application/json',
477
+ body: {
478
+ feed,
479
+ },
480
+ }
481
+ },
482
+ })
483
+ const fgCarlaRes = await carla.app.bsky.feed.generator.create(
484
+ { repo: carla.assertDid, rkey: fg3Uri.rkey },
485
+ {
486
+ did: fg3.did,
487
+ displayName: `Acceptin' Generator`,
488
+ acceptsInteractions: true,
489
+ createdAt: date.next().value,
490
+ },
491
+ )
492
+
493
+ await alice.app.bsky.feed.post.create(
494
+ { repo: alice.assertDid },
495
+ {
496
+ text: `carla accepts interactions on her feed`,
497
+ embed: {
498
+ $type: 'app.bsky.embed.record',
499
+ record: fgCarlaRes,
500
+ },
501
+ createdAt: date.next().value,
502
+ },
503
+ )
504
+
505
+ // create labeler service
506
+ {
507
+ await labeler.app.bsky.labeler.service.create(
508
+ { repo: labeler.did, rkey: 'self' },
509
+ {
510
+ policies: {
511
+ labelValues: [
512
+ '!hide',
513
+ 'porn',
514
+ 'rude',
515
+ 'spam',
516
+ 'spider',
517
+ 'misinfo',
518
+ 'cool',
519
+ 'curate',
520
+ ],
521
+ labelValueDefinitions: [
522
+ {
523
+ identifier: 'rude',
524
+ blurs: 'content',
525
+ severity: 'alert',
526
+ defaultSetting: 'warn',
527
+ adultOnly: true,
528
+ locales: [
529
+ {
530
+ lang: 'en',
531
+ name: 'Rude',
532
+ description: 'Just such a jerk, you wouldnt believe it.',
533
+ },
534
+ ],
535
+ },
536
+ {
537
+ identifier: 'spam',
538
+ blurs: 'content',
539
+ severity: 'inform',
540
+ defaultSetting: 'hide',
541
+ locales: [
542
+ {
543
+ lang: 'en',
544
+ name: 'Spam',
545
+ description:
546
+ 'Low quality posts that dont add to the conversation.',
547
+ },
548
+ ],
549
+ },
550
+ {
551
+ identifier: 'spider',
552
+ blurs: 'media',
553
+ severity: 'alert',
554
+ defaultSetting: 'warn',
555
+ locales: [
556
+ {
557
+ lang: 'en',
558
+ name: 'Spider!',
559
+ description: 'Oh no its a spider.',
560
+ },
561
+ ],
562
+ },
563
+ {
564
+ identifier: 'cool',
565
+ blurs: 'none',
566
+ severity: 'inform',
567
+ defaultSetting: 'warn',
568
+ locales: [
569
+ {
570
+ lang: 'en',
571
+ name: 'Cool',
572
+ description: 'The coolest peeps in the atmosphere.',
573
+ },
574
+ ],
575
+ },
576
+ {
577
+ identifier: 'curate',
578
+ blurs: 'none',
579
+ severity: 'none',
580
+ defaultSetting: 'warn',
581
+ locales: [
582
+ {
583
+ lang: 'en',
584
+ name: 'Curation filter',
585
+ description: 'We just dont want to see it as much.',
586
+ },
587
+ ],
588
+ },
589
+ ],
590
+ },
591
+ createdAt: date.next().value,
592
+ },
593
+ )
594
+ await createLabel(env.bsky.db, {
595
+ uri: alice.assertDid,
596
+ cid: '',
597
+ val: 'rude',
598
+ src: labeler.did,
599
+ })
600
+ await createLabel(env.bsky.db, {
601
+ uri: `at://${alice.assertDid}/app.bsky.feed.generator/alice-favs`,
602
+ cid: '',
603
+ val: 'cool',
604
+ src: labeler.did,
605
+ })
606
+ await createLabel(env.bsky.db, {
607
+ uri: bob.assertDid,
608
+ cid: '',
609
+ val: 'cool',
610
+ src: labeler.did,
611
+ })
612
+ await createLabel(env.bsky.db, {
613
+ uri: carla.assertDid,
614
+ cid: '',
615
+ val: 'spam',
616
+ src: labeler.did,
617
+ })
618
+ }
619
+
620
+ // Create lists and add people to the lists
621
+ {
622
+ const flowerLovers = await alice.app.bsky.graph.list.create(
623
+ { repo: alice.assertDid },
624
+ {
625
+ name: 'Flower Lovers',
626
+ purpose: 'app.bsky.graph.defs#curatelist',
627
+ createdAt: new Date().toISOString(),
628
+ description: 'A list of posts about flowers',
629
+ },
630
+ )
631
+ const labelHaters = await bob.app.bsky.graph.list.create(
632
+ { repo: bob.assertDid },
633
+ {
634
+ name: 'Label Haters',
635
+ purpose: 'app.bsky.graph.defs#modlist',
636
+ createdAt: new Date().toISOString(),
637
+ description: 'A list of people who hate labels',
638
+ },
639
+ )
640
+ await alice.app.bsky.graph.listitem.create(
641
+ { repo: alice.assertDid },
642
+ {
643
+ subject: bob.assertDid,
644
+ createdAt: new Date().toISOString(),
645
+ list: new RecordRef(flowerLovers.uri, flowerLovers.cid).uriStr,
646
+ },
647
+ )
648
+ await bob.app.bsky.graph.listitem.create(
649
+ { repo: bob.assertDid },
650
+ {
651
+ subject: alice.assertDid,
652
+ createdAt: new Date().toISOString(),
653
+ list: new RecordRef(labelHaters.uri, labelHaters.cid).uriStr,
654
+ },
655
+ )
656
+ }
657
+
658
+ await setVerifier(env.bsky.db, alice.assertDid)
659
+
660
+ // @TODO These are useful when testing complex threads, but don't need to be enabled all the time. We could make it configurable.
661
+ // import * as seedThreadV2 from '../seed/thread-v2.js'
662
+ // const sc = env.getSeedClient()
663
+ // await seedThreadV2.simple(sc)
664
+ // await seedThreadV2.long(sc)
665
+ // await seedThreadV2.deep(sc)
666
+ // await seedThreadV2.branchingFactor(sc)
667
+ // await seedThreadV2.annotateMoreReplies(sc)
668
+ // await seedThreadV2.annotateOP(sc)
669
+ // await seedThreadV2.sort(sc)
670
+ // await seedThreadV2.bumpOpAndViewer(sc)
671
+ // await seedThreadV2.bumpGroupSorting(sc)
672
+ // await seedThreadV2.bumpFollows(sc)
673
+ // await seedThreadV2.blockDeletionAuth(sc, env.bsky.ctx.cfg.modServiceDid)
674
+ // await seedThreadV2.mutes(sc)
675
+ // await seedThreadV2.threadgated(sc)
676
+ // await seedThreadV2.tags(sc)
677
+ }
678
+
679
+ const createLabel = async (
680
+ db: Database,
681
+ opts: { uri: string; cid: string; val: string; src?: string },
682
+ ) => {
683
+ await db.db
684
+ .insertInto('label')
685
+ .values({
686
+ uri: opts.uri,
687
+ cid: opts.cid,
688
+ val: opts.val,
689
+ cts: new Date().toISOString(),
690
+ neg: false,
691
+ src: opts.src ?? EXAMPLE_LABELER,
692
+ })
693
+ .execute()
694
+ }
695
+
696
+ const setVerifier = async (db: Database, did: string) => {
697
+ await db.db
698
+ .updateTable('actor')
699
+ .set({ trustedVerifier: true })
700
+ .where('did', '=', did)
701
+ .execute()
702
+ }