@atcute/bluesky-threading 2.0.1 → 3.0.0

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/dist/cbor.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { encode } from '@atcute/cbor';
2
- import { create, format } from '@atcute/cid';
2
+ import { create, toString } from '@atcute/cid';
3
3
  // Sanity-check by requiring a $type here, this is because the records are
4
4
  // expected to be encoded with it, even though the PDS accepts record writes
5
5
  // without the field.
6
6
  export async function serializeRecordCid(record) {
7
7
  const bytes = encode(record);
8
8
  const cid = await create(0x71, bytes);
9
- const serialized = format(cid);
9
+ const serialized = toString(cid);
10
10
  return serialized;
11
11
  }
12
12
  //# sourceMappingURL=cbor.js.map
package/dist/cbor.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cbor.js","sourceRoot":"","sources":["../lib/cbor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE7C,0EAA0E;AAC1E,4EAA4E;AAC5E,qBAAqB;AACrB,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAyB;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAE7B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAE/B,OAAO,UAAU,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"cbor.js","sourceRoot":"","sources":["../lib/cbor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE/C,0EAA0E;AAC1E,4EAA4E;AAC5E,qBAAqB;AACrB,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAyB;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAE7B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAEjC,OAAO,UAAU,CAAC;AACnB,CAAC"}
package/lib/cbor.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { encode } from '@atcute/cbor';
2
+ import { create, toString } from '@atcute/cid';
3
+
4
+ // Sanity-check by requiring a $type here, this is because the records are
5
+ // expected to be encoded with it, even though the PDS accepts record writes
6
+ // without the field.
7
+ export async function serializeRecordCid(record: { $type: string }): Promise<string> {
8
+ const bytes = encode(record);
9
+
10
+ const cid = await create(0x71, bytes);
11
+ const serialized = toString(cid);
12
+
13
+ return serialized;
14
+ }
package/lib/index.ts ADDED
@@ -0,0 +1,417 @@
1
+ import '@atcute/bluesky/lexicons';
2
+
3
+ import { XRPCError, type XRPC } from '@atcute/client';
4
+ import type {
5
+ AppBskyEmbedExternal,
6
+ AppBskyEmbedImages,
7
+ AppBskyEmbedRecord,
8
+ AppBskyEmbedVideo,
9
+ AppBskyFeedDefs,
10
+ AppBskyFeedPost,
11
+ AppBskyFeedThreadgate,
12
+ At,
13
+ Brand,
14
+ ComAtprotoLabelDefs,
15
+ ComAtprotoRepoApplyWrites,
16
+ ComAtprotoRepoStrongRef,
17
+ } from '@atcute/client/lexicons';
18
+ import * as TID from '@atcute/tid';
19
+
20
+ import { serializeRecordCid } from './cbor.js';
21
+ import { getNow } from './time.js';
22
+
23
+ import type {
24
+ ComposedThread,
25
+ ComposedThreadgate,
26
+ PostEmbed,
27
+ PostMediaEmbed,
28
+ PostRecordEmbed,
29
+ } from './types.js';
30
+
31
+ export type * from './types.js';
32
+
33
+ /**
34
+ * Create post records and publish them
35
+ * @param rpc An authenticated Bluesky RPC client
36
+ * @param thread Composed thread
37
+ * @returns An array of post records that were published
38
+ */
39
+ export async function publishThread(
40
+ rpc: XRPC,
41
+ thread: Omit<ComposedThread, 'rpc'>,
42
+ ): Promise<Brand.Union<ComAtprotoRepoApplyWrites.Create>[]> {
43
+ const records = await createThread({ ...thread, rpc });
44
+
45
+ await rpc.call('com.atproto.repo.applyWrites', {
46
+ signal: thread.signal,
47
+ data: {
48
+ repo: thread.author,
49
+ writes: records,
50
+ },
51
+ });
52
+
53
+ return records;
54
+ }
55
+
56
+ /**
57
+ * Create post records without publishing, allows you to do it yourself.
58
+ * @param thread Composed thread
59
+ * @returns An array of post records
60
+ */
61
+ export async function createThread(
62
+ thread: ComposedThread,
63
+ ): Promise<Brand.Union<ComAtprotoRepoApplyWrites.Create>[]> {
64
+ const rpc = thread.rpc;
65
+ const signal = thread.signal;
66
+
67
+ const did = thread.author;
68
+ const posts = thread.posts;
69
+ const threadgate = thread.gate;
70
+ const languages = thread.languages;
71
+
72
+ const writes: Brand.Union<ComAtprotoRepoApplyWrites.Create>[] = [];
73
+
74
+ const now = thread.createdAt !== undefined ? new Date(thread.createdAt) : new Date(getNow(posts.length));
75
+ assert(!Number.isNaN(now.getTime()), `provided createdAt value is invalid`);
76
+
77
+ let reply: AppBskyFeedPost.ReplyRef | undefined;
78
+ let rkey: string | undefined;
79
+
80
+ if (thread.reply) {
81
+ let post = thread.reply;
82
+
83
+ if (typeof post === 'string') {
84
+ // AT-URI being passed
85
+ assertXrpc(rpc, `ComposedThread.reply`);
86
+ post = await getPost(post);
87
+ }
88
+
89
+ let root: ComAtprotoRepoStrongRef.Main | undefined;
90
+ let ref: ComAtprotoRepoStrongRef.Main;
91
+
92
+ if ('record' in post) {
93
+ // AppBskyFeedDefs.PostView being passed
94
+
95
+ root = (post.record as AppBskyFeedPost.Record).reply?.root;
96
+ ref = { uri: post.uri, cid: post.cid };
97
+ } else if ('value' in post) {
98
+ // AppBskyEmbedRecord.ViewRecord being passed
99
+
100
+ root = (post.value as AppBskyFeedPost.Record).reply?.root;
101
+ ref = { uri: post.uri, cid: post.cid };
102
+ } else {
103
+ assert(false, `Unexpected end of code`);
104
+ }
105
+
106
+ reply = {
107
+ root: root ? { uri: root.uri, cid: root.cid } : ref,
108
+ parent: ref,
109
+ };
110
+ }
111
+
112
+ assert(!reply || !threadgate, `threadgate and reply are mutually exclusive`);
113
+
114
+ for (let idx = 0, len = posts.length; idx < len; idx++) {
115
+ // Get the record key for this post
116
+ rkey = TID.createRaw(now.getTime(), Math.floor(Math.random() * 1023));
117
+
118
+ const post = posts[idx];
119
+ const uri = `at://${did}/app.bsky.feed.post/${rkey}`;
120
+
121
+ // Resolve embeds
122
+ let embed: AppBskyFeedPost.Record['embed'];
123
+ if (post.embed !== undefined) {
124
+ embed = await resolveEmbed(post.embed);
125
+ }
126
+
127
+ // Get the self-labels
128
+ const labels = getEmbedLabels(post.embed);
129
+ let selfLabels: Brand.Union<ComAtprotoLabelDefs.SelfLabels> | undefined;
130
+
131
+ if (labels?.length) {
132
+ selfLabels = {
133
+ $type: 'com.atproto.label.defs#selfLabels',
134
+ values: labels.map((val) => ({ val })),
135
+ };
136
+ }
137
+
138
+ // Now form the record
139
+ const content = post.content;
140
+
141
+ const record: AppBskyFeedPost.Record = {
142
+ $type: 'app.bsky.feed.post',
143
+ createdAt: now.toISOString(),
144
+ text: content.text,
145
+ facets: content.facets,
146
+ reply: reply,
147
+ embed: embed,
148
+ langs: post.languages ?? languages,
149
+ labels: selfLabels,
150
+ };
151
+
152
+ writes.push({
153
+ $type: 'com.atproto.repo.applyWrites#create',
154
+ collection: 'app.bsky.feed.post',
155
+ rkey: rkey,
156
+ value: record,
157
+ });
158
+
159
+ // If this is the first post, and we have a threadgate set, create one now.
160
+ if (idx === 0 && threadgate) {
161
+ const threadgateRecord: AppBskyFeedThreadgate.Record = {
162
+ $type: 'app.bsky.feed.threadgate',
163
+ createdAt: now.toISOString(),
164
+ post: uri,
165
+ allow: resolveThreadgate(threadgate),
166
+ };
167
+
168
+ writes.push({
169
+ $type: 'com.atproto.repo.applyWrites#create',
170
+ collection: 'app.bsky.feed.threadgate',
171
+ rkey: rkey,
172
+ value: threadgateRecord,
173
+ });
174
+ }
175
+
176
+ if (idx !== len - 1) {
177
+ // Retrieve the next reply reference
178
+ const serialized = await serializeRecordCid(record);
179
+
180
+ const ref: ComAtprotoRepoStrongRef.Main = {
181
+ cid: serialized,
182
+ uri: uri,
183
+ };
184
+
185
+ reply = {
186
+ root: reply ? reply.root : ref,
187
+ parent: ref,
188
+ };
189
+
190
+ // Posts are not guaranteed to be shown in the correct order if they are
191
+ // all posted with the same timestamp.
192
+ now.setMilliseconds(now.getMilliseconds() + 1);
193
+ }
194
+ }
195
+
196
+ return writes;
197
+
198
+ async function resolveEmbed(embed: PostEmbed): Promise<AppBskyFeedPost.Record['embed'] | undefined> {
199
+ const { media, record } = embed;
200
+
201
+ if (media && record) {
202
+ return {
203
+ $type: 'app.bsky.embed.recordWithMedia',
204
+ media: await resolveMediaEmbed(media),
205
+ record: await resolveRecordEmbed(record),
206
+ };
207
+ } else if (media) {
208
+ return resolveMediaEmbed(media);
209
+ } else if (record) {
210
+ return resolveRecordEmbed(record);
211
+ }
212
+
213
+ return;
214
+
215
+ async function resolveMediaEmbed(
216
+ embed: PostMediaEmbed,
217
+ ): Promise<Brand.Union<AppBskyEmbedExternal.Main | AppBskyEmbedImages.Main | AppBskyEmbedVideo.Main>> {
218
+ const type = embed.type;
219
+
220
+ if (type === 'external') {
221
+ const rawThumb = embed.thumbnail;
222
+ let thumb: At.Blob<any> | undefined;
223
+
224
+ if (rawThumb !== undefined) {
225
+ if (rawThumb instanceof Blob) {
226
+ assertXrpc(rpc, `PostExternalEmbed.thumbnail`);
227
+ thumb = await uploadBlob(rawThumb);
228
+ } else {
229
+ thumb = rawThumb;
230
+ }
231
+ }
232
+
233
+ return {
234
+ $type: 'app.bsky.embed.external',
235
+ external: {
236
+ uri: embed.uri,
237
+ title: embed.title,
238
+ description: embed.description ?? '',
239
+ thumb: thumb,
240
+ },
241
+ };
242
+ }
243
+
244
+ if (type === 'image') {
245
+ const images: AppBskyEmbedImages.Image[] = [];
246
+
247
+ for (const image of embed.images) {
248
+ const aspectRatio = image.aspectRatio;
249
+ const rawBlob = image.blob;
250
+ let blob: At.Blob<any>;
251
+
252
+ if (rawBlob instanceof Blob) {
253
+ assertXrpc(rpc, `PostImageEmbed.images[].blob`);
254
+ blob = await uploadBlob(rawBlob);
255
+ } else {
256
+ blob = rawBlob;
257
+ }
258
+
259
+ images.push({
260
+ image: blob,
261
+ alt: image.alt ?? '',
262
+ aspectRatio: aspectRatio ? { width: aspectRatio.width, height: aspectRatio.height } : undefined,
263
+ });
264
+ }
265
+
266
+ return {
267
+ $type: 'app.bsky.embed.images',
268
+ images: images,
269
+ };
270
+ }
271
+
272
+ if (type === 'video') {
273
+ const aspectRatio = embed.aspectRatio;
274
+ const rawBlob = embed.blob;
275
+ let blob: At.Blob<any> | undefined;
276
+
277
+ if (rawBlob instanceof Blob) {
278
+ assertXrpc(rpc, `PostVideoEmbed.blob`);
279
+ blob = await uploadBlob(rawBlob);
280
+ } else {
281
+ blob = rawBlob;
282
+ }
283
+
284
+ return {
285
+ $type: 'app.bsky.embed.video',
286
+ video: blob,
287
+ alt: embed.alt ?? '',
288
+ aspectRatio: aspectRatio ? { width: aspectRatio.width, height: aspectRatio.height } : undefined,
289
+ };
290
+ }
291
+
292
+ assert(false, `Unexpected end of code`);
293
+ }
294
+
295
+ async function resolveRecordEmbed(embed: PostRecordEmbed): Promise<Brand.Union<AppBskyEmbedRecord.Main>> {
296
+ const uri = embed.uri;
297
+ let cid = embed.cid;
298
+
299
+ if (cid === undefined) {
300
+ const type = embed.type;
301
+
302
+ if (type === 'quote') {
303
+ assertXrpc(rpc, 'PostQuoteEmbed');
304
+
305
+ const post = await getPost(uri);
306
+
307
+ cid = post.cid;
308
+ } else if (type === 'feed') {
309
+ assertXrpc(rpc, 'PostFeedEmbed');
310
+
311
+ const { data } = await rpc.get('app.bsky.feed.getFeedGenerator', {
312
+ signal: signal,
313
+ params: { feed: uri },
314
+ });
315
+
316
+ cid = data.view.cid;
317
+ } else if (type === 'list') {
318
+ assertXrpc(rpc, 'PostListEmbed');
319
+
320
+ const { data } = await rpc.get('app.bsky.graph.getList', {
321
+ signal: signal,
322
+ params: { list: uri, limit: 1 },
323
+ });
324
+
325
+ cid = data.list.cid;
326
+ } else if (type === 'starterpack') {
327
+ assertXrpc(rpc, 'PostStarterpackEmbed');
328
+
329
+ const { data } = await rpc.get('app.bsky.graph.getStarterPack', {
330
+ signal: signal,
331
+ params: { starterPack: uri },
332
+ });
333
+
334
+ cid = data.starterPack.cid;
335
+ } else {
336
+ assert(false, `Unexpected end of code`);
337
+ }
338
+ }
339
+
340
+ return {
341
+ $type: 'app.bsky.embed.record',
342
+ record: {
343
+ uri: uri,
344
+ cid: cid,
345
+ },
346
+ };
347
+ }
348
+ }
349
+
350
+ async function uploadBlob(blob: Blob): Promise<At.Blob> {
351
+ // `rpc` intentionally non-null asserted.
352
+ const { data } = await rpc!.call('com.atproto.repo.uploadBlob', {
353
+ signal: signal,
354
+ data: blob,
355
+ });
356
+
357
+ return data.blob;
358
+ }
359
+
360
+ async function getPost(uri: string): Promise<AppBskyFeedDefs.PostView> {
361
+ // `rpc` intentionally non-null asserted.
362
+ const { data } = await rpc!.get('app.bsky.feed.getPosts', {
363
+ signal: signal,
364
+ params: {
365
+ uris: [uri],
366
+ },
367
+ });
368
+
369
+ const post = data.posts[0];
370
+ if (!post) {
371
+ throw new XRPCError(400, { kind: 'NotFound', description: `Post not found: ${uri}` });
372
+ }
373
+
374
+ return post;
375
+ }
376
+ }
377
+
378
+ function resolveThreadgate(gate: ComposedThreadgate): AppBskyFeedThreadgate.Record['allow'] {
379
+ const rules: AppBskyFeedThreadgate.Record['allow'] = [];
380
+
381
+ if (gate.follows) {
382
+ rules.push({ $type: 'app.bsky.feed.threadgate#followingRule' });
383
+ }
384
+ if (gate.mentions) {
385
+ rules.push({ $type: 'app.bsky.feed.threadgate#mentionRule' });
386
+ }
387
+
388
+ for (const listUri of gate.listUris ?? []) {
389
+ rules.push({ $type: 'app.bsky.feed.threadgate#listRule', list: listUri });
390
+ }
391
+
392
+ return rules;
393
+ }
394
+
395
+ function getEmbedLabels(embed: PostEmbed | undefined): string[] | undefined {
396
+ const media = embed?.media;
397
+
398
+ if (media !== undefined) {
399
+ const type = media.type;
400
+
401
+ if (type === 'image' || type === 'external') {
402
+ return media.labels;
403
+ }
404
+ }
405
+ }
406
+
407
+ function assert(condition: boolean, message: string): asserts condition {
408
+ if (!condition) {
409
+ throw new Error(message);
410
+ }
411
+ }
412
+
413
+ function assertXrpc(rpc: XRPC | undefined, thing: string): asserts rpc {
414
+ if (rpc === undefined) {
415
+ throw new Error(`${thing} requires supplying RPC instance`);
416
+ }
417
+ }
package/lib/time.ts ADDED
@@ -0,0 +1,13 @@
1
+ let lastTimestamp: number = 0;
2
+
3
+ /**
4
+ * Return the current time, make sure that each call never returns same value
5
+ * so that posts sent at the same time from different accounts don't end up
6
+ * colliding with each other potentially causing them to not be shown.
7
+ */
8
+ export function getNow(threadSize: number): number {
9
+ let timestamp = Math.max(Date.now(), lastTimestamp);
10
+ lastTimestamp = timestamp + 2 + threadSize;
11
+
12
+ return timestamp;
13
+ }
package/lib/types.ts ADDED
@@ -0,0 +1,202 @@
1
+ import type { XRPC } from '@atcute/client';
2
+ import type { AppBskyEmbedRecord, AppBskyFeedDefs, AppBskyRichtextFacet, At } from '@atcute/client/lexicons';
3
+
4
+ /** Interface containing aspect ratio of the media */
5
+ export interface MediaAspectRatio {
6
+ /** Media width */
7
+ width: number;
8
+ /** Media height */
9
+ height: number;
10
+ }
11
+
12
+ /** An embed that links to an external page */
13
+ export interface PostExternalEmbed {
14
+ type: 'external';
15
+ /** Link to the page */
16
+ uri: string;
17
+ /** Page title */
18
+ title: string;
19
+ /** Page description */
20
+ description?: string;
21
+ /**
22
+ * Page thumbnail, accepts either a Web Blob instance or a blob returned from
23
+ * the `com.atproto.repo.uploadBlob` procedure. Supplying the former requires
24
+ * you to also supply an authenticated RPC instance for it to be able to make
25
+ * procedure calls.
26
+ */
27
+ thumbnail?: Blob | At.Blob;
28
+ /** Labels to describe this external embed */
29
+ labels?: string[];
30
+ }
31
+
32
+ /** An image within the image embed */
33
+ export interface ComposedImage {
34
+ /**
35
+ * The image data, accepts either a Web Blob instance or a blob returned from
36
+ * the `com.atproto.repo.uploadBlob` procedure. Supplying the former requires
37
+ * you to also supply an authenticated RPC instance for it to be able to make
38
+ * procedure calls.
39
+ */
40
+ blob: Blob | At.Blob;
41
+ /**
42
+ * Alternative text for this image, helps describe images for low-vision users
43
+ * and provide context for everyone.
44
+ */
45
+ alt?: string;
46
+ /**
47
+ * Aspect ratio of the image, supplying this is recommended as clients makes
48
+ * use of it to properly display images.
49
+ */
50
+ aspectRatio?: MediaAspectRatio;
51
+ }
52
+
53
+ /** An embed that displays images */
54
+ export interface PostImageEmbed {
55
+ type: 'image';
56
+ /** An array of images */
57
+ images: ComposedImage[];
58
+ /** Labels to describe this image embed */
59
+ labels?: string[];
60
+ }
61
+
62
+ /** An embed that displays a video */
63
+ export interface PostVideoEmbed {
64
+ type: 'video';
65
+ /**
66
+ * The video data, accepts either a Web Blob instance or a blob returned from
67
+ * the `com.atproto.repo.uploadBlob` procedure. Supplying the former requires
68
+ * you to also supply an authenticated RPC instance for it to be able to make
69
+ * procedure calls.
70
+ */
71
+ blob: Blob | At.Blob;
72
+ /**
73
+ * Alternative text for this video, helps describe video for low-vision users
74
+ * and provide context for everyone.
75
+ */
76
+ alt?: string;
77
+ /**
78
+ * Aspect ratio of the video, supplying this is recommended as clients makes
79
+ * use of it to properly display the video.
80
+ */
81
+ aspectRatio?: MediaAspectRatio;
82
+ /**
83
+ * Labels to describe this video embed
84
+ */
85
+ labels?: string[];
86
+ }
87
+
88
+ /** Union type of media embeds */
89
+ export type PostMediaEmbed = PostExternalEmbed | PostImageEmbed | PostVideoEmbed;
90
+
91
+ /** An embed that displays a feed card */
92
+ export interface PostFeedEmbed {
93
+ type: 'feed';
94
+ /** AT-URI of the feed */
95
+ uri: At.Uri;
96
+ /**
97
+ * CID of the feed, if not supplied, requires you to also supply an RPC
98
+ * instance for it to be able to make query calls.
99
+ */
100
+ cid?: string;
101
+ }
102
+
103
+ /** An embed that displays a user/moderation list card */
104
+ export interface PostListEmbed {
105
+ type: 'list';
106
+ /** AT-URI of the list */
107
+ uri: string;
108
+ /**
109
+ * CID of the list, if not supplied, requires you to also supply an RPC
110
+ * instance for it to be able to make query calls.
111
+ */
112
+ cid?: string;
113
+ }
114
+
115
+ /** An embed that displays a post card (quote post/repost with quote) */
116
+ export interface PostQuoteEmbed {
117
+ type: 'quote';
118
+ /** AT-URI of the post */
119
+ uri: string;
120
+ /**
121
+ * CID of the post, if not supplied, requires you to also supply an RPC
122
+ * instance for it to be able to make query calls.
123
+ */
124
+ cid?: string;
125
+ }
126
+
127
+ /** An embed that displays a starter pack card */
128
+ export interface PostStarterpackEmbed {
129
+ type: 'starterpack';
130
+ /** AT-URI of the post */
131
+ uri: string;
132
+ /**
133
+ * CID of the starter pack, if not supplied, requires you to also supply an
134
+ * RPC instance for it to be able to make query calls.
135
+ */
136
+ cid?: string;
137
+ }
138
+
139
+ /** Union type of "record" embeds */
140
+ export type PostRecordEmbed = PostFeedEmbed | PostListEmbed | PostQuoteEmbed | PostStarterpackEmbed;
141
+
142
+ /** Embed in a post, can contain media and links to other records */
143
+ export interface PostEmbed {
144
+ media?: PostMediaEmbed;
145
+ record?: PostRecordEmbed;
146
+ }
147
+
148
+ /** The post being composed */
149
+ export interface ComposedPost {
150
+ /** The language that this post is in */
151
+ languages?: string[];
152
+ /** The content of the post */
153
+ content: {
154
+ /** Post text */
155
+ text: string;
156
+ /** Decorations applied to the text */
157
+ facets?: AppBskyRichtextFacet.Main[];
158
+ };
159
+ /** Embed assigned to this post */
160
+ embed?: PostEmbed;
161
+ }
162
+
163
+ /** Reply gating options, leave this empty to deny everyone from replying */
164
+ export interface ComposedThreadgate {
165
+ /** Allow replies from users you follow */
166
+ follows?: boolean;
167
+ /** Allow replies from users mentioned in the post */
168
+ mentions?: boolean;
169
+ /** Allow replies from users that are in these user lists */
170
+ listUris?: At.Uri[];
171
+ }
172
+
173
+ /** Base interface for the thread being composed */
174
+ export interface ComposedThread {
175
+ /** An RPC instance, necessary for some options that takes action on your behalf */
176
+ rpc?: XRPC;
177
+ /** Abort signal */
178
+ signal?: AbortSignal;
179
+ /** Author of the thread */
180
+ author: At.DID;
181
+ /**
182
+ * The "creation time" for this thread,
183
+ * if not supplied, the current time is used
184
+ */
185
+ createdAt?: string | number | Date;
186
+ /**
187
+ * The post it should reply to, accepts either an AT-URI of the post, a view
188
+ * of the post, or an embed view of the post. Supplying an AT-URI requires you
189
+ * to also supply an PRC instance for it to be able to make query calls.
190
+ */
191
+ reply?: string | AppBskyFeedDefs.PostView | AppBskyEmbedRecord.ViewRecord;
192
+ /**
193
+ * Thread gating to apply on this thread, this option can't be set if this is
194
+ * a reply, especially to another user's thread. Leave this undefined to allow
195
+ * everyone to reply to the thread, supply an empty object to deny everyone.
196
+ */
197
+ gate?: ComposedThreadgate;
198
+ /** The language that all the posts are in, can be overridden per-post */
199
+ languages?: string[];
200
+ /** An array of posts */
201
+ posts: ComposedPost[];
202
+ }
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@atcute/bluesky-threading",
4
- "version": "2.0.1",
4
+ "version": "3.0.0",
5
5
  "description": "create Bluesky threads containing multiple posts with one write",
