@agnocon/piece-bluesky 0.1.6

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 (67) hide show
  1. package/LICENSE.MIT-AP +24 -0
  2. package/dist/index.d.ts +8 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +32 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/actions/create-post.d.ts +21 -0
  7. package/dist/lib/actions/create-post.d.ts.map +1 -0
  8. package/dist/lib/actions/create-post.js +379 -0
  9. package/dist/lib/actions/create-post.js.map +1 -0
  10. package/dist/lib/actions/find-post.d.ts +8 -0
  11. package/dist/lib/actions/find-post.d.ts.map +1 -0
  12. package/dist/lib/actions/find-post.js +77 -0
  13. package/dist/lib/actions/find-post.js.map +1 -0
  14. package/dist/lib/actions/find-thread.d.ts +10 -0
  15. package/dist/lib/actions/find-thread.d.ts.map +1 -0
  16. package/dist/lib/actions/find-thread.js +117 -0
  17. package/dist/lib/actions/find-thread.js.map +1 -0
  18. package/dist/lib/actions/like-post.d.ts +14 -0
  19. package/dist/lib/actions/like-post.d.ts.map +1 -0
  20. package/dist/lib/actions/like-post.js +129 -0
  21. package/dist/lib/actions/like-post.js.map +1 -0
  22. package/dist/lib/actions/repost.d.ts +14 -0
  23. package/dist/lib/actions/repost.d.ts.map +1 -0
  24. package/dist/lib/actions/repost.js +132 -0
  25. package/dist/lib/actions/repost.js.map +1 -0
  26. package/dist/lib/common/auth.d.ts +11 -0
  27. package/dist/lib/common/auth.d.ts.map +1 -0
  28. package/dist/lib/common/auth.js +75 -0
  29. package/dist/lib/common/auth.js.map +1 -0
  30. package/dist/lib/common/client.d.ts +8 -0
  31. package/dist/lib/common/client.d.ts.map +1 -0
  32. package/dist/lib/common/client.js +47 -0
  33. package/dist/lib/common/client.js.map +1 -0
  34. package/dist/lib/common/props.d.ts +45 -0
  35. package/dist/lib/common/props.d.ts.map +1 -0
  36. package/dist/lib/common/props.js +360 -0
  37. package/dist/lib/common/props.js.map +1 -0
  38. package/dist/lib/triggers/new-follower-on-account.d.ts +19 -0
  39. package/dist/lib/triggers/new-follower-on-account.d.ts.map +1 -0
  40. package/dist/lib/triggers/new-follower-on-account.js +116 -0
  41. package/dist/lib/triggers/new-follower-on-account.js.map +1 -0
  42. package/dist/lib/triggers/new-post.d.ts +75 -0
  43. package/dist/lib/triggers/new-post.d.ts.map +1 -0
  44. package/dist/lib/triggers/new-post.js +214 -0
  45. package/dist/lib/triggers/new-post.js.map +1 -0
  46. package/dist/lib/triggers/new-posts-by-author.d.ts +59 -0
  47. package/dist/lib/triggers/new-posts-by-author.d.ts.map +1 -0
  48. package/dist/lib/triggers/new-posts-by-author.js +243 -0
  49. package/dist/lib/triggers/new-posts-by-author.js.map +1 -0
  50. package/dist/lib/triggers/new-timeline-posts.d.ts +19 -0
  51. package/dist/lib/triggers/new-timeline-posts.d.ts.map +1 -0
  52. package/dist/lib/triggers/new-timeline-posts.js +143 -0
  53. package/dist/lib/triggers/new-timeline-posts.js.map +1 -0
  54. package/package.json +48 -0
  55. package/src/index.ts +25 -0
  56. package/src/lib/actions/create-post.ts +444 -0
  57. package/src/lib/actions/find-post.ts +76 -0
  58. package/src/lib/actions/find-thread.ts +121 -0
  59. package/src/lib/actions/like-post.ts +128 -0
  60. package/src/lib/actions/repost.ts +132 -0
  61. package/src/lib/common/auth.ts +74 -0
  62. package/src/lib/common/client.ts +42 -0
  63. package/src/lib/common/props.ts +392 -0
  64. package/src/lib/triggers/new-follower-on-account.ts +112 -0
  65. package/src/lib/triggers/new-post.ts +225 -0
  66. package/src/lib/triggers/new-posts-by-author.ts +252 -0
  67. package/src/lib/triggers/new-timeline-posts.ts +141 -0
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.newTimelinePosts = void 0;
4
+ const tslib_1 = require("tslib");
5
+ // Vendored from Activepieces packages/pieces/community/bluesky/src/lib/triggers/new-timeline-posts.ts (MIT).
6
+ // Generated by packages/scripts/vendor-ap.mjs - do not edit by hand.
7
+ const pieces_framework_1 = require("@agnocon/pieces-framework");
8
+ const pieces_common_1 = require("@agnocon/pieces-common");
9
+ const auth_1 = require("../common/auth");
10
+ const client_1 = require("../common/client");
11
+ const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
12
+ const polling = {
13
+ strategy: pieces_common_1.DedupeStrategy.TIMEBASED,
14
+ items: (_a) => tslib_1.__awaiter(void 0, [_a], void 0, function* ({ auth, lastFetchEpochMS }) {
15
+ var _b;
16
+ try {
17
+ const agent = yield (0, client_1.createBlueskyAgent)(auth.props);
18
+ const response = yield agent.getTimeline({
19
+ limit: 50
20
+ });
21
+ if (!((_b = response.data) === null || _b === void 0 ? void 0 : _b.feed) || !Array.isArray(response.data.feed)) {
22
+ return [];
23
+ }
24
+ const cutoffTime = lastFetchEpochMS || 0;
25
+ return response.data.feed
26
+ .filter((item) => {
27
+ if (!item.post || !item.post.indexedAt)
28
+ return false;
29
+ const postTime = (0, dayjs_1.default)(item.post.indexedAt).valueOf();
30
+ return postTime > cutoffTime;
31
+ })
32
+ .map((item) => {
33
+ var _a, _b, _c;
34
+ return ({
35
+ epochMilliSeconds: (0, dayjs_1.default)(item.post.indexedAt).valueOf(),
36
+ data: {
37
+ uri: item.post.uri,
38
+ cid: item.post.cid,
39
+ author: item.post.author,
40
+ record: item.post.record,
41
+ indexedAt: item.post.indexedAt,
42
+ replyCount: item.post.replyCount || 0,
43
+ repostCount: item.post.repostCount || 0,
44
+ likeCount: item.post.likeCount || 0,
45
+ quoteCount: item.post.quoteCount || 0,
46
+ labels: item.post.labels || [],
47
+ viewer: item.post.viewer || {},
48
+ embed: item.post.embed || null,
49
+ reason: item.reason || null,
50
+ reply: item.reply || null,
51
+ feedContext: {
52
+ isRepost: !!item.reason,
53
+ repostBy: ((_a = item.reason) === null || _a === void 0 ? void 0 : _a.by) || null,
54
+ isReply: !!item.reply,
55
+ replyToPost: ((_b = item.reply) === null || _b === void 0 ? void 0 : _b.parent) || null,
56
+ replyToRoot: ((_c = item.reply) === null || _c === void 0 ? void 0 : _c.root) || null
57
+ }
58
+ }
59
+ });
60
+ })
61
+ .sort((a, b) => b.epochMilliSeconds - a.epochMilliSeconds);
62
+ }
63
+ catch (error) {
64
+ console.warn('Failed to fetch timeline posts:', error instanceof Error ? error.message : 'Unknown error');
65
+ return [];
66
+ }
67
+ })
68
+ };
69
+ exports.newTimelinePosts = (0, pieces_framework_1.createTrigger)({
70
+ auth: auth_1.blueskyAuth,
71
+ name: 'newTimelinePosts',
72
+ displayName: 'New Timeline Posts',
73
+ description: 'Triggers when new posts appear in your timeline',
74
+ aiMetadata: {
75
+ description: 'Fires when a new post appears in the authenticated account\'s home timeline (posts and reposts from accounts it follows); each event represents one such timeline item.',
76
+ },
77
+ props: {},
78
+ sampleData: {
79
+ uri: 'at://did:plc:example123/app.bsky.feed.post/example456',
80
+ cid: 'bafyreib2rxk3vcfbqij7y6kzgy4knknc7ff4t5jn2m5fbn6jdl7czfqyqe',
81
+ author: {
82
+ did: 'did:plc:example123',
83
+ handle: 'author.bsky.social',
84
+ displayName: 'Example Author',
85
+ avatar: 'https://cdn.bsky.app/img/avatar/plain/did:plc:example123/example@jpeg',
86
+ viewer: {
87
+ muted: false,
88
+ blockedBy: false,
89
+ following: 'at://did:plc:user456/app.bsky.graph.follow/example789'
90
+ }
91
+ },
92
+ record: {
93
+ $type: 'app.bsky.feed.post',
94
+ createdAt: '2024-01-01T12:00:00.000Z',
95
+ text: 'This is a new post in your timeline!',
96
+ langs: ['en']
97
+ },
98
+ indexedAt: '2024-01-01T12:00:00.000Z',
99
+ replyCount: 3,
100
+ repostCount: 8,
101
+ likeCount: 15,
102
+ quoteCount: 2,
103
+ labels: [],
104
+ viewer: {
105
+ repost: null,
106
+ like: null
107
+ },
108
+ embed: null,
109
+ reason: null,
110
+ reply: null,
111
+ feedContext: {
112
+ isRepost: false,
113
+ repostBy: null,
114
+ isReply: false,
115
+ replyToPost: null,
116
+ replyToRoot: null
117
+ }
118
+ },
119
+ type: pieces_framework_1.TriggerStrategy.POLLING,
120
+ test(context) {
121
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
122
+ return yield pieces_common_1.pollingHelper.test(polling, context);
123
+ });
124
+ },
125
+ onEnable(context) {
126
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
127
+ const { store, auth, propsValue } = context;
128
+ yield pieces_common_1.pollingHelper.onEnable(polling, { store, auth, propsValue });
129
+ });
130
+ },
131
+ onDisable(context) {
132
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
133
+ const { store, auth, propsValue } = context;
134
+ yield pieces_common_1.pollingHelper.onDisable(polling, { store, auth, propsValue });
135
+ });
136
+ },
137
+ run(context) {
138
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
139
+ return yield pieces_common_1.pollingHelper.poll(polling, context);
140
+ });
141
+ },
142
+ });
143
+ //# sourceMappingURL=new-timeline-posts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new-timeline-posts.js","sourceRoot":"","sources":["../../../src/lib/triggers/new-timeline-posts.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@agnocon/piece-bluesky",
3
+ "version": "0.1.6",
4
+ "description": "AgnoCon bluesky connector",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "require": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "LICENSE.MIT-AP"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "vendor": "node ../scripts/vendor-ap.mjs piece-bluesky"
22
+ },
23
+ "dependencies": {
24
+ "@agnocon/core-utils": "0.2.0",
25
+ "@agnocon/piece-types": "0.3.1",
26
+ "@agnocon/pieces-common": "0.12.8",
27
+ "@agnocon/pieces-framework": "0.36.0",
28
+ "tslib": "2.6.2",
29
+ "@atproto/api": "0.16.0",
30
+ "dayjs": "1.11.9"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "20.17.6",
34
+ "typescript": "5.5.4"
35
+ },
36
+ "license": "MIT",
37
+ "agnocon": {
38
+ "originVersion": "0.1.6"
39
+ },
40
+ "publishConfig": {
41
+ "access": "restricted",
42
+ "tag": "latest"
43
+ },
44
+ "keywords": [
45
+ "agnocon",
46
+ "connector"
47
+ ]
48
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { createPiece, PieceAuth } from '@agnocon/pieces-framework';
2
+ import { PieceCategory } from '@agnocon/pieces-framework';
3
+ import { blueskyAuth } from './lib/common/auth';
4
+ import { createPost } from './lib/actions/create-post';
5
+ import { likePost } from './lib/actions/like-post';
6
+ import { repostPost } from './lib/actions/repost';
7
+ import { findPost } from './lib/actions/find-post';
8
+ import { findThread } from './lib/actions/find-thread';
9
+ import { newPostsByAuthor } from './lib/triggers/new-posts-by-author';
10
+ import { newFollowerOnAccount } from './lib/triggers/new-follower-on-account';
11
+ import { newTimelinePosts } from './lib/triggers/new-timeline-posts';
12
+ import { newPost } from './lib/triggers/new-post';
13
+ export { blueskyAuth } from './lib/common/auth';
14
+ export { createBlueskyAgent } from './lib/common/client';
15
+
16
+ export const bluesky = createPiece({
17
+ displayName: 'Bluesky',
18
+ auth: blueskyAuth,
19
+ minimumSupportedRelease: '0.36.1',
20
+ logoUrl: 'https://cdn.activepieces.com/pieces/bluesky.png',
21
+ authors: ['Sanket6652'],
22
+ categories: [PieceCategory.COMMUNICATION],
23
+ actions: [createPost, likePost, repostPost, findPost, findThread],
24
+ triggers: [newPostsByAuthor, newFollowerOnAccount, newTimelinePosts, newPost],
25
+ });
@@ -0,0 +1,444 @@
1
+ import { createAction, Property } from '@agnocon/pieces-framework';
2
+ import { blueskyAuth } from '../common/auth';
3
+ import { createBlueskyAgent } from '../common/client';
4
+ import { RichText } from '@atproto/api';
5
+ import {
6
+ postTextProperty,
7
+ postTypeDropdown,
8
+ simpleLanguageDropdown,
9
+ imageUrlsProperty,
10
+ imageDescriptionsProperty,
11
+ linkUrlProperty,
12
+ replyToPostProperty,
13
+ contentWarningDropdown,
14
+ audienceDropdown
15
+ } from '../common/props';
16
+
17
+ const videoUrlProperty = Property.ShortText({
18
+ displayName: 'Video URL',
19
+ description: 'Link to video file (MP4, max 100MB)',
20
+ required: false,
21
+ });
22
+
23
+ const videoAltTextProperty = Property.LongText({
24
+ displayName: 'Video Description',
25
+ description: 'Describe the video for accessibility',
26
+ required: false,
27
+ });
28
+
29
+ const videoCaptionsProperty = Property.Array({
30
+ displayName: 'Video Captions',
31
+ description: 'Caption file URLs (optional)',
32
+ required: false,
33
+ });
34
+
35
+ const threadContentProperty = Property.Array({
36
+ displayName: 'Thread Posts',
37
+ description: 'Create additional connected posts',
38
+ required: false,
39
+ });
40
+
41
+ const additionalHashtagsProperty = Property.ShortText({
42
+ displayName: 'Hashtags',
43
+ description: 'Add hashtags (e.g., tech,bluesky)',
44
+ required: false,
45
+ });
46
+
47
+ async function parsePostUrl(url: string, agent: any): Promise<string> {
48
+ if (url.startsWith('at://')) {
49
+ return url;
50
+ }
51
+
52
+ const urlMatch = url.match(/https?:\/\/bsky\.app\/profile\/([^/]+)\/post\/([^/?]+)/);
53
+ if (urlMatch) {
54
+ const handle = urlMatch[1];
55
+ const postId = urlMatch[2];
56
+
57
+ const didDoc = await agent.resolveHandle({ handle });
58
+ return `at://${didDoc.data.did}/app.bsky.feed.post/${postId}`;
59
+ }
60
+
61
+ throw new Error('Invalid post URL format. Please use a valid Bluesky post URL.');
62
+ }
63
+
64
+ async function getReplyRefs(postUri: string, agent: any): Promise<any> {
65
+ try {
66
+ const postThread = await agent.getPostThread({ uri: postUri });
67
+ const post = postThread.thread.post;
68
+
69
+ if (!post) {
70
+ throw new Error('Post not found');
71
+ }
72
+
73
+ const parentReply = post.record.reply;
74
+
75
+ return {
76
+ root: parentReply?.root || { uri: post.uri, cid: post.cid },
77
+ parent: { uri: post.uri, cid: post.cid },
78
+ };
79
+ } catch (error) {
80
+ throw new Error(`Failed to resolve reply: ${error instanceof Error ? error.message : 'Unknown error'}`);
81
+ }
82
+ }
83
+
84
+ async function createEnhancedLinkEmbed(url: string, agent: any): Promise<any> {
85
+ try {
86
+ const response = await fetch(url, {
87
+ headers: {
88
+ 'User-Agent': 'Mozilla/5.0 (compatible; Activepieces-Bot/1.0)',
89
+ },
90
+ });
91
+
92
+ if (!response.ok) {
93
+ throw new Error(`HTTP ${response.status}`);
94
+ }
95
+
96
+ const html = await response.text();
97
+
98
+ const titleMatch = html.match(/<meta\s+property="og:title"\s+content="([^"]*)"[^>]*>/i) ||
99
+ html.match(/<title[^>]*>([^<]*)<\/title>/i);
100
+ const title = titleMatch ? titleMatch[1].trim() : url;
101
+
102
+ const descMatch = html.match(/<meta\s+property="og:description"\s+content="([^"]*)"[^>]*>/i) ||
103
+ html.match(/<meta\s+name="description"\s+content="([^"]*)"[^>]*>/i);
104
+ const description = descMatch ? descMatch[1].trim() : '';
105
+
106
+ const imageMatch = html.match(/<meta\s+property="og:image"\s+content="([^"]*)"[^>]*>/i);
107
+
108
+ const external: any = {
109
+ uri: url,
110
+ title: title.length > 0 ? title : url,
111
+ description: description.length > 0 ? description : 'Shared link',
112
+ };
113
+
114
+ if (imageMatch && imageMatch[1]) {
115
+ try {
116
+ let imageUrl = imageMatch[1];
117
+
118
+ if (imageUrl.startsWith('/')) {
119
+ const urlObj = new URL(url);
120
+ imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;
121
+ } else if (!imageUrl.startsWith('http')) {
122
+ imageUrl = new URL(imageUrl, url).href;
123
+ }
124
+
125
+ const imageResponse = await fetch(imageUrl);
126
+ if (imageResponse.ok) {
127
+ const imageBlob = await imageResponse.blob();
128
+
129
+ if (imageBlob.size <= 1000000 && imageBlob.type.startsWith('image/')) {
130
+ const thumbResponse = await agent.uploadBlob(imageBlob, {
131
+ encoding: imageBlob.type,
132
+ });
133
+ external.thumb = thumbResponse.data.blob;
134
+ }
135
+ }
136
+ } catch (thumbError) {
137
+ console.warn('Failed to upload link thumbnail:', thumbError);
138
+ }
139
+ }
140
+
141
+ return {
142
+ $type: 'app.bsky.embed.external',
143
+ external,
144
+ };
145
+ } catch (error) {
146
+ return {
147
+ $type: 'app.bsky.embed.external',
148
+ external: {
149
+ uri: url,
150
+ title: url,
151
+ description: 'Shared link',
152
+ },
153
+ };
154
+ }
155
+ }
156
+
157
+ export const createPost = createAction({
158
+ auth: blueskyAuth,
159
+ name: 'createPost',
160
+ displayName: 'Create Post',
161
+ description: 'Create a new post on Bluesky',
162
+ audience: 'both',
163
+ aiMetadata: {
164
+ description: 'Publishes a new post to the authenticated Bluesky account, with optional images, video, an external link card, content-warning labels, and chained thread replies. Use to broadcast content on Bluesky; provide the post text (max 300 characters after hashtags/links are expanded). Not idempotent — each call creates a brand-new post.',
165
+ idempotent: false,
166
+ },
167
+ props: {
168
+ postType: postTypeDropdown,
169
+ text: postTextProperty,
170
+ language: simpleLanguageDropdown,
171
+ imageUrls: imageUrlsProperty,
172
+ imageDescriptions: imageDescriptionsProperty,
173
+ videoUrl: videoUrlProperty,
174
+ videoAltText: videoAltTextProperty,
175
+ videoCaptions: videoCaptionsProperty,
176
+ linkUrl: linkUrlProperty,
177
+ replyToPost: replyToPostProperty,
178
+ threadContent: threadContentProperty,
179
+ additionalHashtags: additionalHashtagsProperty,
180
+ contentWarnings: contentWarningDropdown,
181
+ audience: audienceDropdown,
182
+ },
183
+ async run({ auth, propsValue }) {
184
+ const {
185
+ text,
186
+ language,
187
+ imageUrls,
188
+ imageDescriptions,
189
+ videoUrl,
190
+ videoAltText,
191
+ videoCaptions,
192
+ linkUrl,
193
+ replyToPost,
194
+ threadContent,
195
+ additionalHashtags,
196
+ contentWarnings,
197
+ audience
198
+ } = propsValue;
199
+
200
+ try {
201
+ const agent = await createBlueskyAgent(auth.props);
202
+
203
+ let processedText = text;
204
+ if (additionalHashtags && additionalHashtags.trim()) {
205
+ const hashtags = additionalHashtags
206
+ .split(',')
207
+ .map(tag => tag.trim().startsWith('#') ? tag.trim() : `#${tag.trim()}`)
208
+ .filter(tag => tag.length > 1)
209
+ .join(' ');
210
+
211
+ if (hashtags) {
212
+ processedText += ` ${hashtags}`;
213
+ }
214
+ }
215
+
216
+ const richText = new RichText({ text: processedText });
217
+ await richText.detectFacets(agent);
218
+
219
+ if (richText.length > 300) {
220
+ throw new Error('Post text cannot exceed 300 characters');
221
+ }
222
+
223
+ const postRecord: any = {
224
+ text: richText.text,
225
+ facets: richText.facets,
226
+ createdAt: new Date().toISOString(),
227
+ };
228
+
229
+ if (additionalHashtags && additionalHashtags.trim()) {
230
+ const tagList = additionalHashtags
231
+ .split(',')
232
+ .map(tag => tag.trim().replace(/^#/, ''))
233
+ .filter(tag => tag.length > 0);
234
+
235
+ if (tagList.length > 0) {
236
+ postRecord.tags = tagList;
237
+ }
238
+ }
239
+
240
+ if (contentWarnings && contentWarnings.length > 0) {
241
+ postRecord.labels = {
242
+ $type: 'com.atproto.label.defs#selfLabels',
243
+ values: contentWarnings.map((warning: string) => ({ val: warning })),
244
+ };
245
+ }
246
+
247
+ if (language && language !== 'other') {
248
+ postRecord.langs = [language];
249
+ }
250
+
251
+ if (replyToPost && replyToPost.trim()) {
252
+ const postUri = await parsePostUrl(replyToPost, agent);
253
+ postRecord.reply = await getReplyRefs(postUri, agent);
254
+ }
255
+
256
+ if (videoUrl && videoUrl.trim()) {
257
+ try {
258
+ const videoResponse = await fetch(videoUrl);
259
+ if (!videoResponse.ok) {
260
+ throw new Error(`Failed to fetch video from ${videoUrl}`);
261
+ }
262
+
263
+ const videoBlob = await videoResponse.blob();
264
+
265
+ if (!videoBlob.type.startsWith('video/')) {
266
+ throw new Error('File must be a video format');
267
+ }
268
+
269
+ if (videoBlob.size > 100000000) {
270
+ throw new Error(`Video is too large (max 100MB), got ${Math.round(videoBlob.size / 1000000)}MB`);
271
+ }
272
+
273
+ const videoBlobResponse = await agent.uploadBlob(videoBlob, {
274
+ encoding: videoBlob.type,
275
+ });
276
+
277
+ const captionObjects = [];
278
+ if (videoCaptions && videoCaptions.length > 0) {
279
+ for (let i = 0; i < videoCaptions.length; i++) {
280
+ const captionUrl = videoCaptions[i] as string;
281
+ try {
282
+ const captionResponse = await fetch(captionUrl);
283
+ if (!captionResponse.ok) continue;
284
+
285
+ const captionBlob = await captionResponse.blob();
286
+ const captionBlobResponse = await agent.uploadBlob(captionBlob, {
287
+ encoding: 'text/vtt',
288
+ });
289
+
290
+ captionObjects.push({
291
+ lang: 'en',
292
+ file: captionBlobResponse.data.blob,
293
+ });
294
+ } catch (error) {
295
+ console.warn(`Failed to upload caption ${i + 1}:`, error);
296
+ }
297
+ }
298
+ }
299
+
300
+ const videoEmbed: any = {
301
+ $type: 'app.bsky.embed.video',
302
+ video: videoBlobResponse.data.blob,
303
+ };
304
+
305
+ if (videoAltText && videoAltText.trim()) {
306
+ videoEmbed.alt = videoAltText.trim();
307
+ }
308
+
309
+ if (captionObjects.length > 0) {
310
+ videoEmbed.captions = captionObjects;
311
+ }
312
+
313
+ postRecord.embed = videoEmbed;
314
+ } catch (error) {
315
+ throw new Error(`Failed to process video: ${error instanceof Error ? error.message : 'Unknown error'}`);
316
+ }
317
+ }
318
+ else if (imageUrls && imageUrls.length > 0) {
319
+ if (imageUrls.length > 4) {
320
+ throw new Error('Maximum of 4 images allowed per post');
321
+ }
322
+
323
+ const images = [];
324
+ for (let i = 0; i < imageUrls.length; i++) {
325
+ const imageUrl = imageUrls[i] as string;
326
+ const altText = (imageDescriptions?.[i] as string) || '';
327
+
328
+ try {
329
+ const imageResponse = await fetch(imageUrl);
330
+ if (!imageResponse.ok) {
331
+ throw new Error(`Failed to fetch image from ${imageUrl}`);
332
+ }
333
+
334
+ const imageBlob = await imageResponse.blob();
335
+
336
+ if (imageBlob.size > 1000000) {
337
+ throw new Error(`Image at ${imageUrl} is too large (max 1MB)`);
338
+ }
339
+
340
+ const blobResponse = await agent.uploadBlob(imageBlob, {
341
+ encoding: imageBlob.type,
342
+ });
343
+
344
+ images.push({
345
+ alt: altText,
346
+ image: blobResponse.data.blob,
347
+ });
348
+ } catch (error) {
349
+ throw new Error(`Failed to upload image ${i + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`);
350
+ }
351
+ }
352
+
353
+ postRecord.embed = {
354
+ $type: 'app.bsky.embed.images',
355
+ images,
356
+ };
357
+ }
358
+
359
+ else if (linkUrl && linkUrl.trim()) {
360
+ try {
361
+ const linkEmbed = await createEnhancedLinkEmbed(linkUrl, agent);
362
+ postRecord.embed = linkEmbed;
363
+ } catch (error) {
364
+ console.warn('Failed to fetch link metadata, using basic embed:', error);
365
+ postRecord.embed = {
366
+ $type: 'app.bsky.embed.external',
367
+ external: {
368
+ uri: linkUrl,
369
+ title: linkUrl,
370
+ description: 'Shared link',
371
+ },
372
+ };
373
+ }
374
+ }
375
+
376
+ const mainPostResponse = await agent.post(postRecord);
377
+ const createdPosts = [mainPostResponse];
378
+
379
+ if (threadContent && threadContent.length > 0) {
380
+ let previousPost = mainPostResponse;
381
+
382
+ for (let i = 0; i < threadContent.length; i++) {
383
+ const threadText = threadContent[i] as string;
384
+ if (!threadText || !threadText.trim()) continue;
385
+
386
+ try {
387
+ const threadRichText = new RichText({ text: threadText });
388
+ await threadRichText.detectFacets(agent);
389
+
390
+ if (threadRichText.length > 300) {
391
+ console.warn(`Thread post ${i + 1} exceeds 300 characters, skipping`);
392
+ continue;
393
+ }
394
+
395
+ const threadPostRecord: any = {
396
+ text: threadRichText.text,
397
+ facets: threadRichText.facets,
398
+ createdAt: new Date().toISOString(),
399
+ reply: {
400
+ root: { uri: mainPostResponse.uri, cid: mainPostResponse.cid },
401
+ parent: { uri: previousPost.uri, cid: previousPost.cid },
402
+ },
403
+ };
404
+
405
+ if (language && language !== 'other') {
406
+ threadPostRecord.langs = [language];
407
+ }
408
+
409
+ if (contentWarnings && contentWarnings.length > 0) {
410
+ threadPostRecord.labels = {
411
+ $type: 'com.atproto.label.defs#selfLabels',
412
+ values: contentWarnings.map((warning: string) => ({ val: warning })),
413
+ };
414
+ }
415
+
416
+ const threadResponse = await agent.post(threadPostRecord);
417
+ createdPosts.push(threadResponse);
418
+ previousPost = threadResponse;
419
+
420
+ await new Promise(resolve => setTimeout(resolve, 100));
421
+ } catch (threadError) {
422
+ console.warn(`Failed to create thread post ${i + 1}:`, threadError);
423
+ }
424
+ }
425
+ }
426
+
427
+ return {
428
+ success: true,
429
+ mainPost: {
430
+ uri: mainPostResponse.uri,
431
+ cid: mainPostResponse.cid,
432
+ },
433
+ threadPosts: createdPosts.slice(1).map(post => ({
434
+ uri: post.uri,
435
+ cid: post.cid,
436
+ })),
437
+ totalPosts: createdPosts.length,
438
+ record: postRecord,
439
+ };
440
+ } catch (error) {
441
+ throw new Error(`Failed to create post: ${error instanceof Error ? error.message : 'Unknown error'}`);
442
+ }
443
+ },
444
+ });