@pipedream/bluesky 0.0.1 → 0.1.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/actions/create-post/create-post.mjs +39 -0
- package/actions/like-post/like-post.mjs +61 -0
- package/actions/retrieve-thread/retrieve-thread.mjs +73 -0
- package/bluesky.app.mjs +168 -5
- package/common/constants.mjs +41 -0
- package/common/utils.mjs +17 -0
- package/package.json +4 -1
- package/sources/common/polling.mjs +110 -0
- package/sources/new-follower-on-account/new-follower-on-account.mjs +49 -0
- package/sources/new-follower-on-account/test-event.mjs +13 -0
- package/sources/new-posts-by-author/new-posts-by-author.mjs +50 -0
- package/sources/new-posts-by-author/test-event.mjs +137 -0
- package/sources/new-timeline-posts/new-timeline-posts.mjs +38 -0
- package/sources/new-timeline-posts/test-event.mjs +137 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import app from "../../bluesky.app.mjs";
|
|
2
|
+
import constants from "../../common/constants.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
key: "bluesky-create-post",
|
|
6
|
+
name: "Create Post",
|
|
7
|
+
description: "Creates a new post on Bluesky. [See the documentation](https://docs.bsky.app/docs/api/com-atproto-repo-create-record).",
|
|
8
|
+
version: "0.0.1",
|
|
9
|
+
type: "action",
|
|
10
|
+
props: {
|
|
11
|
+
app,
|
|
12
|
+
text: {
|
|
13
|
+
type: "string",
|
|
14
|
+
label: "Text",
|
|
15
|
+
description: "The text content of the post.",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
async run({ $ }) {
|
|
19
|
+
const {
|
|
20
|
+
app,
|
|
21
|
+
text,
|
|
22
|
+
} = this;
|
|
23
|
+
|
|
24
|
+
const response = await app.createRecord({
|
|
25
|
+
$,
|
|
26
|
+
data: {
|
|
27
|
+
collection: constants.RESOURCE_TYPE.POST,
|
|
28
|
+
record: {
|
|
29
|
+
["$type"]: constants.RESOURCE_TYPE.POST,
|
|
30
|
+
text,
|
|
31
|
+
createdAt: new Date().toISOString(),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
$.export("$summary", `Successfully created a new post with uri \`${response.uri}\`.`);
|
|
37
|
+
return response;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import app from "../../bluesky.app.mjs";
|
|
2
|
+
import constants from "../../common/constants.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
key: "bluesky-like-post",
|
|
6
|
+
name: "Like Post",
|
|
7
|
+
description: "Like a specific post on Bluesky. [See the documentation](https://docs.bsky.app/docs/api/com-atproto-repo-create-record).",
|
|
8
|
+
version: "0.0.1",
|
|
9
|
+
type: "action",
|
|
10
|
+
props: {
|
|
11
|
+
app,
|
|
12
|
+
postUrl: {
|
|
13
|
+
propDefinition: [
|
|
14
|
+
app,
|
|
15
|
+
"postUrl",
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
async run({ $ }) {
|
|
20
|
+
const {
|
|
21
|
+
app,
|
|
22
|
+
postUrl,
|
|
23
|
+
} = this;
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
handle,
|
|
27
|
+
postId,
|
|
28
|
+
} = app.getHandleAndPostIdFromUrl(postUrl);
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
uri,
|
|
32
|
+
cid,
|
|
33
|
+
} = await app.getRecord({
|
|
34
|
+
$,
|
|
35
|
+
params: {
|
|
36
|
+
repo: handle,
|
|
37
|
+
collection: constants.RESOURCE_TYPE.POST,
|
|
38
|
+
rkey: postId,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const response = await app.createRecord({
|
|
43
|
+
$,
|
|
44
|
+
data: {
|
|
45
|
+
collection: constants.RESOURCE_TYPE.LIKE,
|
|
46
|
+
record: {
|
|
47
|
+
["$type"]: constants.RESOURCE_TYPE.LIKE,
|
|
48
|
+
createdAt: new Date().toISOString(),
|
|
49
|
+
subject: {
|
|
50
|
+
uri,
|
|
51
|
+
cid,
|
|
52
|
+
py_type: "com.atproto.repo.strongRef",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
$.export("$summary", "Successfully liked post.");
|
|
59
|
+
return response;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import app from "../../bluesky.app.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "bluesky-retrieve-thread",
|
|
5
|
+
name: "Retrieve Thread",
|
|
6
|
+
description: "Retrieve a full thread of posts. [See the documentation](https://docs.bsky.app/docs/api/app-bsky-feed-get-post-thread).",
|
|
7
|
+
version: "0.0.1",
|
|
8
|
+
type: "action",
|
|
9
|
+
props: {
|
|
10
|
+
app,
|
|
11
|
+
postUrl: {
|
|
12
|
+
propDefinition: [
|
|
13
|
+
app,
|
|
14
|
+
"postUrl",
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
depth: {
|
|
18
|
+
type: "integer",
|
|
19
|
+
label: "Depth",
|
|
20
|
+
description: "How many levels of reply depth should be included in response. Default is `6`.",
|
|
21
|
+
optional: true,
|
|
22
|
+
max: 100,
|
|
23
|
+
},
|
|
24
|
+
parentHeight: {
|
|
25
|
+
type: "integer",
|
|
26
|
+
label: "Parent Height",
|
|
27
|
+
description: "How many levels of parent (and grandparent, etc) post to include. Default is `80`.",
|
|
28
|
+
optional: true,
|
|
29
|
+
max: 100,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
methods: {
|
|
33
|
+
getPostThread(args = {}) {
|
|
34
|
+
return this.app._makeRequest({
|
|
35
|
+
path: "/app.bsky.feed.getPostThread",
|
|
36
|
+
...args,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
async run({ $ }) {
|
|
41
|
+
const {
|
|
42
|
+
app,
|
|
43
|
+
getPostThread,
|
|
44
|
+
postUrl,
|
|
45
|
+
depth,
|
|
46
|
+
parentHeight,
|
|
47
|
+
} = this;
|
|
48
|
+
|
|
49
|
+
const {
|
|
50
|
+
handle,
|
|
51
|
+
postId,
|
|
52
|
+
} = app.getHandleAndPostIdFromUrl(postUrl);
|
|
53
|
+
|
|
54
|
+
const { did } = await app.resolveHandle({
|
|
55
|
+
$,
|
|
56
|
+
params: {
|
|
57
|
+
handle,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const response = await getPostThread({
|
|
62
|
+
$,
|
|
63
|
+
params: {
|
|
64
|
+
uri: app.getPostUri(postId, did),
|
|
65
|
+
depth,
|
|
66
|
+
parentHeight,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
$.export("$summary", "Successfully retrieved thread.");
|
|
71
|
+
return response;
|
|
72
|
+
},
|
|
73
|
+
};
|
package/bluesky.app.mjs
CHANGED
|
@@ -1,11 +1,174 @@
|
|
|
1
|
+
import { axios } from "@pipedream/platform";
|
|
2
|
+
import constants from "./common/constants.mjs";
|
|
3
|
+
import utils from "./common/utils.mjs";
|
|
4
|
+
|
|
1
5
|
export default {
|
|
2
6
|
type: "app",
|
|
3
7
|
app: "bluesky",
|
|
4
|
-
propDefinitions: {
|
|
8
|
+
propDefinitions: {
|
|
9
|
+
postUrl: {
|
|
10
|
+
type: "string",
|
|
11
|
+
label: "Post URL",
|
|
12
|
+
description: "The URL will look like `https://bsky.app/profile/myhandle.bsky.social/post/3le7x3qgmaw23`.",
|
|
13
|
+
},
|
|
14
|
+
authorId: {
|
|
15
|
+
type: "string",
|
|
16
|
+
label: "Author ID",
|
|
17
|
+
description: "The ID of the author to track posts.",
|
|
18
|
+
},
|
|
19
|
+
accountId: {
|
|
20
|
+
type: "string",
|
|
21
|
+
label: "Account ID",
|
|
22
|
+
description: "The ID of the account to monitor for new followers.",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
5
25
|
methods: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
26
|
+
getHandleAndPostIdFromUrl(postUrl) {
|
|
27
|
+
const match = postUrl?.match(constants.HANDLE_AND_POST_ID_REGEX);
|
|
28
|
+
if (!match) {
|
|
29
|
+
throw new Error("Invalid post URL");
|
|
30
|
+
}
|
|
31
|
+
const {
|
|
32
|
+
handle,
|
|
33
|
+
postId,
|
|
34
|
+
} = match.groups;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
handle,
|
|
38
|
+
postId,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
getPostUri(postId, did = this.getDID()) {
|
|
42
|
+
return `at://${did}/${constants.RESOURCE_TYPE.POST}/${postId}`;
|
|
43
|
+
},
|
|
44
|
+
getDID() {
|
|
45
|
+
return this.$auth.did;
|
|
46
|
+
},
|
|
47
|
+
getUrl(path) {
|
|
48
|
+
return `${constants.BASE_URL}${constants.VERSION_PATH}${path}`;
|
|
49
|
+
},
|
|
50
|
+
getHeaders(headers) {
|
|
51
|
+
return {
|
|
52
|
+
Authorization: `Bearer ${this.$auth.oauth_access_token}`,
|
|
53
|
+
...headers,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
_makeRequest({
|
|
57
|
+
$ = this, path, headers, ...args
|
|
58
|
+
} = {}) {
|
|
59
|
+
return axios($, {
|
|
60
|
+
...args,
|
|
61
|
+
url: this.getUrl(path),
|
|
62
|
+
headers: this.getHeaders(headers),
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
post(args = {}) {
|
|
66
|
+
return this._makeRequest({
|
|
67
|
+
method: "POST",
|
|
68
|
+
...args,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
createRecord(args = {}) {
|
|
72
|
+
return this.post({
|
|
73
|
+
path: "/com.atproto.repo.createRecord",
|
|
74
|
+
...args,
|
|
75
|
+
data: {
|
|
76
|
+
...args.data,
|
|
77
|
+
repo: this.getDID(),
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
getRecord(args = {}) {
|
|
82
|
+
return this._makeRequest({
|
|
83
|
+
path: "/com.atproto.repo.getRecord",
|
|
84
|
+
...args,
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
resolveHandle(args = {}) {
|
|
88
|
+
return this._makeRequest({
|
|
89
|
+
path: "/com.atproto.identity.resolveHandle",
|
|
90
|
+
...args,
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
getAuthorFeed(args = {}) {
|
|
94
|
+
return this._makeRequest({
|
|
95
|
+
path: "/app.bsky.feed.getAuthorFeed",
|
|
96
|
+
...args,
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
getTimeline(args = {}) {
|
|
100
|
+
return this._makeRequest({
|
|
101
|
+
path: "/app.bsky.feed.getTimeline",
|
|
102
|
+
...args,
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
getFollowers(args = {}) {
|
|
106
|
+
return this._makeRequest({
|
|
107
|
+
path: "/app.bsky.graph.getFollowers",
|
|
108
|
+
...args,
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
async *getIterations({
|
|
112
|
+
resourcesFn, resourcesFnArgs, resourceName,
|
|
113
|
+
lastDateAt, dateField,
|
|
114
|
+
max = constants.DEFAULT_MAX,
|
|
115
|
+
}) {
|
|
116
|
+
let cursor;
|
|
117
|
+
let resourcesCount = 0;
|
|
118
|
+
const firstRun = !lastDateAt;
|
|
119
|
+
|
|
120
|
+
while (true) {
|
|
121
|
+
const response = await resourcesFn({
|
|
122
|
+
...resourcesFnArgs,
|
|
123
|
+
params: {
|
|
124
|
+
...resourcesFnArgs?.params,
|
|
125
|
+
cursor,
|
|
126
|
+
limit: constants.DEFAULT_LIMIT,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const nextResources = utils.getNestedProperty(response, resourceName);
|
|
131
|
+
|
|
132
|
+
if (!nextResources?.length) {
|
|
133
|
+
console.log("No more resources found");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const resource of nextResources) {
|
|
138
|
+
const isLastDateGreater = lastDateAt
|
|
139
|
+
&& Date.parse(lastDateAt) > Date.parse(utils.getNestedProperty(resource, dateField));
|
|
140
|
+
|
|
141
|
+
if (isLastDateGreater) {
|
|
142
|
+
console.log(`Last date is greater than the current resource date in ${dateField}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!isLastDateGreater) {
|
|
147
|
+
yield resource;
|
|
148
|
+
resourcesCount += 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (resourcesCount >= max) {
|
|
152
|
+
console.log("Reached max resources");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (firstRun) {
|
|
158
|
+
console.log("First run: only one request processed");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (nextResources.length < constants.DEFAULT_LIMIT) {
|
|
163
|
+
console.log("No next page found");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
cursor = response.cursor;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
paginate(args = {}) {
|
|
171
|
+
return utils.iterate(this.getIterations(args));
|
|
9
172
|
},
|
|
10
173
|
},
|
|
11
|
-
};
|
|
174
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const BASE_URL = "https://bsky.social";
|
|
2
|
+
const VERSION_PATH = "/xrpc";
|
|
3
|
+
|
|
4
|
+
const INTERACTION_EVENT = {
|
|
5
|
+
REQUES_TLESS: "app.bsky.feed.defs#requestLess",
|
|
6
|
+
REQUEST_MORE: "app.bsky.feed.defs#requestMore",
|
|
7
|
+
CLICK_THROUGH_ITEM: "app.bsky.feed.defs#clickthroughItem",
|
|
8
|
+
CLICK_THROUGH_AUTHOR: "app.bsky.feed.defs#clickthroughAuthor",
|
|
9
|
+
CLICK_THROUGH_REPOSTER: "app.bsky.feed.defs#clickthroughReposter",
|
|
10
|
+
CLICK_THROUGH_EMBED: "app.bsky.feed.defs#clickthroughEmbed",
|
|
11
|
+
INTERACTION_SEEN: "app.bsky.feed.defs#interactionSeen",
|
|
12
|
+
INTERACTION_LIKE: "app.bsky.feed.defs#interactionLike",
|
|
13
|
+
INTERACTION_REPOST: "app.bsky.feed.defs#interactionRepost",
|
|
14
|
+
INTERACTION_REPLY: "app.bsky.feed.defs#interactionReply",
|
|
15
|
+
INTERACTION_QUOTE: "app.bsky.feed.defs#interactionQuote",
|
|
16
|
+
INTERACTION_SHARE: "app.bsky.feed.defs#interactionShare",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const RESOURCE_TYPE = {
|
|
20
|
+
POST: "app.bsky.feed.post",
|
|
21
|
+
LIKE: "app.bsky.feed.like",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const HANDLE_AND_POST_ID_REGEX = /(?:https?:\/\/)?(?:www\.)?(?:[^/]+)\/profile\/(?<handle>[^/]+)\/post\/(?<postId>[^/]+)/;
|
|
25
|
+
|
|
26
|
+
const DEFAULT_LIMIT = 3;
|
|
27
|
+
const DEFAULT_MAX = 600;
|
|
28
|
+
const IS_FIRST_RUN = "isFirstRun";
|
|
29
|
+
const LAST_DATE_AT = "lastDateAt";
|
|
30
|
+
|
|
31
|
+
export default {
|
|
32
|
+
BASE_URL,
|
|
33
|
+
VERSION_PATH,
|
|
34
|
+
INTERACTION_EVENT,
|
|
35
|
+
RESOURCE_TYPE,
|
|
36
|
+
HANDLE_AND_POST_ID_REGEX,
|
|
37
|
+
DEFAULT_LIMIT,
|
|
38
|
+
DEFAULT_MAX,
|
|
39
|
+
IS_FIRST_RUN,
|
|
40
|
+
LAST_DATE_AT,
|
|
41
|
+
};
|
package/common/utils.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
async function iterate(iterations) {
|
|
2
|
+
const items = [];
|
|
3
|
+
for await (const item of iterations) {
|
|
4
|
+
items.push(item);
|
|
5
|
+
}
|
|
6
|
+
return items;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getNestedProperty(obj, propertyString) {
|
|
10
|
+
const properties = propertyString.split(".");
|
|
11
|
+
return properties.reduce((prev, curr) => prev?.[curr], obj);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
iterate,
|
|
16
|
+
getNestedProperty,
|
|
17
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipedream/bluesky",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Pipedream Bluesky Components",
|
|
5
5
|
"main": "bluesky.app.mjs",
|
|
6
6
|
"keywords": [
|
|
@@ -11,5 +11,8 @@
|
|
|
11
11
|
"author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@pipedream/platform": "3.0.3"
|
|
14
17
|
}
|
|
15
18
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfigurationError,
|
|
3
|
+
DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
|
|
4
|
+
} from "@pipedream/platform";
|
|
5
|
+
import app from "../../bluesky.app.mjs";
|
|
6
|
+
import constants from "../../common/constants.mjs";
|
|
7
|
+
import utils from "../../common/utils.mjs";
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
props: {
|
|
11
|
+
app,
|
|
12
|
+
db: "$.service.db",
|
|
13
|
+
timer: {
|
|
14
|
+
type: "$.interface.timer",
|
|
15
|
+
label: "Polling Schedule",
|
|
16
|
+
description: "How often to poll the API",
|
|
17
|
+
default: {
|
|
18
|
+
intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
hooks: {
|
|
23
|
+
deploy() {
|
|
24
|
+
this.setIsFirstRun(true);
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
methods: {
|
|
28
|
+
generateMeta() {
|
|
29
|
+
throw new ConfigurationError("generateMeta is not implemented");
|
|
30
|
+
},
|
|
31
|
+
setIsFirstRun(value) {
|
|
32
|
+
this.db.set(constants.IS_FIRST_RUN, value);
|
|
33
|
+
},
|
|
34
|
+
getIsFirstRun() {
|
|
35
|
+
return this.db.get(constants.IS_FIRST_RUN);
|
|
36
|
+
},
|
|
37
|
+
setLastDateAt(value) {
|
|
38
|
+
this.db.set(constants.LAST_DATE_AT, value);
|
|
39
|
+
},
|
|
40
|
+
getLastDateAt() {
|
|
41
|
+
return this.db.get(constants.LAST_DATE_AT);
|
|
42
|
+
},
|
|
43
|
+
getDateField() {
|
|
44
|
+
throw new ConfigurationError("getDateField is not implemented");
|
|
45
|
+
},
|
|
46
|
+
getResourceName() {
|
|
47
|
+
throw new ConfigurationError("getResourceName is not implemented");
|
|
48
|
+
},
|
|
49
|
+
getResourcesFn() {
|
|
50
|
+
throw new ConfigurationError("getResourcesFn is not implemented");
|
|
51
|
+
},
|
|
52
|
+
getResourcesFnArgs() {
|
|
53
|
+
throw new ConfigurationError("getResourcesFnArgs is not implemented");
|
|
54
|
+
},
|
|
55
|
+
processResource(resource) {
|
|
56
|
+
const meta = this.generateMeta(resource);
|
|
57
|
+
this.$emit(resource, meta);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
async run() {
|
|
61
|
+
const {
|
|
62
|
+
app,
|
|
63
|
+
getDateField,
|
|
64
|
+
getLastDateAt,
|
|
65
|
+
getResourcesFn,
|
|
66
|
+
getResourcesFnArgs,
|
|
67
|
+
getResourceName,
|
|
68
|
+
processResource,
|
|
69
|
+
getIsFirstRun,
|
|
70
|
+
setIsFirstRun,
|
|
71
|
+
setLastDateAt,
|
|
72
|
+
} = this;
|
|
73
|
+
|
|
74
|
+
const isFirstRun = getIsFirstRun();
|
|
75
|
+
const dateField = getDateField();
|
|
76
|
+
const lastDateAt = getLastDateAt();
|
|
77
|
+
|
|
78
|
+
const otherArgs = isFirstRun
|
|
79
|
+
? {
|
|
80
|
+
max: constants.DEFAULT_LIMIT,
|
|
81
|
+
}
|
|
82
|
+
: {
|
|
83
|
+
dateField,
|
|
84
|
+
lastDateAt,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const resources = await app.paginate({
|
|
88
|
+
resourcesFn: getResourcesFn(),
|
|
89
|
+
resourcesFnArgs: getResourcesFnArgs(),
|
|
90
|
+
resourceName: getResourceName(),
|
|
91
|
+
...otherArgs,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (resources.length) {
|
|
95
|
+
const [
|
|
96
|
+
firstResource,
|
|
97
|
+
] = Array.from(resources);
|
|
98
|
+
if (firstResource) {
|
|
99
|
+
setLastDateAt(utils.getNestedProperty(firstResource, dateField));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Array.from(resources)
|
|
104
|
+
.forEach(processResource);
|
|
105
|
+
|
|
106
|
+
if (isFirstRun) {
|
|
107
|
+
setIsFirstRun(false);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import common from "../common/polling.mjs";
|
|
2
|
+
import sampleEmit from "./test-event.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
...common,
|
|
6
|
+
key: "bluesky-new-follower-on-account",
|
|
7
|
+
name: "New Follower On Account",
|
|
8
|
+
description: "Emit new event when someone follows the specified account. Requires the account ID as a prop to monitor followers for that account. [See the documentation](https://docs.bsky.app/docs/api/app-bsky-graph-get-followers).",
|
|
9
|
+
version: "0.0.1",
|
|
10
|
+
type: "source",
|
|
11
|
+
dedupe: "unique",
|
|
12
|
+
props: {
|
|
13
|
+
...common.props,
|
|
14
|
+
accountId: {
|
|
15
|
+
propDefinition: [
|
|
16
|
+
common.props.app,
|
|
17
|
+
"accountId",
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
methods: {
|
|
22
|
+
...common.methods,
|
|
23
|
+
getDateField() {
|
|
24
|
+
return "createdAt";
|
|
25
|
+
},
|
|
26
|
+
getResourceName() {
|
|
27
|
+
return "followers";
|
|
28
|
+
},
|
|
29
|
+
getResourcesFn() {
|
|
30
|
+
return this.app.getFollowers;
|
|
31
|
+
},
|
|
32
|
+
getResourcesFnArgs() {
|
|
33
|
+
return {
|
|
34
|
+
debug: true,
|
|
35
|
+
params: {
|
|
36
|
+
actor: this.accountId,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
generateMeta(resource) {
|
|
41
|
+
return {
|
|
42
|
+
id: resource.did,
|
|
43
|
+
summary: `New Follower ${resource.handle}`,
|
|
44
|
+
ts: Date.parse(resource.createdAt),
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
sampleEmit,
|
|
49
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
"did": "did:plc:rwanjd2ci6wnzxmxbjqibfdsdl",
|
|
3
|
+
"handle": "test.bsky.social",
|
|
4
|
+
"displayName": "",
|
|
5
|
+
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:rwdfasdfa@jpeg",
|
|
6
|
+
"viewer": {
|
|
7
|
+
"muted": false,
|
|
8
|
+
"blockedBy": false
|
|
9
|
+
},
|
|
10
|
+
"labels": [],
|
|
11
|
+
"createdAt": "2024-12-25T17:22:17.844Z",
|
|
12
|
+
"indexedAt": "2024-12-25T17:22:17.844Z"
|
|
13
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import common from "../common/polling.mjs";
|
|
2
|
+
import sampleEmit from "./test-event.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
...common,
|
|
6
|
+
key: "bluesky-new-posts-by-author",
|
|
7
|
+
name: "New Posts By Author",
|
|
8
|
+
description: "Emit new event when an author creates a post. Requires the author id as a prop to track posts from a specific author. [See the documentation](https://docs.bsky.app/docs/api/app-bsky-feed-search-posts).",
|
|
9
|
+
version: "0.0.1",
|
|
10
|
+
type: "source",
|
|
11
|
+
dedupe: "unique",
|
|
12
|
+
props: {
|
|
13
|
+
...common.props,
|
|
14
|
+
authorId: {
|
|
15
|
+
propDefinition: [
|
|
16
|
+
common.props.app,
|
|
17
|
+
"authorId",
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
methods: {
|
|
22
|
+
...common.methods,
|
|
23
|
+
getDateField() {
|
|
24
|
+
return "post.record.createdAt";
|
|
25
|
+
},
|
|
26
|
+
getResourceName() {
|
|
27
|
+
return "feed";
|
|
28
|
+
},
|
|
29
|
+
getResourcesFn() {
|
|
30
|
+
return this.app.getAuthorFeed;
|
|
31
|
+
},
|
|
32
|
+
getResourcesFnArgs() {
|
|
33
|
+
return {
|
|
34
|
+
debug: true,
|
|
35
|
+
params: {
|
|
36
|
+
actor: this.authorId,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
generateMeta(resource) {
|
|
41
|
+
const { post } = resource;
|
|
42
|
+
return {
|
|
43
|
+
id: post.cid,
|
|
44
|
+
summary: `New Post at ${post.record.createdAt}`,
|
|
45
|
+
ts: Date.parse(post.record.createdAt),
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
sampleEmit,
|
|
50
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
post: {
|
|
3
|
+
uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockPostId",
|
|
4
|
+
cid: "bafyreimachangedxxxyyyzzz",
|
|
5
|
+
author: {
|
|
6
|
+
did: "did:plc:fakeOne",
|
|
7
|
+
handle: "john_doe.bsky.social",
|
|
8
|
+
displayName: "John Doe",
|
|
9
|
+
avatar: "https://mock.cdn/avatar/plain/did:plc:fakeOne/bafkremockavatar@jpeg",
|
|
10
|
+
viewer: {
|
|
11
|
+
muted: false,
|
|
12
|
+
blockedBy: false,
|
|
13
|
+
following: "at://did:plc:someOtherDid/app.bsky.graph.follow/mockFollow"
|
|
14
|
+
},
|
|
15
|
+
labels: [],
|
|
16
|
+
createdAt: "2025-01-01T08:00:00.000Z",
|
|
17
|
+
},
|
|
18
|
+
record: {
|
|
19
|
+
$type: "app.bsky.feed.post",
|
|
20
|
+
createdAt: "2025-01-01T12:00:00.000Z",
|
|
21
|
+
langs: ["en"],
|
|
22
|
+
reply: {
|
|
23
|
+
parent: {
|
|
24
|
+
cid: "bafyreimockparentxyz",
|
|
25
|
+
uri: "at://did:plc:someoneElseDid/app.bsky.feed.post/mockParentPost"
|
|
26
|
+
},
|
|
27
|
+
root: {
|
|
28
|
+
cid: "bafyreimockrootabc",
|
|
29
|
+
uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockRootPost"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
text: "Mock text for demonstration of the post content structure.",
|
|
33
|
+
},
|
|
34
|
+
replyCount: 0,
|
|
35
|
+
repostCount: 0,
|
|
36
|
+
likeCount: 1,
|
|
37
|
+
quoteCount: 0,
|
|
38
|
+
indexedAt: "2025-01-01T12:00:10.000Z",
|
|
39
|
+
viewer: {
|
|
40
|
+
threadMuted: false,
|
|
41
|
+
embeddingDisabled: false,
|
|
42
|
+
},
|
|
43
|
+
labels: [],
|
|
44
|
+
},
|
|
45
|
+
reply: {
|
|
46
|
+
root: {
|
|
47
|
+
$type: "app.bsky.feed.defs#postView",
|
|
48
|
+
uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockRootPost",
|
|
49
|
+
cid: "bafyreimockrootabc",
|
|
50
|
+
author: {
|
|
51
|
+
did: "did:plc:fakeOne",
|
|
52
|
+
handle: "john_doe.bsky.social",
|
|
53
|
+
displayName: "John Doe",
|
|
54
|
+
avatar: "https://mock.cdn/avatar/plain/did:plc:fakeOne/bafkremockavatar@jpeg",
|
|
55
|
+
viewer: {
|
|
56
|
+
muted: false,
|
|
57
|
+
blockedBy: false,
|
|
58
|
+
following: "at://did:plc:someOtherDid/app.bsky.graph.follow/mockFollow"
|
|
59
|
+
},
|
|
60
|
+
labels: [],
|
|
61
|
+
createdAt: "2025-01-01T08:00:00.000Z"
|
|
62
|
+
},
|
|
63
|
+
record: {
|
|
64
|
+
$type: "app.bsky.feed.post",
|
|
65
|
+
createdAt: "2025-01-01T09:00:00.000Z",
|
|
66
|
+
langs: ["en"],
|
|
67
|
+
text: "Mock text to show how the root reply might look.",
|
|
68
|
+
},
|
|
69
|
+
replyCount: 5,
|
|
70
|
+
repostCount: 2,
|
|
71
|
+
likeCount: 10,
|
|
72
|
+
quoteCount: 0,
|
|
73
|
+
indexedAt: "2025-01-01T09:00:05.000Z",
|
|
74
|
+
viewer: {
|
|
75
|
+
threadMuted: false,
|
|
76
|
+
embeddingDisabled: false,
|
|
77
|
+
},
|
|
78
|
+
labels: [],
|
|
79
|
+
},
|
|
80
|
+
parent: {
|
|
81
|
+
$type: "app.bsky.feed.defs#postView",
|
|
82
|
+
uri: "at://did:plc:someoneElseDid/app.bsky.feed.post/mockParentPost",
|
|
83
|
+
cid: "bafyreimockparentxyz",
|
|
84
|
+
author: {
|
|
85
|
+
did: "did:plc:someoneElseDid",
|
|
86
|
+
handle: "alice_example.bsky.social",
|
|
87
|
+
displayName: "Alice Example",
|
|
88
|
+
avatar: "https://mock.cdn/avatar/plain/did:plc:someoneElseDid/bafkremockavatar@jpeg",
|
|
89
|
+
viewer: {
|
|
90
|
+
muted: false,
|
|
91
|
+
blockedBy: false,
|
|
92
|
+
},
|
|
93
|
+
labels: [],
|
|
94
|
+
createdAt: "2025-01-01T07:00:00.000Z"
|
|
95
|
+
},
|
|
96
|
+
record: {
|
|
97
|
+
$type: "app.bsky.feed.post",
|
|
98
|
+
createdAt: "2025-01-01T10:00:00.000Z",
|
|
99
|
+
langs: ["en"],
|
|
100
|
+
reply: {
|
|
101
|
+
parent: {
|
|
102
|
+
cid: "bafyreimockrootabc",
|
|
103
|
+
uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockRootPost"
|
|
104
|
+
},
|
|
105
|
+
root: {
|
|
106
|
+
cid: "bafyreimockrootabc",
|
|
107
|
+
uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockRootPost"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
text: "Mock text showing how a parent comment might appear in the structure."
|
|
111
|
+
},
|
|
112
|
+
replyCount: 1,
|
|
113
|
+
repostCount: 0,
|
|
114
|
+
likeCount: 0,
|
|
115
|
+
quoteCount: 0,
|
|
116
|
+
indexedAt: "2025-01-01T10:00:05.000Z",
|
|
117
|
+
viewer: {
|
|
118
|
+
threadMuted: false,
|
|
119
|
+
embeddingDisabled: false,
|
|
120
|
+
},
|
|
121
|
+
labels: [],
|
|
122
|
+
},
|
|
123
|
+
grandparentAuthor: {
|
|
124
|
+
did: "did:plc:fakeOne",
|
|
125
|
+
handle: "john_doe.bsky.social",
|
|
126
|
+
displayName: "John Doe",
|
|
127
|
+
avatar: "https://mock.cdn/avatar/plain/did:plc:fakeOne/bafkremockavatar@jpeg",
|
|
128
|
+
viewer: {
|
|
129
|
+
muted: false,
|
|
130
|
+
blockedBy: false,
|
|
131
|
+
following: "at://did:plc:someOtherDid/app.bsky.graph.follow/mockFollow"
|
|
132
|
+
},
|
|
133
|
+
labels: [],
|
|
134
|
+
createdAt: "2025-01-01T08:00:00.000Z",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import common from "../common/polling.mjs";
|
|
2
|
+
import sampleEmit from "./test-event.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
...common,
|
|
6
|
+
key: "bluesky-new-timeline-posts",
|
|
7
|
+
name: "New Timeline Posts",
|
|
8
|
+
description: "Emit new event when posts appear in the `following` feed. [See the documentation](https://docs.bsky.app/docs/api/app-bsky-feed-get-timeline).",
|
|
9
|
+
version: "0.0.1",
|
|
10
|
+
type: "source",
|
|
11
|
+
dedupe: "unique",
|
|
12
|
+
methods: {
|
|
13
|
+
...common.methods,
|
|
14
|
+
getDateField() {
|
|
15
|
+
return "post.record.createdAt";
|
|
16
|
+
},
|
|
17
|
+
getResourceName() {
|
|
18
|
+
return "feed";
|
|
19
|
+
},
|
|
20
|
+
getResourcesFn() {
|
|
21
|
+
return this.app.getTimeline;
|
|
22
|
+
},
|
|
23
|
+
getResourcesFnArgs() {
|
|
24
|
+
return {
|
|
25
|
+
debug: true,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
generateMeta(resource) {
|
|
29
|
+
const { post } = resource;
|
|
30
|
+
return {
|
|
31
|
+
id: post.cid,
|
|
32
|
+
summary: `New Post at ${post.record.createdAt}`,
|
|
33
|
+
ts: Date.parse(post.record.createdAt),
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
sampleEmit,
|
|
38
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
"post": {
|
|
3
|
+
"uri": "at://did:plc:mockDid/app.bsky.feed.post/mockUri",
|
|
4
|
+
"cid": "mockCid",
|
|
5
|
+
"author": {
|
|
6
|
+
"did": "did:plc:mockDid",
|
|
7
|
+
"handle": "mockUser.bsky.social",
|
|
8
|
+
"displayName": "Mock User",
|
|
9
|
+
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:mockDid/mockAvatar@jpeg",
|
|
10
|
+
"viewer": {
|
|
11
|
+
"muted": false,
|
|
12
|
+
"blockedBy": false,
|
|
13
|
+
"following": "at://did:plc:mockFollow/app.bsky.graph.follow/mockId"
|
|
14
|
+
},
|
|
15
|
+
"labels": [],
|
|
16
|
+
"createdAt": "2025-01-01T00:00:00.000Z"
|
|
17
|
+
},
|
|
18
|
+
"record": {
|
|
19
|
+
"$type": "app.bsky.feed.post",
|
|
20
|
+
"createdAt": "2025-01-01T00:00:00.000Z",
|
|
21
|
+
"langs": ["en"],
|
|
22
|
+
"reply": {
|
|
23
|
+
"parent": {
|
|
24
|
+
"cid": "mockParentCid",
|
|
25
|
+
"uri": "at://did:plc:mockParentDid/app.bsky.feed.post/mockParentUri"
|
|
26
|
+
},
|
|
27
|
+
"root": {
|
|
28
|
+
"cid": "mockRootCid",
|
|
29
|
+
"uri": "at://did:plc:mockRootDid/app.bsky.feed.post/mockRootUri"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"text": "This is some mock post content."
|
|
33
|
+
},
|
|
34
|
+
"replyCount": 0,
|
|
35
|
+
"repostCount": 0,
|
|
36
|
+
"likeCount": 0,
|
|
37
|
+
"quoteCount": 0,
|
|
38
|
+
"indexedAt": "2025-01-01T00:00:00.000Z",
|
|
39
|
+
"viewer": {
|
|
40
|
+
"threadMuted": false,
|
|
41
|
+
"embeddingDisabled": false
|
|
42
|
+
},
|
|
43
|
+
"labels": []
|
|
44
|
+
},
|
|
45
|
+
"reply": {
|
|
46
|
+
"root": {
|
|
47
|
+
"$type": "app.bsky.feed.defs#postView",
|
|
48
|
+
"uri": "at://did:plc:mockRootDid/app.bsky.feed.post/mockRootUri",
|
|
49
|
+
"cid": "mockRootCid",
|
|
50
|
+
"author": {
|
|
51
|
+
"did": "did:plc:mockRootDid",
|
|
52
|
+
"handle": "mockRootUser.bsky.social",
|
|
53
|
+
"displayName": "Mock Root User",
|
|
54
|
+
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:mockRootDid/mockRootAvatar@jpeg",
|
|
55
|
+
"viewer": {
|
|
56
|
+
"muted": false,
|
|
57
|
+
"blockedBy": false,
|
|
58
|
+
"following": "at://did:plc:mockOtherFollow/app.bsky.graph.follow/mockRootFollowId"
|
|
59
|
+
},
|
|
60
|
+
"labels": [],
|
|
61
|
+
"createdAt": "2025-01-01T01:11:00.000Z"
|
|
62
|
+
},
|
|
63
|
+
"record": {
|
|
64
|
+
"$type": "app.bsky.feed.post",
|
|
65
|
+
"createdAt": "2025-01-01T01:12:00.000Z",
|
|
66
|
+
"langs": ["en"],
|
|
67
|
+
"text": "Mock text for root post."
|
|
68
|
+
},
|
|
69
|
+
"replyCount": 9,
|
|
70
|
+
"repostCount": 8,
|
|
71
|
+
"likeCount": 36,
|
|
72
|
+
"quoteCount": 0,
|
|
73
|
+
"indexedAt": "2025-01-01T01:13:00.000Z",
|
|
74
|
+
"viewer": {
|
|
75
|
+
"threadMuted": false,
|
|
76
|
+
"embeddingDisabled": false
|
|
77
|
+
},
|
|
78
|
+
"labels": []
|
|
79
|
+
},
|
|
80
|
+
"parent": {
|
|
81
|
+
"$type": "app.bsky.feed.defs#postView",
|
|
82
|
+
"uri": "at://did:plc:mockParentDid/app.bsky.feed.post/mockParentUri",
|
|
83
|
+
"cid": "mockParentCid",
|
|
84
|
+
"author": {
|
|
85
|
+
"did": "did:plc:mockParentDid",
|
|
86
|
+
"handle": "mockParentUser.bsky.social",
|
|
87
|
+
"displayName": "Mock Parent User",
|
|
88
|
+
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:mockParentDid/mockParentAvatar@jpeg",
|
|
89
|
+
"viewer": {
|
|
90
|
+
"muted": false,
|
|
91
|
+
"blockedBy": false
|
|
92
|
+
},
|
|
93
|
+
"labels": [],
|
|
94
|
+
"createdAt": "2025-01-01T02:15:00.000Z"
|
|
95
|
+
},
|
|
96
|
+
"record": {
|
|
97
|
+
"$type": "app.bsky.feed.post",
|
|
98
|
+
"createdAt": "2025-01-01T02:16:00.000Z",
|
|
99
|
+
"langs": ["en"],
|
|
100
|
+
"reply": {
|
|
101
|
+
"parent": {
|
|
102
|
+
"cid": "mockRootCid",
|
|
103
|
+
"uri": "at://did:plc:mockRootDid/app.bsky.feed.post/mockRootUri"
|
|
104
|
+
},
|
|
105
|
+
"root": {
|
|
106
|
+
"cid": "mockRootCid",
|
|
107
|
+
"uri": "at://did:plc:mockRootDid/app.bsky.feed.post/mockRootUri"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"text": "Mock text for parent post."
|
|
111
|
+
},
|
|
112
|
+
"replyCount": 1,
|
|
113
|
+
"repostCount": 0,
|
|
114
|
+
"likeCount": 1,
|
|
115
|
+
"quoteCount": 0,
|
|
116
|
+
"indexedAt": "2025-01-01T02:17:00.000Z",
|
|
117
|
+
"viewer": {
|
|
118
|
+
"threadMuted": false,
|
|
119
|
+
"embeddingDisabled": false
|
|
120
|
+
},
|
|
121
|
+
"labels": []
|
|
122
|
+
},
|
|
123
|
+
"grandparentAuthor": {
|
|
124
|
+
"did": "did:plc:mockDid",
|
|
125
|
+
"handle": "mockUser.bsky.social",
|
|
126
|
+
"displayName": "Mock User",
|
|
127
|
+
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:mockDid/mockAvatar@jpeg",
|
|
128
|
+
"viewer": {
|
|
129
|
+
"muted": false,
|
|
130
|
+
"blockedBy": false,
|
|
131
|
+
"following": "at://did:plc:anotherMockDid/app.bsky.graph.follow/mockGrandparentFollowId"
|
|
132
|
+
},
|
|
133
|
+
"labels": [],
|
|
134
|
+
"createdAt": "2025-01-01T03:00:00.000Z"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|