@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,225 @@
|
|
|
1
|
+
import { createTrigger, TriggerStrategy, PiecePropValueSchema, Property, 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 { simpleLanguageDropdown } from '../common/props';
|
|
6
|
+
import dayjs from 'dayjs';
|
|
7
|
+
|
|
8
|
+
const polling: Polling<AppConnectionValueForAuthProperty<typeof blueskyAuth>, {
|
|
9
|
+
searchQuery: string;
|
|
10
|
+
searchLanguage?: string;
|
|
11
|
+
includeImages?: boolean;
|
|
12
|
+
includeVideos?: boolean;
|
|
13
|
+
sortBy?: string;
|
|
14
|
+
}> = {
|
|
15
|
+
strategy: DedupeStrategy.TIMEBASED,
|
|
16
|
+
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
|
17
|
+
const { searchQuery, searchLanguage, includeImages, includeVideos, sortBy } = propsValue;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
if (!searchQuery || searchQuery.trim().length === 0) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const agent = await createBlueskyAgent(auth.props);
|
|
25
|
+
|
|
26
|
+
const searchParams: any = {
|
|
27
|
+
q: searchQuery.trim(),
|
|
28
|
+
limit: 50,
|
|
29
|
+
sort: sortBy || 'latest'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (searchLanguage && searchLanguage !== 'other') {
|
|
33
|
+
searchParams.lang = searchLanguage;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const response = await agent.api.app.bsky.feed.searchPosts(searchParams);
|
|
37
|
+
|
|
38
|
+
if (!response.data?.posts || !Array.isArray(response.data.posts)) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const cutoffTime = lastFetchEpochMS || 0;
|
|
43
|
+
|
|
44
|
+
return response.data.posts
|
|
45
|
+
.filter((post: any) => {
|
|
46
|
+
if (!post.indexedAt) return false;
|
|
47
|
+
|
|
48
|
+
const postTime = dayjs(post.indexedAt).valueOf();
|
|
49
|
+
if (postTime <= cutoffTime) return false;
|
|
50
|
+
|
|
51
|
+
if (includeImages === false && post.embed?.images) return false;
|
|
52
|
+
if (includeVideos === false && post.embed?.video) return false;
|
|
53
|
+
if (includeImages === true && !post.embed?.images) return false;
|
|
54
|
+
if (includeVideos === true && !post.embed?.video) return false;
|
|
55
|
+
|
|
56
|
+
return true;
|
|
57
|
+
})
|
|
58
|
+
.map((post: any) => ({
|
|
59
|
+
epochMilliSeconds: dayjs(post.indexedAt).valueOf(),
|
|
60
|
+
data: {
|
|
61
|
+
uri: post.uri,
|
|
62
|
+
cid: post.cid,
|
|
63
|
+
author: post.author,
|
|
64
|
+
record: post.record,
|
|
65
|
+
indexedAt: post.indexedAt,
|
|
66
|
+
|
|
67
|
+
replyCount: post.replyCount || 0,
|
|
68
|
+
repostCount: post.repostCount || 0,
|
|
69
|
+
likeCount: post.likeCount || 0,
|
|
70
|
+
quoteCount: post.quoteCount || 0,
|
|
71
|
+
|
|
72
|
+
labels: post.labels || [],
|
|
73
|
+
viewer: post.viewer || {},
|
|
74
|
+
embed: post.embed || null,
|
|
75
|
+
|
|
76
|
+
searchContext: {
|
|
77
|
+
query: searchQuery,
|
|
78
|
+
language: searchLanguage || null,
|
|
79
|
+
matchedTerms: extractMatchedTerms(post.record?.text || '', searchQuery),
|
|
80
|
+
hasImages: !!(post.embed?.images),
|
|
81
|
+
hasVideo: !!(post.embed?.video),
|
|
82
|
+
hasExternalLink: !!(post.embed?.external)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}))
|
|
86
|
+
.sort((a: any, b: any) => b.epochMilliSeconds - a.epochMilliSeconds);
|
|
87
|
+
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn('Failed to search posts:', error instanceof Error ? error.message : 'Unknown error');
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function extractMatchedTerms(text: string, query: string): string[] {
|
|
96
|
+
if (!text || !query) return [];
|
|
97
|
+
|
|
98
|
+
const cleanQuery = query.toLowerCase()
|
|
99
|
+
.replace(/["']/g, '')
|
|
100
|
+
.replace(/\s+(or|and)\s+/gi, ' ');
|
|
101
|
+
|
|
102
|
+
const terms = cleanQuery.split(/\s+/).filter(term => term.length > 0 && !['or', 'and'].includes(term.toLowerCase()));
|
|
103
|
+
const matchedTerms: string[] = [];
|
|
104
|
+
const lowerText = text.toLowerCase();
|
|
105
|
+
|
|
106
|
+
terms.forEach(term => {
|
|
107
|
+
if (term.startsWith('#') || term.startsWith('@')) {
|
|
108
|
+
if (lowerText.includes(term)) {
|
|
109
|
+
matchedTerms.push(term);
|
|
110
|
+
}
|
|
111
|
+
} else if (lowerText.includes(term)) {
|
|
112
|
+
matchedTerms.push(term);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return [...new Set(matchedTerms)];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const newPost = createTrigger({
|
|
120
|
+
auth: blueskyAuth,
|
|
121
|
+
name: 'newPost',
|
|
122
|
+
displayName: 'New Post (with Search Options)',
|
|
123
|
+
description: 'Triggers when posts match your search criteria',
|
|
124
|
+
aiMetadata: {
|
|
125
|
+
description: 'Fires when a new public Bluesky post matches a configured search query (keywords, hashtags, or mentions), with optional language and media filters; each event represents one newly indexed matching post.',
|
|
126
|
+
},
|
|
127
|
+
props: {
|
|
128
|
+
searchQuery: Property.ShortText({
|
|
129
|
+
displayName: 'Search Query',
|
|
130
|
+
description: 'Keywords, hashtags (#example), or mentions (@handle) to find',
|
|
131
|
+
required: true
|
|
132
|
+
}),
|
|
133
|
+
searchLanguage: {
|
|
134
|
+
...simpleLanguageDropdown,
|
|
135
|
+
displayName: 'Language Filter',
|
|
136
|
+
description: 'Filter by language',
|
|
137
|
+
required: false
|
|
138
|
+
},
|
|
139
|
+
includeImages: Property.Checkbox({
|
|
140
|
+
displayName: 'Filter by Images',
|
|
141
|
+
description: 'Only posts with/without images',
|
|
142
|
+
required: false,
|
|
143
|
+
defaultValue: undefined
|
|
144
|
+
}),
|
|
145
|
+
includeVideos: Property.Checkbox({
|
|
146
|
+
displayName: 'Filter by Videos',
|
|
147
|
+
description: 'Only posts with/without videos',
|
|
148
|
+
required: false,
|
|
149
|
+
defaultValue: undefined
|
|
150
|
+
}),
|
|
151
|
+
sortBy: Property.StaticDropdown({
|
|
152
|
+
displayName: 'Sort Order',
|
|
153
|
+
description: 'How to sort results',
|
|
154
|
+
required: false,
|
|
155
|
+
defaultValue: 'latest',
|
|
156
|
+
options: {
|
|
157
|
+
options: [
|
|
158
|
+
{ label: 'Latest First', value: 'latest' },
|
|
159
|
+
{ label: 'Most Popular', value: 'top' }
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
sampleData: {
|
|
165
|
+
uri: 'at://did:plc:example123/app.bsky.feed.post/example456',
|
|
166
|
+
cid: 'bafyreib2rxk3vcfbqij7y6kzgy4knknc7ff4t5jn2m5fbn6jdl7czfqyqe',
|
|
167
|
+
author: {
|
|
168
|
+
did: 'did:plc:example123',
|
|
169
|
+
handle: 'searchauthor.bsky.social',
|
|
170
|
+
displayName: 'Search Result Author',
|
|
171
|
+
avatar: 'https://cdn.bsky.app/img/avatar/plain/did:plc:example123/example@jpeg',
|
|
172
|
+
viewer: {
|
|
173
|
+
muted: false,
|
|
174
|
+
blockedBy: false
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
record: {
|
|
178
|
+
$type: 'app.bsky.feed.post',
|
|
179
|
+
createdAt: '2024-01-01T12:00:00.000Z',
|
|
180
|
+
text: 'This post matches your search criteria! #automation #activepieces',
|
|
181
|
+
langs: ['en']
|
|
182
|
+
},
|
|
183
|
+
indexedAt: '2024-01-01T12:00:00.000Z',
|
|
184
|
+
|
|
185
|
+
replyCount: 2,
|
|
186
|
+
repostCount: 5,
|
|
187
|
+
likeCount: 12,
|
|
188
|
+
quoteCount: 1,
|
|
189
|
+
|
|
190
|
+
labels: [],
|
|
191
|
+
viewer: {
|
|
192
|
+
repost: null,
|
|
193
|
+
like: null
|
|
194
|
+
},
|
|
195
|
+
embed: null,
|
|
196
|
+
|
|
197
|
+
searchContext: {
|
|
198
|
+
query: 'automation',
|
|
199
|
+
language: 'en',
|
|
200
|
+
matchedTerms: ['automation'],
|
|
201
|
+
hasImages: false,
|
|
202
|
+
hasVideo: false,
|
|
203
|
+
hasExternalLink: false
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
type: TriggerStrategy.POLLING,
|
|
207
|
+
|
|
208
|
+
async test(context) {
|
|
209
|
+
return await pollingHelper.test(polling, context);
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
async onEnable(context) {
|
|
213
|
+
const { store, auth, propsValue } = context;
|
|
214
|
+
await pollingHelper.onEnable(polling, { store, auth, propsValue });
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
async onDisable(context) {
|
|
218
|
+
const { store, auth, propsValue } = context;
|
|
219
|
+
await pollingHelper.onDisable(polling, { store, auth, propsValue });
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
async run(context) {
|
|
223
|
+
return await pollingHelper.poll(polling, context);
|
|
224
|
+
},
|
|
225
|
+
});
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { createTrigger, TriggerStrategy, PiecePropValueSchema, Property, AppConnectionValueForAuthProperty } from '@agnocon/pieces-framework';
|
|
2
|
+
import { DedupeStrategy, Polling, pollingHelper } from '@agnocon/pieces-common';
|
|
3
|
+
import { blueskyAuth, BlueSkyAuthType } from '../common/auth';
|
|
4
|
+
import { createBlueskyAgent } from '../common/client';
|
|
5
|
+
import dayjs from 'dayjs';
|
|
6
|
+
|
|
7
|
+
const polling: Polling<AppConnectionValueForAuthProperty<typeof blueskyAuth>, {
|
|
8
|
+
authorSelection: string;
|
|
9
|
+
authorFromFollowing?: string;
|
|
10
|
+
authorHandle?: string;
|
|
11
|
+
includeReplies?: boolean;
|
|
12
|
+
includeReposts?: boolean;
|
|
13
|
+
}> = {
|
|
14
|
+
strategy: DedupeStrategy.TIMEBASED,
|
|
15
|
+
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
|
16
|
+
const { authorSelection, authorFromFollowing, authorHandle, includeReplies = false, includeReposts = false } = propsValue;
|
|
17
|
+
|
|
18
|
+
let selectedAuthorHandle: string;
|
|
19
|
+
if (authorSelection === 'following') {
|
|
20
|
+
if (!authorFromFollowing) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
selectedAuthorHandle = authorFromFollowing;
|
|
24
|
+
} else if (authorSelection === 'manual') {
|
|
25
|
+
if (!authorHandle) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
selectedAuthorHandle = authorHandle;
|
|
29
|
+
} else {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
if (!selectedAuthorHandle || selectedAuthorHandle.trim().length === 0) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const agent = await createBlueskyAgent(auth.props);
|
|
39
|
+
|
|
40
|
+
const normalizedHandle = selectedAuthorHandle.replace('@', '').trim();
|
|
41
|
+
|
|
42
|
+
const response = await agent.getAuthorFeed({
|
|
43
|
+
actor: normalizedHandle,
|
|
44
|
+
limit: 50,
|
|
45
|
+
filter: 'posts_with_replies'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.data?.feed || !Array.isArray(response.data.feed)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const cutoffTime = lastFetchEpochMS || 0;
|
|
53
|
+
|
|
54
|
+
return response.data.feed
|
|
55
|
+
.filter((feedItem: any) => {
|
|
56
|
+
const post = feedItem.post;
|
|
57
|
+
if (!post || !post.indexedAt) return false;
|
|
58
|
+
|
|
59
|
+
const postTime = dayjs(post.indexedAt).valueOf();
|
|
60
|
+
if (postTime <= cutoffTime) return false;
|
|
61
|
+
|
|
62
|
+
const isReply = post.record?.reply !== undefined;
|
|
63
|
+
const isRepost = feedItem.reason?.$type === 'app.bsky.feed.defs#reasonRepost';
|
|
64
|
+
|
|
65
|
+
if (isReply && !includeReplies) return false;
|
|
66
|
+
if (isRepost && !includeReposts) return false;
|
|
67
|
+
|
|
68
|
+
return post.author.handle === normalizedHandle || post.author.did === normalizedHandle;
|
|
69
|
+
})
|
|
70
|
+
.map((feedItem: any) => {
|
|
71
|
+
const post = feedItem.post;
|
|
72
|
+
return {
|
|
73
|
+
epochMilliSeconds: dayjs(post.indexedAt).valueOf(),
|
|
74
|
+
data: {
|
|
75
|
+
uri: post.uri,
|
|
76
|
+
cid: post.cid,
|
|
77
|
+
author: post.author,
|
|
78
|
+
record: post.record,
|
|
79
|
+
indexedAt: post.indexedAt,
|
|
80
|
+
|
|
81
|
+
replyCount: post.replyCount || 0,
|
|
82
|
+
repostCount: post.repostCount || 0,
|
|
83
|
+
likeCount: post.likeCount || 0,
|
|
84
|
+
quoteCount: post.quoteCount || 0,
|
|
85
|
+
|
|
86
|
+
labels: post.labels || [],
|
|
87
|
+
viewer: post.viewer || {},
|
|
88
|
+
embed: post.embed || null,
|
|
89
|
+
|
|
90
|
+
postContext: {
|
|
91
|
+
authorHandle: normalizedHandle,
|
|
92
|
+
isReply: post.record?.reply !== undefined,
|
|
93
|
+
isRepost: feedItem.reason?.$type === 'app.bsky.feed.defs#reasonRepost',
|
|
94
|
+
replyTo: post.record?.reply?.parent?.uri || null,
|
|
95
|
+
hasImages: !!(post.embed?.images),
|
|
96
|
+
hasVideo: !!(post.embed?.video),
|
|
97
|
+
hasExternalLink: !!(post.embed?.external)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
})
|
|
102
|
+
.sort((a: any, b: any) => b.epochMilliSeconds - a.epochMilliSeconds);
|
|
103
|
+
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.warn('Failed to fetch author posts:', error instanceof Error ? error.message : 'Unknown error');
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const newPostsByAuthor = createTrigger({
|
|
112
|
+
auth: blueskyAuth,
|
|
113
|
+
name: 'newPostsByAuthor',
|
|
114
|
+
displayName: 'New Posts by Author',
|
|
115
|
+
description: 'Triggers when a selected author creates a new post',
|
|
116
|
+
aiMetadata: {
|
|
117
|
+
description: 'Fires when a chosen Bluesky author (picked from your following list or by handle) publishes a new post; each event represents one new post by that author, optionally including their replies and reposts.',
|
|
118
|
+
},
|
|
119
|
+
props: {
|
|
120
|
+
authorSelection: Property.StaticDropdown({
|
|
121
|
+
displayName: 'How to select author?',
|
|
122
|
+
description: 'Choose how to select the author',
|
|
123
|
+
required: true,
|
|
124
|
+
defaultValue: 'following',
|
|
125
|
+
options: {
|
|
126
|
+
options: [
|
|
127
|
+
{ label: 'From my following list', value: 'following' },
|
|
128
|
+
{ label: 'Enter handle manually', value: 'manual' },
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
|
|
133
|
+
authorFromFollowing: Property.Dropdown({
|
|
134
|
+
auth: blueskyAuth,
|
|
135
|
+
displayName: 'Select Author',
|
|
136
|
+
description: 'Choose from accounts you follow',
|
|
137
|
+
required: false,
|
|
138
|
+
refreshers: ['auth'],
|
|
139
|
+
options: async ({ auth }) => {
|
|
140
|
+
try {
|
|
141
|
+
if (!auth) return { options: [] };
|
|
142
|
+
const agent = await createBlueskyAgent(auth.props);
|
|
143
|
+
const session = agent.session;
|
|
144
|
+
|
|
145
|
+
if (!session?.did) {
|
|
146
|
+
return { options: [{ label: 'Please authenticate first', value: '' }] };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const followingResponse = await agent.getFollows({
|
|
150
|
+
actor: session.did,
|
|
151
|
+
limit: 100
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
options: followingResponse.data.follows.map(follow => ({
|
|
156
|
+
label: `${follow.displayName || follow.handle} (@${follow.handle})`,
|
|
157
|
+
value: follow.handle
|
|
158
|
+
}))
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return {
|
|
162
|
+
options: [{ label: 'Error loading following list', value: '' }]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}),
|
|
167
|
+
|
|
168
|
+
authorHandle: Property.ShortText({
|
|
169
|
+
displayName: 'Author Handle',
|
|
170
|
+
description: 'Enter the Bluesky username (e.g., username.bsky.social)',
|
|
171
|
+
required: false
|
|
172
|
+
}),
|
|
173
|
+
includeReplies: Property.Checkbox({
|
|
174
|
+
displayName: 'Include Replies',
|
|
175
|
+
description: 'Include reply posts by this author',
|
|
176
|
+
required: false,
|
|
177
|
+
defaultValue: false
|
|
178
|
+
}),
|
|
179
|
+
includeReposts: Property.Checkbox({
|
|
180
|
+
displayName: 'Include Reposts',
|
|
181
|
+
description: 'Include posts that this author reposted',
|
|
182
|
+
required: false,
|
|
183
|
+
defaultValue: false
|
|
184
|
+
})
|
|
185
|
+
},
|
|
186
|
+
sampleData: {
|
|
187
|
+
uri: 'at://did:plc:example123/app.bsky.feed.post/example456',
|
|
188
|
+
cid: 'bafyreib2rxk3vcfbqij7y6kzgy4knknc7ff4t5jn2m5fbn6jdl7czfqyqe',
|
|
189
|
+
author: {
|
|
190
|
+
did: 'did:plc:example123',
|
|
191
|
+
handle: 'author.bsky.social',
|
|
192
|
+
displayName: 'Example Author',
|
|
193
|
+
avatar: 'https://cdn.bsky.app/img/avatar/plain/did:plc:example123/example@jpeg',
|
|
194
|
+
followersCount: 1500,
|
|
195
|
+
followsCount: 300,
|
|
196
|
+
postsCount: 250,
|
|
197
|
+
viewer: {
|
|
198
|
+
muted: false,
|
|
199
|
+
blockedBy: false,
|
|
200
|
+
following: 'at://following-record-uri'
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
record: {
|
|
204
|
+
$type: 'app.bsky.feed.post',
|
|
205
|
+
createdAt: '2024-01-01T12:00:00.000Z',
|
|
206
|
+
text: 'Just posted something new! Excited to share this with everyone.',
|
|
207
|
+
langs: ['en']
|
|
208
|
+
},
|
|
209
|
+
indexedAt: '2024-01-01T12:00:00.000Z',
|
|
210
|
+
|
|
211
|
+
replyCount: 3,
|
|
212
|
+
repostCount: 8,
|
|
213
|
+
likeCount: 25,
|
|
214
|
+
quoteCount: 2,
|
|
215
|
+
|
|
216
|
+
labels: [],
|
|
217
|
+
viewer: {
|
|
218
|
+
repost: null,
|
|
219
|
+
like: null
|
|
220
|
+
},
|
|
221
|
+
embed: null,
|
|
222
|
+
|
|
223
|
+
postContext: {
|
|
224
|
+
authorHandle: 'author.bsky.social',
|
|
225
|
+
isReply: false,
|
|
226
|
+
isRepost: false,
|
|
227
|
+
replyTo: null,
|
|
228
|
+
hasImages: false,
|
|
229
|
+
hasVideo: false,
|
|
230
|
+
hasExternalLink: false
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
type: TriggerStrategy.POLLING,
|
|
234
|
+
|
|
235
|
+
async test(context) {
|
|
236
|
+
return await pollingHelper.test(polling, context);
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
async onEnable(context) {
|
|
240
|
+
const { store, auth, propsValue } = context;
|
|
241
|
+
await pollingHelper.onEnable(polling, { store, auth, propsValue });
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
async onDisable(context) {
|
|
245
|
+
const { store, auth, propsValue } = context;
|
|
246
|
+
await pollingHelper.onDisable(polling, { store, auth, propsValue });
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
async run(context) {
|
|
250
|
+
return await pollingHelper.poll(polling, context);
|
|
251
|
+
},
|
|
252
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
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 response = await agent.getTimeline({
|
|
14
|
+
limit: 50
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (!response.data?.feed || !Array.isArray(response.data.feed)) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const cutoffTime = lastFetchEpochMS || 0;
|
|
22
|
+
|
|
23
|
+
return response.data.feed
|
|
24
|
+
.filter((item: any) => {
|
|
25
|
+
if (!item.post || !item.post.indexedAt) return false;
|
|
26
|
+
|
|
27
|
+
const postTime = dayjs(item.post.indexedAt).valueOf();
|
|
28
|
+
return postTime > cutoffTime;
|
|
29
|
+
})
|
|
30
|
+
.map((item: any) => ({
|
|
31
|
+
epochMilliSeconds: dayjs(item.post.indexedAt).valueOf(),
|
|
32
|
+
data: {
|
|
33
|
+
uri: item.post.uri,
|
|
34
|
+
cid: item.post.cid,
|
|
35
|
+
author: item.post.author,
|
|
36
|
+
record: item.post.record,
|
|
37
|
+
indexedAt: item.post.indexedAt,
|
|
38
|
+
|
|
39
|
+
replyCount: item.post.replyCount || 0,
|
|
40
|
+
repostCount: item.post.repostCount || 0,
|
|
41
|
+
likeCount: item.post.likeCount || 0,
|
|
42
|
+
quoteCount: item.post.quoteCount || 0,
|
|
43
|
+
|
|
44
|
+
labels: item.post.labels || [],
|
|
45
|
+
viewer: item.post.viewer || {},
|
|
46
|
+
embed: item.post.embed || null,
|
|
47
|
+
|
|
48
|
+
reason: item.reason || null,
|
|
49
|
+
reply: item.reply || null,
|
|
50
|
+
|
|
51
|
+
feedContext: {
|
|
52
|
+
isRepost: !!item.reason,
|
|
53
|
+
repostBy: item.reason?.by || null,
|
|
54
|
+
isReply: !!item.reply,
|
|
55
|
+
replyToPost: item.reply?.parent || null,
|
|
56
|
+
replyToRoot: item.reply?.root || null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}))
|
|
60
|
+
.sort((a: any, b: any) => b.epochMilliSeconds - a.epochMilliSeconds);
|
|
61
|
+
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn('Failed to fetch timeline posts:', error instanceof Error ? error.message : 'Unknown error');
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const newTimelinePosts = createTrigger({
|
|
70
|
+
auth: 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
|
+
|
|
100
|
+
replyCount: 3,
|
|
101
|
+
repostCount: 8,
|
|
102
|
+
likeCount: 15,
|
|
103
|
+
quoteCount: 2,
|
|
104
|
+
|
|
105
|
+
labels: [],
|
|
106
|
+
viewer: {
|
|
107
|
+
repost: null,
|
|
108
|
+
like: null
|
|
109
|
+
},
|
|
110
|
+
embed: null,
|
|
111
|
+
|
|
112
|
+
reason: null,
|
|
113
|
+
reply: null,
|
|
114
|
+
feedContext: {
|
|
115
|
+
isRepost: false,
|
|
116
|
+
repostBy: null,
|
|
117
|
+
isReply: false,
|
|
118
|
+
replyToPost: null,
|
|
119
|
+
replyToRoot: null
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
type: TriggerStrategy.POLLING,
|
|
123
|
+
|
|
124
|
+
async test(context) {
|
|
125
|
+
return await pollingHelper.test(polling, context);
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
async onEnable(context) {
|
|
129
|
+
const { store, auth, propsValue } = context;
|
|
130
|
+
await pollingHelper.onEnable(polling, { store, auth, propsValue });
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async onDisable(context) {
|
|
134
|
+
const { store, auth, propsValue } = context;
|
|
135
|
+
await pollingHelper.onDisable(polling, { store, auth, propsValue });
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
async run(context) {
|
|
139
|
+
return await pollingHelper.poll(polling, context);
|
|
140
|
+
},
|
|
141
|
+
});
|