@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,676 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { CID } from 'multiformats/cid'
5
+ import type {
6
+ $Typed,
7
+ AppBskyActorProfile,
8
+ AppBskyFeedLike,
9
+ AppBskyFeedPost,
10
+ AppBskyFeedRepost,
11
+ AppBskyGraphBlock,
12
+ AppBskyGraphFollow,
13
+ AppBskyGraphList,
14
+ AppBskyGraphVerification,
15
+ AppBskyRichtextFacet,
16
+ AtpAgent,
17
+ ChatBskyConvoDefs,
18
+ ComAtprotoAdminDefs,
19
+ ComAtprotoModerationCreateReport,
20
+ ComAtprotoRepoStrongRef,
21
+ } from '@atproto/api'
22
+ import type { CidString, Client } from '@atproto/lex'
23
+ import type { BlobRef } from '@atproto/lexicon'
24
+ import {
25
+ AtUri,
26
+ type AtUriString,
27
+ type DidString,
28
+ type HandleString,
29
+ } from '@atproto/syntax'
30
+ import type { TestNetworkNoAppView } from '../network-no-appview.js'
31
+
32
+ // Makes it simple to create data via the XRPC client,
33
+ // and keeps track of all created data in memory for convenience.
34
+
35
+ let AVATAR_IMG: Uint8Array | undefined
36
+
37
+ // AVATAR_PATH is defined in a non-CWD-dependant way, so this works
38
+ // for any consumer of this package, even outside the atproto repo.
39
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
40
+ const AVATAR_PATH = path.resolve(
41
+ __dirname,
42
+ '../../assets/key-portrait-small.jpg',
43
+ )
44
+
45
+ export type ImageRef = {
46
+ image: BlobRef
47
+ alt: string
48
+ }
49
+
50
+ export class RecordRef {
51
+ uri: AtUri
52
+ cid: CID
53
+
54
+ constructor(uri: AtUri | string, cid: CID | string) {
55
+ this.uri = new AtUri(uri.toString())
56
+ this.cid = CID.parse(cid.toString())
57
+ }
58
+
59
+ get raw(): { uri: AtUriString; cid: CidString } {
60
+ return {
61
+ uri: this.uri.toString(),
62
+ cid: this.cid.toString(),
63
+ }
64
+ }
65
+
66
+ get uriStr(): AtUriString {
67
+ return this.uri.toString()
68
+ }
69
+
70
+ get cidStr(): CidString {
71
+ return this.cid.toString()
72
+ }
73
+ }
74
+
75
+ export type Account = {
76
+ did: DidString
77
+ accessJwt: string
78
+ refreshJwt: string
79
+ handle: HandleString
80
+ email: string
81
+ password: string
82
+ }
83
+
84
+ export class SeedClient<
85
+ Network extends TestNetworkNoAppView = TestNetworkNoAppView,
86
+ > {
87
+ accounts: Record<DidString, Account>
88
+ profiles: Record<
89
+ DidString,
90
+ {
91
+ displayName: string
92
+ description: string
93
+ avatar: { cid: string; mimeType: string }
94
+ joinedViaStarterPack: RecordRef | undefined
95
+ ref: RecordRef
96
+ }
97
+ >
98
+ follows: Record<DidString, Record<DidString, RecordRef>>
99
+ blocks: Record<DidString, Record<DidString, RecordRef>>
100
+ mutes: Record<DidString, Set<DidString>>
101
+ posts: Record<
102
+ DidString,
103
+ { text: string; ref: RecordRef; images: ImageRef[]; quote?: RecordRef }[]
104
+ >
105
+ likes: Record<DidString, Record<string, AtUri>>
106
+ replies: Record<
107
+ string,
108
+ { text: string; ref: RecordRef; images: ImageRef[] }[]
109
+ >
110
+ reposts: Record<DidString, RecordRef[]>
111
+ lists: Record<
112
+ DidString,
113
+ Record<
114
+ AtUriString,
115
+ {
116
+ ref: RecordRef
117
+ items: Record<DidString, RecordRef>
118
+ }
119
+ >
120
+ >
121
+ feedgens: Record<
122
+ DidString,
123
+ Record<
124
+ AtUriString,
125
+ {
126
+ ref: RecordRef
127
+ items: Record<string, RecordRef>
128
+ }
129
+ >
130
+ >
131
+ starterpacks: Record<
132
+ DidString,
133
+ Record<
134
+ AtUriString,
135
+ {
136
+ ref: RecordRef
137
+ name: string
138
+ list: RecordRef
139
+ feeds: readonly AtUriString[]
140
+ }
141
+ >
142
+ >
143
+
144
+ verifications: Record<DidString, Record<DidString, AtUri>>
145
+
146
+ dids: Record<string, DidString>
147
+
148
+ constructor(
149
+ public network: Network,
150
+ public agent: AtpAgent,
151
+ public client: Client,
152
+ ) {
153
+ this.accounts = {}
154
+ this.profiles = {}
155
+ this.follows = {}
156
+ this.blocks = {}
157
+ this.mutes = {}
158
+ this.posts = {}
159
+ this.likes = {}
160
+ this.replies = {}
161
+ this.reposts = {}
162
+ this.lists = {}
163
+ this.feedgens = {}
164
+ this.starterpacks = {}
165
+ this.verifications = {}
166
+ this.dids = {}
167
+ }
168
+
169
+ async createAccount(
170
+ shortName: string,
171
+ params: {
172
+ handle: HandleString
173
+ email: string
174
+ password: string
175
+ inviteCode?: string
176
+ },
177
+ ): Promise<Account> {
178
+ const { data: account } =
179
+ await this.agent.com.atproto.server.createAccount(params)
180
+ const did = account.did as DidString
181
+ this.dids[shortName] = did
182
+ this.accounts[account.did] = {
183
+ // @NOTE type case needed until we move test to lex-sdk
184
+ ...(account as typeof account & { handle: HandleString }),
185
+ did,
186
+ email: params.email,
187
+ password: params.password,
188
+ }
189
+ return this.accounts[account.did]
190
+ }
191
+
192
+ async updateHandle(by: DidString, handle: HandleString) {
193
+ await this.agent.com.atproto.identity.updateHandle(
194
+ { handle },
195
+ { encoding: 'application/json', headers: this.getHeaders(by) },
196
+ )
197
+ }
198
+
199
+ async createProfile(
200
+ by: DidString,
201
+ displayName: string,
202
+ description: string,
203
+ selfLabels?: readonly string[],
204
+ joinedViaStarterPack?: RecordRef,
205
+ overrides?: Partial<AppBskyActorProfile.Record>,
206
+ ): Promise<{
207
+ displayName: string
208
+ description: string
209
+ avatar: { cid: string; mimeType: string }
210
+ ref: RecordRef
211
+ joinedViaStarterPack?: RecordRef
212
+ }> {
213
+ AVATAR_IMG ??= await fs.readFile(AVATAR_PATH)
214
+
215
+ let avatarBlob
216
+ {
217
+ const res = await this.agent.com.atproto.repo.uploadBlob(AVATAR_IMG, {
218
+ encoding: 'image/jpeg',
219
+ headers: this.getHeaders(by),
220
+ } as any)
221
+ avatarBlob = res.data.blob
222
+ }
223
+
224
+ {
225
+ const res = await this.agent.app.bsky.actor.profile.create(
226
+ { repo: by },
227
+ {
228
+ displayName,
229
+ description,
230
+ avatar: avatarBlob,
231
+ labels: selfLabels
232
+ ? {
233
+ $type: 'com.atproto.label.defs#selfLabels',
234
+ values: selfLabels.map((val) => ({ val })),
235
+ }
236
+ : undefined,
237
+ joinedViaStarterPack: joinedViaStarterPack?.raw,
238
+ createdAt: new Date().toISOString(),
239
+ ...overrides,
240
+ },
241
+ this.getHeaders(by),
242
+ )
243
+ this.profiles[by] = {
244
+ displayName,
245
+ description,
246
+ avatar: avatarBlob,
247
+ joinedViaStarterPack,
248
+ ref: new RecordRef(res.uri, res.cid),
249
+ }
250
+ }
251
+ return this.profiles[by]
252
+ }
253
+
254
+ async updateProfile(by: DidString, record: Record<string, unknown>) {
255
+ const res = await this.agent.com.atproto.repo.putRecord(
256
+ {
257
+ repo: by,
258
+ collection: 'app.bsky.actor.profile',
259
+ rkey: 'self',
260
+ record,
261
+ },
262
+ { headers: this.getHeaders(by), encoding: 'application/json' },
263
+ )
264
+ this.profiles[by] = {
265
+ ...(this.profiles[by] ?? {}),
266
+ ...record,
267
+ ref: new RecordRef(res.data.uri, res.data.cid),
268
+ }
269
+ return this.profiles[by]
270
+ }
271
+
272
+ async follow(
273
+ from: DidString,
274
+ to: DidString,
275
+ overrides?: Partial<AppBskyGraphFollow.Record>,
276
+ ) {
277
+ const res = await this.agent.app.bsky.graph.follow.create(
278
+ { repo: from },
279
+ {
280
+ subject: to,
281
+ createdAt: new Date().toISOString(),
282
+ ...overrides,
283
+ },
284
+ this.getHeaders(from),
285
+ )
286
+ this.follows[from] ??= {}
287
+ this.follows[from][to] = new RecordRef(res.uri, res.cid)
288
+ return this.follows[from][to]
289
+ }
290
+
291
+ async unfollow(from: DidString, to: DidString) {
292
+ const follow = this.follows[from][to]
293
+ if (!follow) {
294
+ throw new Error('follow does not exist')
295
+ }
296
+ await this.agent.app.bsky.graph.follow.delete(
297
+ { repo: from, rkey: follow.uri.rkey },
298
+ this.getHeaders(from),
299
+ )
300
+ delete this.follows[from][to]
301
+ }
302
+
303
+ async block(
304
+ from: DidString,
305
+ to: DidString,
306
+ overrides?: Partial<AppBskyGraphBlock.Record>,
307
+ ) {
308
+ const res = await this.agent.app.bsky.graph.block.create(
309
+ { repo: from },
310
+ {
311
+ subject: to,
312
+ createdAt: new Date().toISOString(),
313
+ ...overrides,
314
+ },
315
+ this.getHeaders(from),
316
+ )
317
+ this.blocks[from] ??= {}
318
+ this.blocks[from][to] = new RecordRef(res.uri, res.cid)
319
+ return this.blocks[from][to]
320
+ }
321
+
322
+ async unblock(from: DidString, to: DidString) {
323
+ const block = this.blocks[from][to]
324
+ if (!block) {
325
+ throw new Error('block does not exist')
326
+ }
327
+ await this.agent.app.bsky.graph.block.delete(
328
+ { repo: from, rkey: block.uri.rkey },
329
+ this.getHeaders(from),
330
+ )
331
+ delete this.blocks[from][to]
332
+ }
333
+
334
+ async mute(from: DidString, to: DidString) {
335
+ await this.agent.app.bsky.graph.muteActor(
336
+ {
337
+ actor: to,
338
+ },
339
+ { headers: this.getHeaders(from) },
340
+ )
341
+ this.mutes[from] ??= new Set()
342
+ this.mutes[from].add(to)
343
+ return this.mutes[from][to]
344
+ }
345
+
346
+ async post(
347
+ by: DidString,
348
+ text: string,
349
+ facets?: AppBskyRichtextFacet.Main[],
350
+ images?: ImageRef[],
351
+ quote?: RecordRef,
352
+ overrides?: Partial<AppBskyFeedPost.Record>,
353
+ ) {
354
+ const imageEmbed = images && {
355
+ $type: 'app.bsky.embed.images',
356
+ images,
357
+ }
358
+ const recordEmbed = quote && {
359
+ record: { uri: quote.uriStr, cid: quote.cidStr },
360
+ }
361
+ const embed =
362
+ imageEmbed && recordEmbed
363
+ ? {
364
+ $type: 'app.bsky.embed.recordWithMedia',
365
+ record: recordEmbed,
366
+ media: imageEmbed,
367
+ }
368
+ : recordEmbed
369
+ ? { $type: 'app.bsky.embed.record', ...recordEmbed }
370
+ : imageEmbed
371
+ const res = await this.agent.app.bsky.feed.post.create(
372
+ { repo: by },
373
+ {
374
+ text: text,
375
+ facets,
376
+ embed,
377
+ createdAt: new Date().toISOString(),
378
+ ...overrides,
379
+ },
380
+ this.getHeaders(by),
381
+ )
382
+ this.posts[by] ??= []
383
+ const post = {
384
+ text,
385
+ ref: new RecordRef(res.uri, res.cid),
386
+ images: images ?? [],
387
+ quote,
388
+ }
389
+ this.posts[by].push(post)
390
+ return post
391
+ }
392
+
393
+ async deletePost(by: DidString, uri: AtUri) {
394
+ await this.agent.app.bsky.feed.post.delete(
395
+ {
396
+ repo: by,
397
+ rkey: uri.rkey,
398
+ },
399
+ this.getHeaders(by),
400
+ )
401
+ }
402
+
403
+ async uploadFile(
404
+ by: DidString,
405
+ filePath: string,
406
+ encoding: string,
407
+ ): Promise<ImageRef> {
408
+ const file = await fs.readFile(filePath)
409
+ const res = await this.agent.com.atproto.repo.uploadBlob(file, {
410
+ headers: this.getHeaders(by),
411
+ encoding,
412
+ } as any)
413
+ return { image: res.data.blob, alt: filePath }
414
+ }
415
+
416
+ async like(
417
+ by: DidString,
418
+ subject: RecordRef,
419
+ overrides?: Partial<AppBskyFeedLike.Record>,
420
+ ) {
421
+ const res = await this.agent.app.bsky.feed.like.create(
422
+ { repo: by },
423
+ {
424
+ subject: subject.raw,
425
+ createdAt: new Date().toISOString(),
426
+ ...overrides,
427
+ },
428
+ this.getHeaders(by),
429
+ )
430
+ this.likes[by] ??= {}
431
+ this.likes[by][subject.uriStr] = new AtUri(res.uri)
432
+ return this.likes[by][subject.uriStr]
433
+ }
434
+
435
+ async reply(
436
+ by: DidString,
437
+ root: RecordRef,
438
+ parent: RecordRef,
439
+ text: string,
440
+ facets?: AppBskyRichtextFacet.Main[],
441
+ images?: ImageRef[],
442
+ overrides?: Partial<AppBskyFeedPost.Record>,
443
+ ) {
444
+ const embed = images
445
+ ? {
446
+ $type: 'app.bsky.embed.images',
447
+ images,
448
+ }
449
+ : undefined
450
+ const res = await this.agent.app.bsky.feed.post.create(
451
+ { repo: by },
452
+ {
453
+ text: text,
454
+ reply: {
455
+ root: root.raw,
456
+ parent: parent.raw,
457
+ },
458
+ facets,
459
+ embed,
460
+ createdAt: new Date().toISOString(),
461
+ ...overrides,
462
+ },
463
+ this.getHeaders(by),
464
+ )
465
+ this.replies[by] ??= []
466
+ const reply = {
467
+ text,
468
+ ref: new RecordRef(res.uri, res.cid),
469
+ images: images ?? [],
470
+ }
471
+ this.replies[by].push(reply)
472
+ return reply
473
+ }
474
+
475
+ async repost(
476
+ by: DidString,
477
+ subject: RecordRef,
478
+ overrides?: Partial<AppBskyFeedRepost.Record>,
479
+ ) {
480
+ const res = await this.agent.app.bsky.feed.repost.create(
481
+ { repo: by },
482
+ {
483
+ subject: subject.raw,
484
+ createdAt: new Date().toISOString(),
485
+ ...overrides,
486
+ },
487
+ this.getHeaders(by),
488
+ )
489
+ this.reposts[by] ??= []
490
+ const repost = new RecordRef(res.uri, res.cid)
491
+ this.reposts[by].push(repost)
492
+ return repost
493
+ }
494
+
495
+ async createList(
496
+ by: DidString,
497
+ name: string,
498
+ purpose: 'mod' | 'curate' | 'reference',
499
+ overrides?: Partial<AppBskyGraphList.Record>,
500
+ ) {
501
+ const res = await this.agent.app.bsky.graph.list.create(
502
+ { repo: by },
503
+ {
504
+ name,
505
+ purpose:
506
+ purpose === 'mod'
507
+ ? 'app.bsky.graph.defs#modlist'
508
+ : purpose === 'curate'
509
+ ? 'app.bsky.graph.defs#curatelist'
510
+ : 'app.bsky.graph.defs#referencelist',
511
+ createdAt: new Date().toISOString(),
512
+ ...(overrides || {}),
513
+ },
514
+ this.getHeaders(by),
515
+ )
516
+ this.lists[by] ??= {}
517
+ const ref = new RecordRef(res.uri, res.cid)
518
+ this.lists[by][ref.uriStr] = {
519
+ ref: ref,
520
+ items: {},
521
+ }
522
+ return ref
523
+ }
524
+
525
+ async createFeedGen(by: DidString, feedDid: string, name: string) {
526
+ const res = await this.agent.app.bsky.feed.generator.create(
527
+ { repo: by },
528
+ {
529
+ did: feedDid,
530
+ displayName: name,
531
+ createdAt: new Date().toISOString(),
532
+ },
533
+ this.getHeaders(by),
534
+ )
535
+ this.feedgens[by] ??= {}
536
+ const ref = new RecordRef(res.uri, res.cid)
537
+ this.feedgens[by][ref.uriStr] = {
538
+ ref: ref,
539
+ items: {},
540
+ }
541
+ return ref
542
+ }
543
+
544
+ async createStarterPack(
545
+ by: DidString,
546
+ name: string,
547
+ actors: readonly DidString[],
548
+ feeds?: readonly AtUriString[],
549
+ ) {
550
+ const list = await this.createList(by, 'n/a', 'reference')
551
+ for (const did of actors) {
552
+ await this.addToList(by, did, list)
553
+ }
554
+ const res = await this.agent.app.bsky.graph.starterpack.create(
555
+ { repo: by },
556
+ {
557
+ name,
558
+ list: list.uriStr,
559
+ feeds: feeds?.map((uri) => ({ uri })),
560
+ createdAt: new Date().toISOString(),
561
+ },
562
+ this.getHeaders(by),
563
+ )
564
+ this.starterpacks[by] ??= {}
565
+ const ref = new RecordRef(res.uri, res.cid)
566
+ this.starterpacks[by][ref.uriStr] = {
567
+ ref: ref,
568
+ list,
569
+ feeds: feeds ?? [],
570
+ name,
571
+ }
572
+ return ref
573
+ }
574
+
575
+ async addToList(by: DidString, subject: DidString, list: RecordRef) {
576
+ const res = await this.agent.app.bsky.graph.listitem.create(
577
+ { repo: by },
578
+ { subject, list: list.uriStr, createdAt: new Date().toISOString() },
579
+ this.getHeaders(by),
580
+ )
581
+ const ref = new RecordRef(res.uri, res.cid)
582
+ const found = (this.lists[by] ?? {})[list.uriStr]
583
+ if (found) {
584
+ found.items[subject] = ref
585
+ }
586
+ return ref
587
+ }
588
+
589
+ async rmFromList(by: DidString, subject: DidString, list: RecordRef) {
590
+ const foundList = (this.lists[by] ?? {})[list.uriStr] ?? {}
591
+ if (!foundList) return
592
+ const foundItem = foundList.items[subject]
593
+ if (!foundItem) return
594
+ await this.agent.app.bsky.graph.listitem.delete(
595
+ { repo: by, rkey: foundItem.uri.rkey },
596
+ this.getHeaders(by),
597
+ )
598
+ delete foundList.items[subject]
599
+ }
600
+
601
+ // override public signature to add support for convos and messages
602
+ async createReport(opts: {
603
+ reasonType: ComAtprotoModerationCreateReport.InputSchema['reasonType']
604
+ subject:
605
+ | $Typed<ComAtprotoAdminDefs.RepoRef>
606
+ | $Typed<ComAtprotoRepoStrongRef.Main>
607
+ | $Typed<ChatBskyConvoDefs.MessageRef>
608
+ | $Typed<ChatBskyConvoDefs.ConvoRef>
609
+ | { $type: string }
610
+ reason?: string
611
+ reportedBy: DidString
612
+ }): Promise<
613
+ ComAtprotoModerationCreateReport.OutputSchema & {
614
+ subject:
615
+ | $Typed<ComAtprotoAdminDefs.RepoRef>
616
+ | $Typed<ComAtprotoRepoStrongRef.Main>
617
+ | $Typed<ChatBskyConvoDefs.MessageRef>
618
+ | $Typed<ChatBskyConvoDefs.ConvoRef>
619
+ | { $type: string }
620
+ }
621
+ > {
622
+ const { reasonType, subject, reason, reportedBy } = opts
623
+ const result = await this.agent.com.atproto.moderation.createReport(
624
+ { reasonType, subject, reason },
625
+ {
626
+ encoding: 'application/json',
627
+ headers: this.getHeaders(reportedBy),
628
+ },
629
+ )
630
+ return result.data
631
+ }
632
+
633
+ async verify(
634
+ by: DidString,
635
+ subject: DidString,
636
+ handle: HandleString,
637
+ displayName: string,
638
+ overrides?: Partial<AppBskyGraphVerification.Record>,
639
+ ) {
640
+ const res = await this.agent.app.bsky.graph.verification.create(
641
+ { repo: by },
642
+ {
643
+ subject,
644
+ createdAt: new Date().toISOString(),
645
+ handle,
646
+ displayName,
647
+ ...overrides,
648
+ },
649
+ this.getHeaders(by),
650
+ )
651
+ this.verifications[by] ??= {}
652
+ this.verifications[by][subject] = new AtUri(res.uri)
653
+ return this.verifications[by][subject]
654
+ }
655
+
656
+ async unverify(by: DidString, subject: DidString) {
657
+ const verification = this.verifications[by]?.[subject]
658
+ if (!verification) {
659
+ throw new Error('verification does not exist')
660
+ }
661
+
662
+ await this.agent.app.bsky.graph.verification.delete(
663
+ { repo: by, rkey: verification.rkey },
664
+ this.getHeaders(by),
665
+ )
666
+ delete this.verifications[by][subject]
667
+ }
668
+
669
+ getHeaders(did: DidString) {
670
+ return SeedClient.getHeaders(this.accounts[did].accessJwt)
671
+ }
672
+
673
+ static getHeaders(jwt: string) {
674
+ return { authorization: `Bearer ${jwt}` }
675
+ }
676
+ }
@@ -0,0 +1,57 @@
1
+ import type { SeedClient } from './client.js'
2
+
3
+ export default async (sc: SeedClient) => {
4
+ await sc.createAccount('alice', users.alice)
5
+ await sc.createAccount('bob', users.bob)
6
+ await sc.createAccount('carol', users.carol)
7
+ await sc.createAccount('dan', users.dan)
8
+ await sc.createAccount('eve', users.eve)
9
+ for (const name in sc.dids) {
10
+ await sc.createProfile(sc.dids[name], `display-${name}`, `descript-${name}`)
11
+ }
12
+ const alice = sc.dids.alice
13
+ const bob = sc.dids.bob
14
+ const carol = sc.dids.carol
15
+ const dan = sc.dids.dan
16
+ const eve = sc.dids.eve
17
+ await sc.follow(alice, bob)
18
+ await sc.follow(alice, carol)
19
+ await sc.follow(alice, dan)
20
+ await sc.follow(alice, eve)
21
+ await sc.follow(carol, alice)
22
+ await sc.follow(bob, alice)
23
+ await sc.follow(bob, carol)
24
+ await sc.follow(dan, alice)
25
+ await sc.follow(dan, bob)
26
+ await sc.follow(dan, eve)
27
+ await sc.follow(eve, alice)
28
+ await sc.follow(eve, carol)
29
+ }
30
+
31
+ const users = {
32
+ alice: {
33
+ email: 'alice@test.com',
34
+ handle: 'alice.test',
35
+ password: 'alice-pass',
36
+ },
37
+ bob: {
38
+ email: 'bob@test.com',
39
+ handle: 'bob.test',
40
+ password: 'bob-pass',
41
+ },
42
+ carol: {
43
+ email: 'carol@test.com',
44
+ handle: 'carol.test',
45
+ password: 'carol-pass',
46
+ },
47
+ dan: {
48
+ email: 'dan@test.com',
49
+ handle: 'dan.test',
50
+ password: 'dan-pass',
51
+ },
52
+ eve: {
53
+ email: 'eve@test.com',
54
+ handle: 'eve.test',
55
+ password: 'eve-pass',
56
+ },
57
+ } as const