-
Notifications
You must be signed in to change notification settings - Fork 5.5k
[Components] bluesky - new components #15087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import app from "../../bluesky.app.mjs"; | ||
import constants from "../../common/constants.mjs"; | ||
|
||
export default { | ||
key: "bluesky-create-post", | ||
name: "Create Post", | ||
description: "Creates a new post on Bluesky. [See the documentation](https://docs.bsky.app/docs/api/com-atproto-repo-create-record).", | ||
version: "0.0.1", | ||
type: "action", | ||
props: { | ||
app, | ||
text: { | ||
type: "string", | ||
label: "Text", | ||
description: "The text content of the post.", | ||
}, | ||
}, | ||
async run({ $ }) { | ||
const { | ||
app, | ||
text, | ||
} = this; | ||
|
||
const response = await app.createRecord({ | ||
$, | ||
data: { | ||
collection: constants.RESOURCE_TYPE.POST, | ||
record: { | ||
["$type"]: constants.RESOURCE_TYPE.POST, | ||
text, | ||
createdAt: new Date().toISOString(), | ||
}, | ||
}, | ||
}); | ||
|
||
$.export("$summary", `Successfully created a new post with uri \`${response.uri}\`.`); | ||
return response; | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import app from "../../bluesky.app.mjs"; | ||
import constants from "../../common/constants.mjs"; | ||
|
||
export default { | ||
key: "bluesky-like-post", | ||
name: "Like Post", | ||
description: "Like a specific post on Bluesky. [See the documentation](https://docs.bsky.app/docs/api/com-atproto-repo-create-record).", | ||
version: "0.0.1", | ||
type: "action", | ||
props: { | ||
app, | ||
postUrl: { | ||
propDefinition: [ | ||
app, | ||
"postUrl", | ||
], | ||
}, | ||
}, | ||
async run({ $ }) { | ||
const { | ||
app, | ||
postUrl, | ||
} = this; | ||
|
||
const { | ||
handle, | ||
postId, | ||
} = app.getHandleAndPostIdFromUrl(postUrl); | ||
|
||
const { | ||
uri, | ||
cid, | ||
} = await app.getRecord({ | ||
$, | ||
params: { | ||
repo: handle, | ||
collection: constants.RESOURCE_TYPE.POST, | ||
rkey: postId, | ||
}, | ||
}); | ||
|
||
const response = await app.createRecord({ | ||
$, | ||
data: { | ||
collection: constants.RESOURCE_TYPE.LIKE, | ||
record: { | ||
["$type"]: constants.RESOURCE_TYPE.LIKE, | ||
createdAt: new Date().toISOString(), | ||
subject: { | ||
uri, | ||
cid, | ||
py_type: "com.atproto.repo.strongRef", | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
$.export("$summary", "Successfully liked post."); | ||
return response; | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import app from "../../bluesky.app.mjs"; | ||
|
||
export default { | ||
key: "bluesky-retrieve-thread", | ||
name: "Retrieve Thread", | ||
description: "Retrieve a full thread of posts. [See the documentation](https://docs.bsky.app/docs/api/app-bsky-feed-get-post-thread).", | ||
version: "0.0.1", | ||
type: "action", | ||
props: { | ||
app, | ||
postUrl: { | ||
propDefinition: [ | ||
app, | ||
"postUrl", | ||
], | ||
}, | ||
depth: { | ||
type: "integer", | ||
label: "Depth", | ||
description: "How many levels of reply depth should be included in response. Default is `6`.", | ||
optional: true, | ||
max: 100, | ||
}, | ||
parentHeight: { | ||
type: "integer", | ||
label: "Parent Height", | ||
description: "How many levels of parent (and grandparent, etc) post to include. Default is `80`.", | ||
optional: true, | ||
max: 100, | ||
}, | ||
}, | ||
methods: { | ||
getPostThread(args = {}) { | ||
return this.app._makeRequest({ | ||
path: "/app.bsky.feed.getPostThread", | ||
...args, | ||
}); | ||
}, | ||
}, | ||
async run({ $ }) { | ||
const { | ||
app, | ||
getPostThread, | ||
postUrl, | ||
depth, | ||
parentHeight, | ||
} = this; | ||
|
||
const { | ||
handle, | ||
postId, | ||
} = app.getHandleAndPostIdFromUrl(postUrl); | ||
|
||
const { did } = await app.resolveHandle({ | ||
$, | ||
params: { | ||
handle, | ||
}, | ||
}); | ||
|
||
const response = await getPostThread({ | ||
$, | ||
params: { | ||
uri: app.getPostUri(postId, did), | ||
depth, | ||
parentHeight, | ||
}, | ||
}); | ||
|
||
$.export("$summary", "Successfully retrieved thread."); | ||
return response; | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,11 +1,174 @@ | ||||||||||||||||||||
import { axios } from "@pipedream/platform"; | ||||||||||||||||||||
import constants from "./common/constants.mjs"; | ||||||||||||||||||||
import utils from "./common/utils.mjs"; | ||||||||||||||||||||
|
||||||||||||||||||||
export default { | ||||||||||||||||||||
type: "app", | ||||||||||||||||||||
app: "bluesky", | ||||||||||||||||||||
propDefinitions: {}, | ||||||||||||||||||||
propDefinitions: { | ||||||||||||||||||||
postUrl: { | ||||||||||||||||||||
type: "string", | ||||||||||||||||||||
label: "Post URL", | ||||||||||||||||||||
description: "The URL will look like `https://bsky.app/profile/myhandle.bsky.social/post/3le7x3qgmaw23`.", | ||||||||||||||||||||
}, | ||||||||||||||||||||
authorId: { | ||||||||||||||||||||
type: "string", | ||||||||||||||||||||
label: "Author ID", | ||||||||||||||||||||
description: "The ID of the author to track posts.", | ||||||||||||||||||||
}, | ||||||||||||||||||||
accountId: { | ||||||||||||||||||||
type: "string", | ||||||||||||||||||||
label: "Account ID", | ||||||||||||||||||||
description: "The ID of the account to monitor for new followers.", | ||||||||||||||||||||
}, | ||||||||||||||||||||
}, | ||||||||||||||||||||
methods: { | ||||||||||||||||||||
// this.$auth contains connected account data | ||||||||||||||||||||
authKeys() { | ||||||||||||||||||||
console.log(Object.keys(this.$auth)); | ||||||||||||||||||||
getHandleAndPostIdFromUrl(postUrl) { | ||||||||||||||||||||
const match = postUrl?.match(constants.HANDLE_AND_POST_ID_REGEX); | ||||||||||||||||||||
if (!match) { | ||||||||||||||||||||
throw new Error("Invalid post URL"); | ||||||||||||||||||||
} | ||||||||||||||||||||
const { | ||||||||||||||||||||
handle, | ||||||||||||||||||||
postId, | ||||||||||||||||||||
} = match.groups; | ||||||||||||||||||||
|
||||||||||||||||||||
return { | ||||||||||||||||||||
handle, | ||||||||||||||||||||
postId, | ||||||||||||||||||||
}; | ||||||||||||||||||||
}, | ||||||||||||||||||||
getPostUri(postId, did = this.getDID()) { | ||||||||||||||||||||
return `at://${did}/${constants.RESOURCE_TYPE.POST}/${postId}`; | ||||||||||||||||||||
}, | ||||||||||||||||||||
getDID() { | ||||||||||||||||||||
return this.$auth.did; | ||||||||||||||||||||
}, | ||||||||||||||||||||
Comment on lines
+44
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for missing DID. The method assumes getDID() {
+ if (!this.$auth.did) {
+ throw new Error("DID not found in authentication context");
+ }
return this.$auth.did;
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||
getUrl(path) { | ||||||||||||||||||||
return `${constants.BASE_URL}${constants.VERSION_PATH}${path}`; | ||||||||||||||||||||
}, | ||||||||||||||||||||
getHeaders(headers) { | ||||||||||||||||||||
return { | ||||||||||||||||||||
Authorization: `Bearer ${this.$auth.oauth_access_token}`, | ||||||||||||||||||||
...headers, | ||||||||||||||||||||
}; | ||||||||||||||||||||
}, | ||||||||||||||||||||
_makeRequest({ | ||||||||||||||||||||
$ = this, path, headers, ...args | ||||||||||||||||||||
} = {}) { | ||||||||||||||||||||
return axios($, { | ||||||||||||||||||||
...args, | ||||||||||||||||||||
url: this.getUrl(path), | ||||||||||||||||||||
headers: this.getHeaders(headers), | ||||||||||||||||||||
}); | ||||||||||||||||||||
}, | ||||||||||||||||||||
post(args = {}) { | ||||||||||||||||||||
return this._makeRequest({ | ||||||||||||||||||||
method: "POST", | ||||||||||||||||||||
...args, | ||||||||||||||||||||
}); | ||||||||||||||||||||
}, | ||||||||||||||||||||
createRecord(args = {}) { | ||||||||||||||||||||
return this.post({ | ||||||||||||||||||||
path: "/com.atproto.repo.createRecord", | ||||||||||||||||||||
...args, | ||||||||||||||||||||
data: { | ||||||||||||||||||||
...args.data, | ||||||||||||||||||||
repo: this.getDID(), | ||||||||||||||||||||
}, | ||||||||||||||||||||
}); | ||||||||||||||||||||
}, | ||||||||||||||||||||
getRecord(args = {}) { | ||||||||||||||||||||
return this._makeRequest({ | ||||||||||||||||||||
path: "/com.atproto.repo.getRecord", | ||||||||||||||||||||
...args, | ||||||||||||||||||||
}); | ||||||||||||||||||||
}, | ||||||||||||||||||||
resolveHandle(args = {}) { | ||||||||||||||||||||
return this._makeRequest({ | ||||||||||||||||||||
path: "/com.atproto.identity.resolveHandle", | ||||||||||||||||||||
...args, | ||||||||||||||||||||
}); | ||||||||||||||||||||
}, | ||||||||||||||||||||
getAuthorFeed(args = {}) { | ||||||||||||||||||||
return this._makeRequest({ | ||||||||||||||||||||
path: "/app.bsky.feed.getAuthorFeed", | ||||||||||||||||||||
...args, | ||||||||||||||||||||
}); | ||||||||||||||||||||
}, | ||||||||||||||||||||
getTimeline(args = {}) { | ||||||||||||||||||||
return this._makeRequest({ | ||||||||||||||||||||
path: "/app.bsky.feed.getTimeline", | ||||||||||||||||||||
...args, | ||||||||||||||||||||
}); | ||||||||||||||||||||
}, | ||||||||||||||||||||
getFollowers(args = {}) { | ||||||||||||||||||||
return this._makeRequest({ | ||||||||||||||||||||
path: "/app.bsky.graph.getFollowers", | ||||||||||||||||||||
...args, | ||||||||||||||||||||
}); | ||||||||||||||||||||
}, | ||||||||||||||||||||
async *getIterations({ | ||||||||||||||||||||
resourcesFn, resourcesFnArgs, resourceName, | ||||||||||||||||||||
lastDateAt, dateField, | ||||||||||||||||||||
max = constants.DEFAULT_MAX, | ||||||||||||||||||||
}) { | ||||||||||||||||||||
let cursor; | ||||||||||||||||||||
let resourcesCount = 0; | ||||||||||||||||||||
const firstRun = !lastDateAt; | ||||||||||||||||||||
|
||||||||||||||||||||
while (true) { | ||||||||||||||||||||
const response = await resourcesFn({ | ||||||||||||||||||||
...resourcesFnArgs, | ||||||||||||||||||||
params: { | ||||||||||||||||||||
...resourcesFnArgs?.params, | ||||||||||||||||||||
cursor, | ||||||||||||||||||||
limit: constants.DEFAULT_LIMIT, | ||||||||||||||||||||
}, | ||||||||||||||||||||
}); | ||||||||||||||||||||
|
||||||||||||||||||||
const nextResources = utils.getNestedProperty(response, resourceName); | ||||||||||||||||||||
|
||||||||||||||||||||
if (!nextResources?.length) { | ||||||||||||||||||||
console.log("No more resources found"); | ||||||||||||||||||||
return; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
for (const resource of nextResources) { | ||||||||||||||||||||
const isLastDateGreater = lastDateAt | ||||||||||||||||||||
&& Date.parse(lastDateAt) > Date.parse(utils.getNestedProperty(resource, dateField)); | ||||||||||||||||||||
|
||||||||||||||||||||
if (isLastDateGreater) { | ||||||||||||||||||||
console.log(`Last date is greater than the current resource date in ${dateField}`); | ||||||||||||||||||||
return; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (!isLastDateGreater) { | ||||||||||||||||||||
yield resource; | ||||||||||||||||||||
resourcesCount += 1; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (resourcesCount >= max) { | ||||||||||||||||||||
console.log("Reached max resources"); | ||||||||||||||||||||
return; | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (firstRun) { | ||||||||||||||||||||
console.log("First run: only one request processed"); | ||||||||||||||||||||
return; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (nextResources.length < constants.DEFAULT_LIMIT) { | ||||||||||||||||||||
console.log("No next page found"); | ||||||||||||||||||||
return; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
cursor = response.cursor; | ||||||||||||||||||||
} | ||||||||||||||||||||
}, | ||||||||||||||||||||
michelle0927 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
paginate(args = {}) { | ||||||||||||||||||||
return utils.iterate(this.getIterations(args)); | ||||||||||||||||||||
}, | ||||||||||||||||||||
}, | ||||||||||||||||||||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
const BASE_URL = "https://bsky.social"; | ||
const VERSION_PATH = "/xrpc"; | ||
|
||
const INTERACTION_EVENT = { | ||
REQUES_TLESS: "app.bsky.feed.defs#requestLess", | ||
REQUEST_MORE: "app.bsky.feed.defs#requestMore", | ||
CLICK_THROUGH_ITEM: "app.bsky.feed.defs#clickthroughItem", | ||
CLICK_THROUGH_AUTHOR: "app.bsky.feed.defs#clickthroughAuthor", | ||
CLICK_THROUGH_REPOSTER: "app.bsky.feed.defs#clickthroughReposter", | ||
CLICK_THROUGH_EMBED: "app.bsky.feed.defs#clickthroughEmbed", | ||
INTERACTION_SEEN: "app.bsky.feed.defs#interactionSeen", | ||
INTERACTION_LIKE: "app.bsky.feed.defs#interactionLike", | ||
INTERACTION_REPOST: "app.bsky.feed.defs#interactionRepost", | ||
INTERACTION_REPLY: "app.bsky.feed.defs#interactionReply", | ||
INTERACTION_QUOTE: "app.bsky.feed.defs#interactionQuote", | ||
INTERACTION_SHARE: "app.bsky.feed.defs#interactionShare", | ||
}; | ||
jcortes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const RESOURCE_TYPE = { | ||
POST: "app.bsky.feed.post", | ||
LIKE: "app.bsky.feed.like", | ||
}; | ||
|
||
const HANDLE_AND_POST_ID_REGEX = /(?:https?:\/\/)?(?:www\.)?(?:[^/]+)\/profile\/(?<handle>[^/]+)\/post\/(?<postId>[^/]+)/; | ||
|
||
const DEFAULT_LIMIT = 3; | ||
const DEFAULT_MAX = 600; | ||
const IS_FIRST_RUN = "isFirstRun"; | ||
const LAST_DATE_AT = "lastDateAt"; | ||
|
||
export default { | ||
BASE_URL, | ||
VERSION_PATH, | ||
INTERACTION_EVENT, | ||
RESOURCE_TYPE, | ||
HANDLE_AND_POST_ID_REGEX, | ||
DEFAULT_LIMIT, | ||
DEFAULT_MAX, | ||
IS_FIRST_RUN, | ||
LAST_DATE_AT, | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.