@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.
- package/LICENSE.MIT-AP +24 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/actions/create-post.d.ts +21 -0
- package/dist/lib/actions/create-post.d.ts.map +1 -0
- package/dist/lib/actions/create-post.js +379 -0
- package/dist/lib/actions/create-post.js.map +1 -0
- package/dist/lib/actions/find-post.d.ts +8 -0
- package/dist/lib/actions/find-post.d.ts.map +1 -0
- package/dist/lib/actions/find-post.js +77 -0
- package/dist/lib/actions/find-post.js.map +1 -0
- package/dist/lib/actions/find-thread.d.ts +10 -0
- package/dist/lib/actions/find-thread.d.ts.map +1 -0
- package/dist/lib/actions/find-thread.js +117 -0
- package/dist/lib/actions/find-thread.js.map +1 -0
- package/dist/lib/actions/like-post.d.ts +14 -0
- package/dist/lib/actions/like-post.d.ts.map +1 -0
- package/dist/lib/actions/like-post.js +129 -0
- package/dist/lib/actions/like-post.js.map +1 -0
- package/dist/lib/actions/repost.d.ts +14 -0
- package/dist/lib/actions/repost.d.ts.map +1 -0
- package/dist/lib/actions/repost.js +132 -0
- package/dist/lib/actions/repost.js.map +1 -0
- package/dist/lib/common/auth.d.ts +11 -0
- package/dist/lib/common/auth.d.ts.map +1 -0
- package/dist/lib/common/auth.js +75 -0
- package/dist/lib/common/auth.js.map +1 -0
- package/dist/lib/common/client.d.ts +8 -0
- package/dist/lib/common/client.d.ts.map +1 -0
- package/dist/lib/common/client.js +47 -0
- package/dist/lib/common/client.js.map +1 -0
- package/dist/lib/common/props.d.ts +45 -0
- package/dist/lib/common/props.d.ts.map +1 -0
- package/dist/lib/common/props.js +360 -0
- package/dist/lib/common/props.js.map +1 -0
- package/dist/lib/triggers/new-follower-on-account.d.ts +19 -0
- package/dist/lib/triggers/new-follower-on-account.d.ts.map +1 -0
- package/dist/lib/triggers/new-follower-on-account.js +116 -0
- package/dist/lib/triggers/new-follower-on-account.js.map +1 -0
- package/dist/lib/triggers/new-post.d.ts +75 -0
- package/dist/lib/triggers/new-post.d.ts.map +1 -0
- package/dist/lib/triggers/new-post.js +214 -0
- package/dist/lib/triggers/new-post.js.map +1 -0
- package/dist/lib/triggers/new-posts-by-author.d.ts +59 -0
- package/dist/lib/triggers/new-posts-by-author.d.ts.map +1 -0
- package/dist/lib/triggers/new-posts-by-author.js +243 -0
- package/dist/lib/triggers/new-posts-by-author.js.map +1 -0
- package/dist/lib/triggers/new-timeline-posts.d.ts +19 -0
- package/dist/lib/triggers/new-timeline-posts.d.ts.map +1 -0
- package/dist/lib/triggers/new-timeline-posts.js +143 -0
- package/dist/lib/triggers/new-timeline-posts.js.map +1 -0
- package/package.json +48 -0
- package/src/index.ts +25 -0
- package/src/lib/actions/create-post.ts +444 -0
- package/src/lib/actions/find-post.ts +76 -0
- package/src/lib/actions/find-thread.ts +121 -0
- package/src/lib/actions/like-post.ts +128 -0
- package/src/lib/actions/repost.ts +132 -0
- package/src/lib/common/auth.ts +74 -0
- package/src/lib/common/client.ts +42 -0
- package/src/lib/common/props.ts +392 -0
- package/src/lib/triggers/new-follower-on-account.ts +112 -0
- package/src/lib/triggers/new-post.ts +225 -0
- package/src/lib/triggers/new-posts-by-author.ts +252 -0
- package/src/lib/triggers/new-timeline-posts.ts +141 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { Property } from '@agnocon/pieces-framework';
|
|
2
|
+
import { blueskyAuth } from './auth';
|
|
3
|
+
|
|
4
|
+
export const languageOptions = [
|
|
5
|
+
{ label: 'English', value: 'en' },
|
|
6
|
+
{ label: 'Spanish', value: 'es' },
|
|
7
|
+
{ label: 'French', value: 'fr' },
|
|
8
|
+
{ label: 'German', value: 'de' },
|
|
9
|
+
{ label: 'Italian', value: 'it' },
|
|
10
|
+
{ label: 'Portuguese', value: 'pt' },
|
|
11
|
+
{ label: 'Russian', value: 'ru' },
|
|
12
|
+
{ label: 'Japanese', value: 'ja' },
|
|
13
|
+
{ label: 'Korean', value: 'ko' },
|
|
14
|
+
{ label: 'Chinese (Simplified)', value: 'zh' },
|
|
15
|
+
{ label: 'Chinese (Traditional)', value: 'zh-TW' },
|
|
16
|
+
{ label: 'Arabic', value: 'ar' },
|
|
17
|
+
{ label: 'Hindi', value: 'hi' },
|
|
18
|
+
{ label: 'Dutch', value: 'nl' },
|
|
19
|
+
{ label: 'Swedish', value: 'sv' },
|
|
20
|
+
{ label: 'Norwegian', value: 'no' },
|
|
21
|
+
{ label: 'Danish', value: 'da' },
|
|
22
|
+
{ label: 'Finnish', value: 'fi' },
|
|
23
|
+
{ label: 'Polish', value: 'pl' },
|
|
24
|
+
{ label: 'Czech', value: 'cs' },
|
|
25
|
+
{ label: 'Hungarian', value: 'hu' },
|
|
26
|
+
{ label: 'Romanian', value: 'ro' },
|
|
27
|
+
{ label: 'Greek', value: 'el' },
|
|
28
|
+
{ label: 'Turkish', value: 'tr' },
|
|
29
|
+
{ label: 'Hebrew', value: 'he' },
|
|
30
|
+
{ label: 'Thai', value: 'th' },
|
|
31
|
+
{ label: 'Vietnamese', value: 'vi' },
|
|
32
|
+
{ label: 'Indonesian', value: 'id' },
|
|
33
|
+
{ label: 'Malay', value: 'ms' },
|
|
34
|
+
{ label: 'Filipino', value: 'fil' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
export const languageDropdown = Property.StaticDropdown({
|
|
39
|
+
displayName: 'Language',
|
|
40
|
+
description: 'Select the language for the post',
|
|
41
|
+
required: false,
|
|
42
|
+
options: {
|
|
43
|
+
options: languageOptions,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const multiLanguageDropdown = Property.StaticMultiSelectDropdown({
|
|
48
|
+
displayName: 'Languages',
|
|
49
|
+
description: 'Select one or more languages for the post',
|
|
50
|
+
required: false,
|
|
51
|
+
options: {
|
|
52
|
+
options: languageOptions,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const threadDepthDropdown = Property.StaticDropdown({
|
|
57
|
+
displayName: 'Thread Depth',
|
|
58
|
+
description: 'How many levels deep to retrieve replies',
|
|
59
|
+
required: false,
|
|
60
|
+
defaultValue: '10',
|
|
61
|
+
options: {
|
|
62
|
+
options: [
|
|
63
|
+
{ label: '1 level', value: '1' },
|
|
64
|
+
{ label: '2 levels', value: '2' },
|
|
65
|
+
{ label: '3 levels', value: '3' },
|
|
66
|
+
{ label: '5 levels', value: '5' },
|
|
67
|
+
{ label: '10 levels', value: '10' },
|
|
68
|
+
{ label: '20 levels', value: '20' },
|
|
69
|
+
{ label: '50 levels', value: '50' },
|
|
70
|
+
{ label: '100 levels (max)', value: '100' },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const parentHeightDropdown = Property.StaticDropdown({
|
|
76
|
+
displayName: 'Parent Height',
|
|
77
|
+
description: 'How many parent posts to retrieve',
|
|
78
|
+
required: false,
|
|
79
|
+
defaultValue: '3',
|
|
80
|
+
options: {
|
|
81
|
+
options: [
|
|
82
|
+
{ label: 'No parents', value: '0' },
|
|
83
|
+
{ label: '1 parent', value: '1' },
|
|
84
|
+
{ label: '2 parents', value: '2' },
|
|
85
|
+
{ label: '3 parents', value: '3' },
|
|
86
|
+
{ label: '5 parents', value: '5' },
|
|
87
|
+
{ label: '10 parents', value: '10' },
|
|
88
|
+
{ label: '20 parents', value: '20' },
|
|
89
|
+
{ label: 'All parents (80 max)', value: '80' },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const visibilityDropdown = Property.StaticDropdown({
|
|
95
|
+
displayName: 'Visibility',
|
|
96
|
+
description: 'Post visibility setting',
|
|
97
|
+
required: false,
|
|
98
|
+
defaultValue: 'public',
|
|
99
|
+
options: {
|
|
100
|
+
options: [
|
|
101
|
+
{ label: 'Public', value: 'public' },
|
|
102
|
+
{ label: 'Unlisted', value: 'unlisted' },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export const moderationLabelDropdown = Property.StaticMultiSelectDropdown({
|
|
108
|
+
displayName: 'Content Labels',
|
|
109
|
+
description: 'Apply content moderation labels to the post',
|
|
110
|
+
required: false,
|
|
111
|
+
options: {
|
|
112
|
+
options: [
|
|
113
|
+
{ label: 'Adult Content', value: 'adult' },
|
|
114
|
+
{ label: 'Graphic Media', value: 'graphic-media' },
|
|
115
|
+
{ label: 'Nudity', value: 'nudity' },
|
|
116
|
+
{ label: 'Sexual', value: 'sexual' },
|
|
117
|
+
{ label: 'Violence', value: 'violence' },
|
|
118
|
+
{ label: 'Self Harm', value: 'self-harm' },
|
|
119
|
+
{ label: 'Spam', value: 'spam' },
|
|
120
|
+
{ label: 'Impersonation', value: 'impersonation' },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export const postUrlProperty = Property.ShortText({
|
|
126
|
+
displayName: 'Post URL',
|
|
127
|
+
description: 'Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)',
|
|
128
|
+
required: true,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export const userHandleProperty = Property.ShortText({
|
|
132
|
+
displayName: 'User Handle',
|
|
133
|
+
description: 'Bluesky username (e.g., username.bsky.social)',
|
|
134
|
+
required: true,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export const authorSelectionProperty = Property.Dropdown({
|
|
138
|
+
displayName: 'Select Author',
|
|
139
|
+
description: 'Choose from accounts you follow',
|
|
140
|
+
required: false,
|
|
141
|
+
auth: blueskyAuth,
|
|
142
|
+
refreshers: ['auth'],
|
|
143
|
+
options: async ({ auth }) => {
|
|
144
|
+
try {
|
|
145
|
+
const { createBlueskyAgent } = await import('./client');
|
|
146
|
+
if (!auth) return { options: [] };
|
|
147
|
+
const agent = await createBlueskyAgent(auth.props);
|
|
148
|
+
const session = agent.session;
|
|
149
|
+
|
|
150
|
+
if (!session?.did) {
|
|
151
|
+
return { options: [{ label: 'Please authenticate first', value: '' }] };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const followingResponse = await agent.getFollows({
|
|
155
|
+
actor: session.did,
|
|
156
|
+
limit: 100
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const options = followingResponse.data.follows.map((follow: any) => ({
|
|
160
|
+
label: `${follow.displayName || follow.handle} (@${follow.handle})`,
|
|
161
|
+
value: follow.handle
|
|
162
|
+
}));
|
|
163
|
+
|
|
164
|
+
options.unshift({ label: 'Enter handle manually', value: 'manual' });
|
|
165
|
+
|
|
166
|
+
return { options };
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return {
|
|
169
|
+
options: [{ label: 'Error loading following list', value: '' }]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export const postSearchProperty = Property.ShortText({
|
|
176
|
+
displayName: 'Search Posts',
|
|
177
|
+
description: 'Search for posts by keywords, hashtags, or content',
|
|
178
|
+
required: false,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
export const postTextProperty = Property.LongText({
|
|
182
|
+
displayName: 'Post Text',
|
|
183
|
+
description: 'What do you want to post? (Max 300 characters)',
|
|
184
|
+
required: true,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
export const imageUrlsProperty = Property.Array({
|
|
188
|
+
displayName: 'Image URLs',
|
|
189
|
+
description: 'Add up to 4 images by URL',
|
|
190
|
+
required: false,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
export const imageDescriptionsProperty = Property.Array({
|
|
194
|
+
displayName: 'Image Descriptions',
|
|
195
|
+
description: 'Describe each image for accessibility',
|
|
196
|
+
required: false,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
export const linkUrlProperty = Property.ShortText({
|
|
200
|
+
displayName: 'Link to Share',
|
|
201
|
+
description: 'URL to share with your post',
|
|
202
|
+
required: false,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
export const replyToPostProperty = Property.ShortText({
|
|
206
|
+
displayName: 'Reply to Post',
|
|
207
|
+
description: 'URL of post to reply to',
|
|
208
|
+
required: false,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
export const postTypeDropdown = Property.StaticDropdown({
|
|
212
|
+
displayName: 'Post Type',
|
|
213
|
+
description: 'Type of content you\'re sharing',
|
|
214
|
+
required: false,
|
|
215
|
+
defaultValue: 'text',
|
|
216
|
+
options: {
|
|
217
|
+
options: [
|
|
218
|
+
{ label: 'Text Post', value: 'text' },
|
|
219
|
+
{ label: 'Photo Post', value: 'photo' },
|
|
220
|
+
{ label: 'Link Share', value: 'link' },
|
|
221
|
+
{ label: 'Reply', value: 'reply' },
|
|
222
|
+
{ label: 'Repost with Comment', value: 'quote' },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export const simpleLanguageDropdown = Property.StaticDropdown({
|
|
228
|
+
displayName: 'Post Language',
|
|
229
|
+
description: 'Language of your post',
|
|
230
|
+
required: false,
|
|
231
|
+
defaultValue: 'en',
|
|
232
|
+
options: {
|
|
233
|
+
options: [
|
|
234
|
+
{ label: 'English', value: 'en' },
|
|
235
|
+
{ label: 'Spanish', value: 'es' },
|
|
236
|
+
{ label: 'French', value: 'fr' },
|
|
237
|
+
{ label: 'German', value: 'de' },
|
|
238
|
+
{ label: 'Italian', value: 'it' },
|
|
239
|
+
{ label: 'Portuguese', value: 'pt' },
|
|
240
|
+
{ label: 'Japanese', value: 'ja' },
|
|
241
|
+
{ label: 'Korean', value: 'ko' },
|
|
242
|
+
{ label: 'Chinese', value: 'zh' },
|
|
243
|
+
{ label: 'Russian', value: 'ru' },
|
|
244
|
+
{ label: 'Arabic', value: 'ar' },
|
|
245
|
+
{ label: 'Hindi', value: 'hi' },
|
|
246
|
+
{ label: 'Dutch', value: 'nl' },
|
|
247
|
+
{ label: 'Swedish', value: 'sv' },
|
|
248
|
+
{ label: 'Other', value: 'other' },
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
export const replyDepthDropdown = Property.StaticDropdown({
|
|
254
|
+
displayName: 'How many replies to show?',
|
|
255
|
+
description: 'Choose how deep to go into the conversation',
|
|
256
|
+
required: false,
|
|
257
|
+
defaultValue: '10',
|
|
258
|
+
options: {
|
|
259
|
+
options: [
|
|
260
|
+
{ label: 'Just direct replies (1 level)', value: '1' },
|
|
261
|
+
{ label: 'Short conversation (3 levels)', value: '3' },
|
|
262
|
+
{ label: 'Full conversation (10 levels)', value: '10' },
|
|
263
|
+
{ label: 'Entire thread (50+ levels)', value: '50' },
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
export const parentPostsDropdown = Property.StaticDropdown({
|
|
269
|
+
displayName: 'Show conversation context?',
|
|
270
|
+
description: 'Include previous posts in the thread for context',
|
|
271
|
+
required: false,
|
|
272
|
+
defaultValue: '3',
|
|
273
|
+
options: {
|
|
274
|
+
options: [
|
|
275
|
+
{ label: 'No context - just this post', value: '0' },
|
|
276
|
+
{ label: 'Recent context (3 previous posts)', value: '3' },
|
|
277
|
+
{ label: 'Full context (10 previous posts)', value: '10' },
|
|
278
|
+
{ label: 'Complete thread history', value: '80' },
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
export const contentWarningDropdown = Property.StaticMultiSelectDropdown({
|
|
284
|
+
displayName: 'Content Warnings',
|
|
285
|
+
description: 'Add warnings for sensitive content',
|
|
286
|
+
required: false,
|
|
287
|
+
options: {
|
|
288
|
+
options: [
|
|
289
|
+
{ label: 'Adult Content', value: 'adult' },
|
|
290
|
+
{ label: 'Graphic Content', value: 'graphic-media' },
|
|
291
|
+
{ label: 'Sensitive Topic', value: 'sensitive' },
|
|
292
|
+
{ label: 'Violence', value: 'violence' },
|
|
293
|
+
{ label: 'Spam/Promotional', value: 'spam' },
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
export const audienceDropdown = Property.StaticDropdown({
|
|
299
|
+
displayName: 'Audience',
|
|
300
|
+
description: 'Who can see this post',
|
|
301
|
+
required: false,
|
|
302
|
+
defaultValue: 'public',
|
|
303
|
+
options: {
|
|
304
|
+
options: [
|
|
305
|
+
{ label: 'Everyone (Public)', value: 'public' },
|
|
306
|
+
{ label: 'Followers only', value: 'followers' },
|
|
307
|
+
{ label: 'Private/Unlisted', value: 'unlisted' },
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
export function extractPostInfoFromUrl(url: string): { uri?: string; handle?: string; postId?: string } {
|
|
313
|
+
if (url.startsWith('at://')) {
|
|
314
|
+
return { uri: url };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const urlMatch = url.match(/https?:\/\/bsky\.app\/profile\/([^/]+)\/post\/([^/?]+)/);
|
|
318
|
+
if (urlMatch) {
|
|
319
|
+
const handle = urlMatch[1];
|
|
320
|
+
const postId = urlMatch[2];
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
handle: handle,
|
|
324
|
+
postId: postId,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function normalizeHandle(handle: string): string {
|
|
332
|
+
let cleanHandle = handle.replace(/^@/, '');
|
|
333
|
+
|
|
334
|
+
if (!cleanHandle.includes('.')) {
|
|
335
|
+
cleanHandle += '.bsky.social';
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return cleanHandle;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function createSimpleExternalLink(url: string): { uri: string; title: string; description?: string } {
|
|
342
|
+
return {
|
|
343
|
+
uri: url,
|
|
344
|
+
title: url,
|
|
345
|
+
description: 'Shared link'
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
export const postUriProperty = Property.ShortText({
|
|
351
|
+
displayName: 'Post URI (Advanced)',
|
|
352
|
+
description: 'Technical AT-URI format (at://did:plc:xxx/app.bsky.feed.post/xxx) - use Post URL field instead',
|
|
353
|
+
required: false,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
export const externalLinkProperty = Property.Object({
|
|
357
|
+
displayName: 'External Link (Advanced)',
|
|
358
|
+
description: 'Manual link object - use "Link to Share" field instead',
|
|
359
|
+
required: false,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
export const replyToProperty = Property.Object({
|
|
363
|
+
displayName: 'Reply To (Advanced)',
|
|
364
|
+
description: 'Manual reply object - use "Reply to Post" field instead',
|
|
365
|
+
required: false,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
export async function parseBlueskyUrl(url: string, agent: any): Promise<string> {
|
|
371
|
+
if (url.startsWith('at://')) {
|
|
372
|
+
if (!url.match(/^at:\/\/did:plc:[a-z0-9]+\/app\.bsky\.feed\.post\/[a-z0-9]+$/)) {
|
|
373
|
+
throw new Error('Invalid AT-URI format');
|
|
374
|
+
}
|
|
375
|
+
return url;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const urlMatch = url.match(/https?:\/\/bsky\.app\/profile\/([^/]+)\/post\/([^/?]+)/);
|
|
379
|
+
if (urlMatch) {
|
|
380
|
+
const handle = urlMatch[1];
|
|
381
|
+
const postId = urlMatch[2];
|
|
382
|
+
|
|
383
|
+
if (!handle.includes('.') && !handle.includes('@')) {
|
|
384
|
+
throw new Error('Invalid handle format in URL');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const didDoc = await agent.resolveHandle({ handle: handle.replace('@', '') });
|
|
388
|
+
return `at://${didDoc.data.did}/app.bsky.feed.post/${postId}`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
throw new Error('Invalid post URL format. Please use a valid Bluesky post URL or AT-URI.');
|
|
392
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createTrigger, TriggerStrategy, PiecePropValueSchema, AppConnectionValueForAuthProperty } from '@agnocon/pieces-framework';
|
|
2
|
+
import { DedupeStrategy, Polling, pollingHelper } from '@agnocon/pieces-common';
|
|
3
|
+
import { blueskyAuth } from '../common/auth';
|
|
4
|
+
import { createBlueskyAgent } from '../common/client';
|
|
5
|
+
import dayjs from 'dayjs';
|
|
6
|
+
|
|
7
|
+
const polling: Polling<AppConnectionValueForAuthProperty<typeof blueskyAuth>, Record<string, never>> = {
|
|
8
|
+
strategy: DedupeStrategy.TIMEBASED,
|
|
9
|
+
items: async ({ auth, lastFetchEpochMS }) => {
|
|
10
|
+
try {
|
|
11
|
+
const agent = await createBlueskyAgent(auth.props);
|
|
12
|
+
|
|
13
|
+
const session = agent.session;
|
|
14
|
+
if (!session?.did) {
|
|
15
|
+
throw new Error('Could not get user DID from session');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const userDid = session.did;
|
|
19
|
+
|
|
20
|
+
const response = await agent.getFollowers({
|
|
21
|
+
actor: userDid,
|
|
22
|
+
limit: 100
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!response.data?.followers || !Array.isArray(response.data.followers)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const currentTime = Date.now();
|
|
30
|
+
const cutoffTime = lastFetchEpochMS || 0;
|
|
31
|
+
|
|
32
|
+
return response.data.followers
|
|
33
|
+
.filter((follower: any) => {
|
|
34
|
+
const profileTime = follower.indexedAt ? dayjs(follower.indexedAt).valueOf() : currentTime;
|
|
35
|
+
return profileTime > cutoffTime - (24 * 60 * 60 * 1000);
|
|
36
|
+
})
|
|
37
|
+
.map((follower: any) => ({
|
|
38
|
+
epochMilliSeconds: follower.indexedAt ? dayjs(follower.indexedAt).valueOf() : currentTime,
|
|
39
|
+
data: {
|
|
40
|
+
did: follower.did,
|
|
41
|
+
handle: follower.handle,
|
|
42
|
+
displayName: follower.displayName || follower.handle,
|
|
43
|
+
description: follower.description || '',
|
|
44
|
+
avatar: follower.avatar || '',
|
|
45
|
+
banner: follower.banner || '',
|
|
46
|
+
followersCount: follower.followersCount || 0,
|
|
47
|
+
followsCount: follower.followsCount || 0,
|
|
48
|
+
postsCount: follower.postsCount || 0,
|
|
49
|
+
indexedAt: follower.indexedAt || new Date().toISOString(),
|
|
50
|
+
viewer: follower.viewer || {},
|
|
51
|
+
labels: follower.labels || [],
|
|
52
|
+
createdAt: follower.createdAt || null
|
|
53
|
+
}
|
|
54
|
+
}))
|
|
55
|
+
.sort((a: any, b: any) => b.epochMilliSeconds - a.epochMilliSeconds);
|
|
56
|
+
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Error fetching followers:', error);
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const newFollowerOnAccount = createTrigger({
|
|
65
|
+
auth: blueskyAuth,
|
|
66
|
+
name: 'newFollowerOnAccount',
|
|
67
|
+
displayName: 'New Follower on Account',
|
|
68
|
+
description: 'Triggers when someone new follows your Bluesky account',
|
|
69
|
+
aiMetadata: {
|
|
70
|
+
description: 'Fires when a new account follows the authenticated Bluesky account; each event represents one new follower and carries that follower\'s profile details.',
|
|
71
|
+
},
|
|
72
|
+
props: {},
|
|
73
|
+
sampleData: {
|
|
74
|
+
did: 'did:plc:example123',
|
|
75
|
+
handle: 'newfollower.bsky.social',
|
|
76
|
+
displayName: 'New Follower',
|
|
77
|
+
description: 'A new user who just followed your account',
|
|
78
|
+
avatar: 'https://cdn.bsky.app/img/avatar/plain/did:plc:example123/example@jpeg',
|
|
79
|
+
banner: 'https://cdn.bsky.app/img/banner/plain/did:plc:example123/example@jpeg',
|
|
80
|
+
followersCount: 42,
|
|
81
|
+
followsCount: 156,
|
|
82
|
+
postsCount: 78,
|
|
83
|
+
indexedAt: '2024-01-01T12:00:00.000Z',
|
|
84
|
+
viewer: {
|
|
85
|
+
muted: false,
|
|
86
|
+
blockedBy: false,
|
|
87
|
+
following: 'at://did:plc:example123/app.bsky.graph.follow/example456',
|
|
88
|
+
followedBy: 'at://did:plc:example456/app.bsky.graph.follow/example789'
|
|
89
|
+
},
|
|
90
|
+
labels: [],
|
|
91
|
+
createdAt: '2023-06-01T12:00:00.000Z'
|
|
92
|
+
},
|
|
93
|
+
type: TriggerStrategy.POLLING,
|
|
94
|
+
|
|
95
|
+
async test(context) {
|
|
96
|
+
return await pollingHelper.test(polling, context);
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async onEnable(context) {
|
|
100
|
+
const { store, auth, propsValue } = context;
|
|
101
|
+
await pollingHelper.onEnable(polling, { store, auth, propsValue });
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async onDisable(context) {
|
|
105
|
+
const { store, auth, propsValue } = context;
|
|
106
|
+
await pollingHelper.onDisable(polling, { store, auth, propsValue });
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async run(context) {
|
|
110
|
+
return await pollingHelper.poll(polling, context);
|
|
111
|
+
},
|
|
112
|
+
});
|