6
6
  "license": "MIT",
7
7
  "repository": {
8
- "url": "https://codeberg.org/mary-ext/atcute"
8
+ "url": "https://github.com/mary-ext/atcute",
9
+ "directory": "packages/bluesky/threading"
9
10
  },
10
11
  "files": [
11
- "dist/"
12
+ "dist/",
13
+ "lib/",
14
+ "!lib/**/*.bench.ts",
15
+ "!lib/**/*.test.ts"
12
16
  ],
13
17
  "exports": {
14
18
  ".": "./dist/index.js"
@@ -19,15 +23,15 @@
19
23
  "@atcute/client": "^2.0.0"
20
24
  },
21
25
  "dependencies": {
22
- "@atcute/cbor": "^1.0.2",
23
- "@atcute/cid": "^1.0.1",
24
- "@atcute/tid": "^1.0.0"
26
+ "@atcute/cbor": "^2.0.0",
27
+ "@atcute/tid": "^1.0.1",
28
+ "@atcute/cid": "^2.0.0"
25
29
  },
26
30
  "devDependencies": {
27
- "@types/bun": "^1.1.10",
28
- "@atcute/bluesky": "^1.0.7",
29
- "@atcute/client": "^2.0.3",
30
- "@atcute/bluesky-richtext-builder": "^1.0.1"
31
+ "@types/bun": "^1.1.12",
32
+ "@atcute/bluesky": "^1.0.11",
33
+ "@atcute/bluesky-richtext-builder": "^1.0.2",
34
+ "@atcute/client": "^2.0.6"
31
35
  },
32
36
  "scripts": {
33
37
  "build": "tsc --project tsconfig.build.json",