diff --git a/components/bluesky/actions/create-post/create-post.mjs b/components/bluesky/actions/create-post/create-post.mjs new file mode 100644 index 0000000000000..4f9be04ea4869 --- /dev/null +++ b/components/bluesky/actions/create-post/create-post.mjs @@ -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; + }, +}; diff --git a/components/bluesky/actions/like-post/like-post.mjs b/components/bluesky/actions/like-post/like-post.mjs new file mode 100644 index 0000000000000..cdaae7eb8d3b3 --- /dev/null +++ b/components/bluesky/actions/like-post/like-post.mjs @@ -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; + }, +}; diff --git a/components/bluesky/actions/retrieve-thread/retrieve-thread.mjs b/components/bluesky/actions/retrieve-thread/retrieve-thread.mjs new file mode 100644 index 0000000000000..ed0a4ee9e300a --- /dev/null +++ b/components/bluesky/actions/retrieve-thread/retrieve-thread.mjs @@ -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; + }, +}; diff --git a/components/bluesky/bluesky.app.mjs b/components/bluesky/bluesky.app.mjs index c759ef1cae494..4f74ce123e284 100644 --- a/components/bluesky/bluesky.app.mjs +++ b/components/bluesky/bluesky.app.mjs @@ -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; + }, + 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; + } + }, + paginate(args = {}) { + return utils.iterate(this.getIterations(args)); }, }, }; diff --git a/components/bluesky/common/constants.mjs b/components/bluesky/common/constants.mjs new file mode 100644 index 0000000000000..578245b441bc0 --- /dev/null +++ b/components/bluesky/common/constants.mjs @@ -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", +}; + +const RESOURCE_TYPE = { + POST: "app.bsky.feed.post", + LIKE: "app.bsky.feed.like", +}; + +const HANDLE_AND_POST_ID_REGEX = /(?:https?:\/\/)?(?:www\.)?(?:[^/]+)\/profile\/(?[^/]+)\/post\/(?[^/]+)/; + +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, +}; diff --git a/components/bluesky/common/utils.mjs b/components/bluesky/common/utils.mjs new file mode 100644 index 0000000000000..de7ee6c4a4692 --- /dev/null +++ b/components/bluesky/common/utils.mjs @@ -0,0 +1,17 @@ +async function iterate(iterations) { + const items = []; + for await (const item of iterations) { + items.push(item); + } + return items; +} + +function getNestedProperty(obj, propertyString) { + const properties = propertyString.split("."); + return properties.reduce((prev, curr) => prev?.[curr], obj); +} + +export default { + iterate, + getNestedProperty, +}; diff --git a/components/bluesky/package.json b/components/bluesky/package.json index b16789f0694b1..9ad450bfd688c 100644 --- a/components/bluesky/package.json +++ b/components/bluesky/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/bluesky", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Bluesky Components", "main": "bluesky.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "3.0.3" } -} \ No newline at end of file +} diff --git a/components/bluesky/sources/common/polling.mjs b/components/bluesky/sources/common/polling.mjs new file mode 100644 index 0000000000000..0a24e4b463dcf --- /dev/null +++ b/components/bluesky/sources/common/polling.mjs @@ -0,0 +1,110 @@ +import { + ConfigurationError, + DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import app from "../../bluesky.app.mjs"; +import constants from "../../common/constants.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + props: { + app, + db: "$.service.db", + timer: { + type: "$.interface.timer", + label: "Polling Schedule", + description: "How often to poll the API", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + deploy() { + this.setIsFirstRun(true); + }, + }, + methods: { + generateMeta() { + throw new ConfigurationError("generateMeta is not implemented"); + }, + setIsFirstRun(value) { + this.db.set(constants.IS_FIRST_RUN, value); + }, + getIsFirstRun() { + return this.db.get(constants.IS_FIRST_RUN); + }, + setLastDateAt(value) { + this.db.set(constants.LAST_DATE_AT, value); + }, + getLastDateAt() { + return this.db.get(constants.LAST_DATE_AT); + }, + getDateField() { + throw new ConfigurationError("getDateField is not implemented"); + }, + getResourceName() { + throw new ConfigurationError("getResourceName is not implemented"); + }, + getResourcesFn() { + throw new ConfigurationError("getResourcesFn is not implemented"); + }, + getResourcesFnArgs() { + throw new ConfigurationError("getResourcesFnArgs is not implemented"); + }, + processResource(resource) { + const meta = this.generateMeta(resource); + this.$emit(resource, meta); + }, + }, + async run() { + const { + app, + getDateField, + getLastDateAt, + getResourcesFn, + getResourcesFnArgs, + getResourceName, + processResource, + getIsFirstRun, + setIsFirstRun, + setLastDateAt, + } = this; + + const isFirstRun = getIsFirstRun(); + const dateField = getDateField(); + const lastDateAt = getLastDateAt(); + + const otherArgs = isFirstRun + ? { + max: constants.DEFAULT_LIMIT, + } + : { + dateField, + lastDateAt, + }; + + const resources = await app.paginate({ + resourcesFn: getResourcesFn(), + resourcesFnArgs: getResourcesFnArgs(), + resourceName: getResourceName(), + ...otherArgs, + }); + + if (resources.length) { + const [ + firstResource, + ] = Array.from(resources); + if (firstResource) { + setLastDateAt(utils.getNestedProperty(firstResource, dateField)); + } + } + + Array.from(resources) + .forEach(processResource); + + if (isFirstRun) { + setIsFirstRun(false); + } + }, +}; diff --git a/components/bluesky/sources/new-follower-on-account/new-follower-on-account.mjs b/components/bluesky/sources/new-follower-on-account/new-follower-on-account.mjs new file mode 100644 index 0000000000000..a679a42795575 --- /dev/null +++ b/components/bluesky/sources/new-follower-on-account/new-follower-on-account.mjs @@ -0,0 +1,49 @@ +import common from "../common/polling.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "bluesky-new-follower-on-account", + name: "New Follower On Account", + 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).", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + accountId: { + propDefinition: [ + common.props.app, + "accountId", + ], + }, + }, + methods: { + ...common.methods, + getDateField() { + return "createdAt"; + }, + getResourceName() { + return "followers"; + }, + getResourcesFn() { + return this.app.getFollowers; + }, + getResourcesFnArgs() { + return { + debug: true, + params: { + actor: this.accountId, + }, + }; + }, + generateMeta(resource) { + return { + id: resource.did, + summary: `New Follower ${resource.handle}`, + ts: Date.parse(resource.createdAt), + }; + }, + }, + sampleEmit, +}; diff --git a/components/bluesky/sources/new-follower-on-account/test-event.mjs b/components/bluesky/sources/new-follower-on-account/test-event.mjs new file mode 100644 index 0000000000000..26e8dd25184ec --- /dev/null +++ b/components/bluesky/sources/new-follower-on-account/test-event.mjs @@ -0,0 +1,13 @@ +export default { + "did": "did:plc:rwanjd2ci6wnzxmxbjqibfdsdl", + "handle": "test.bsky.social", + "displayName": "", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:rwdfasdfa@jpeg", + "viewer": { + "muted": false, + "blockedBy": false + }, + "labels": [], + "createdAt": "2024-12-25T17:22:17.844Z", + "indexedAt": "2024-12-25T17:22:17.844Z" +}; diff --git a/components/bluesky/sources/new-posts-by-author/new-posts-by-author.mjs b/components/bluesky/sources/new-posts-by-author/new-posts-by-author.mjs new file mode 100644 index 0000000000000..e1c706e048c0a --- /dev/null +++ b/components/bluesky/sources/new-posts-by-author/new-posts-by-author.mjs @@ -0,0 +1,50 @@ +import common from "../common/polling.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "bluesky-new-posts-by-author", + name: "New Posts By Author", + 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).", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + authorId: { + propDefinition: [ + common.props.app, + "authorId", + ], + }, + }, + methods: { + ...common.methods, + getDateField() { + return "post.record.createdAt"; + }, + getResourceName() { + return "feed"; + }, + getResourcesFn() { + return this.app.getAuthorFeed; + }, + getResourcesFnArgs() { + return { + debug: true, + params: { + actor: this.authorId, + }, + }; + }, + generateMeta(resource) { + const { post } = resource; + return { + id: post.cid, + summary: `New Post at ${post.record.createdAt}`, + ts: Date.parse(post.record.createdAt), + }; + }, + }, + sampleEmit, +}; diff --git a/components/bluesky/sources/new-posts-by-author/test-event.mjs b/components/bluesky/sources/new-posts-by-author/test-event.mjs new file mode 100644 index 0000000000000..0ca423d0a1a09 --- /dev/null +++ b/components/bluesky/sources/new-posts-by-author/test-event.mjs @@ -0,0 +1,137 @@ +export default { + post: { + uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockPostId", + cid: "bafyreimachangedxxxyyyzzz", + author: { + did: "did:plc:fakeOne", + handle: "john_doe.bsky.social", + displayName: "John Doe", + avatar: "https://mock.cdn/avatar/plain/did:plc:fakeOne/bafkremockavatar@jpeg", + viewer: { + muted: false, + blockedBy: false, + following: "at://did:plc:someOtherDid/app.bsky.graph.follow/mockFollow" + }, + labels: [], + createdAt: "2025-01-01T08:00:00.000Z", + }, + record: { + $type: "app.bsky.feed.post", + createdAt: "2025-01-01T12:00:00.000Z", + langs: ["en"], + reply: { + parent: { + cid: "bafyreimockparentxyz", + uri: "at://did:plc:someoneElseDid/app.bsky.feed.post/mockParentPost" + }, + root: { + cid: "bafyreimockrootabc", + uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockRootPost" + } + }, + text: "Mock text for demonstration of the post content structure.", + }, + replyCount: 0, + repostCount: 0, + likeCount: 1, + quoteCount: 0, + indexedAt: "2025-01-01T12:00:10.000Z", + viewer: { + threadMuted: false, + embeddingDisabled: false, + }, + labels: [], + }, + reply: { + root: { + $type: "app.bsky.feed.defs#postView", + uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockRootPost", + cid: "bafyreimockrootabc", + author: { + did: "did:plc:fakeOne", + handle: "john_doe.bsky.social", + displayName: "John Doe", + avatar: "https://mock.cdn/avatar/plain/did:plc:fakeOne/bafkremockavatar@jpeg", + viewer: { + muted: false, + blockedBy: false, + following: "at://did:plc:someOtherDid/app.bsky.graph.follow/mockFollow" + }, + labels: [], + createdAt: "2025-01-01T08:00:00.000Z" + }, + record: { + $type: "app.bsky.feed.post", + createdAt: "2025-01-01T09:00:00.000Z", + langs: ["en"], + text: "Mock text to show how the root reply might look.", + }, + replyCount: 5, + repostCount: 2, + likeCount: 10, + quoteCount: 0, + indexedAt: "2025-01-01T09:00:05.000Z", + viewer: { + threadMuted: false, + embeddingDisabled: false, + }, + labels: [], + }, + parent: { + $type: "app.bsky.feed.defs#postView", + uri: "at://did:plc:someoneElseDid/app.bsky.feed.post/mockParentPost", + cid: "bafyreimockparentxyz", + author: { + did: "did:plc:someoneElseDid", + handle: "alice_example.bsky.social", + displayName: "Alice Example", + avatar: "https://mock.cdn/avatar/plain/did:plc:someoneElseDid/bafkremockavatar@jpeg", + viewer: { + muted: false, + blockedBy: false, + }, + labels: [], + createdAt: "2025-01-01T07:00:00.000Z" + }, + record: { + $type: "app.bsky.feed.post", + createdAt: "2025-01-01T10:00:00.000Z", + langs: ["en"], + reply: { + parent: { + cid: "bafyreimockrootabc", + uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockRootPost" + }, + root: { + cid: "bafyreimockrootabc", + uri: "at://did:plc:fakeOne/app.bsky.feed.post/mockRootPost" + } + }, + text: "Mock text showing how a parent comment might appear in the structure." + }, + replyCount: 1, + repostCount: 0, + likeCount: 0, + quoteCount: 0, + indexedAt: "2025-01-01T10:00:05.000Z", + viewer: { + threadMuted: false, + embeddingDisabled: false, + }, + labels: [], + }, + grandparentAuthor: { + did: "did:plc:fakeOne", + handle: "john_doe.bsky.social", + displayName: "John Doe", + avatar: "https://mock.cdn/avatar/plain/did:plc:fakeOne/bafkremockavatar@jpeg", + viewer: { + muted: false, + blockedBy: false, + following: "at://did:plc:someOtherDid/app.bsky.graph.follow/mockFollow" + }, + labels: [], + createdAt: "2025-01-01T08:00:00.000Z", + }, + }, +}; diff --git a/components/bluesky/sources/new-timeline-posts/new-timeline-posts.mjs b/components/bluesky/sources/new-timeline-posts/new-timeline-posts.mjs new file mode 100644 index 0000000000000..f233c6452de45 --- /dev/null +++ b/components/bluesky/sources/new-timeline-posts/new-timeline-posts.mjs @@ -0,0 +1,38 @@ +import common from "../common/polling.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "bluesky-new-timeline-posts", + name: "New Timeline Posts", + 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).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getDateField() { + return "post.record.createdAt"; + }, + getResourceName() { + return "feed"; + }, + getResourcesFn() { + return this.app.getTimeline; + }, + getResourcesFnArgs() { + return { + debug: true, + }; + }, + generateMeta(resource) { + const { post } = resource; + return { + id: post.cid, + summary: `New Post at ${post.record.createdAt}`, + ts: Date.parse(post.record.createdAt), + }; + }, + }, + sampleEmit, +}; diff --git a/components/bluesky/sources/new-timeline-posts/test-event.mjs b/components/bluesky/sources/new-timeline-posts/test-event.mjs new file mode 100644 index 0000000000000..560f897f9aa54 --- /dev/null +++ b/components/bluesky/sources/new-timeline-posts/test-event.mjs @@ -0,0 +1,137 @@ +export default { + "post": { + "uri": "at://did:plc:mockDid/app.bsky.feed.post/mockUri", + "cid": "mockCid", + "author": { + "did": "did:plc:mockDid", + "handle": "mockUser.bsky.social", + "displayName": "Mock User", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:mockDid/mockAvatar@jpeg", + "viewer": { + "muted": false, + "blockedBy": false, + "following": "at://did:plc:mockFollow/app.bsky.graph.follow/mockId" + }, + "labels": [], + "createdAt": "2025-01-01T00:00:00.000Z" + }, + "record": { + "$type": "app.bsky.feed.post", + "createdAt": "2025-01-01T00:00:00.000Z", + "langs": ["en"], + "reply": { + "parent": { + "cid": "mockParentCid", + "uri": "at://did:plc:mockParentDid/app.bsky.feed.post/mockParentUri" + }, + "root": { + "cid": "mockRootCid", + "uri": "at://did:plc:mockRootDid/app.bsky.feed.post/mockRootUri" + } + }, + "text": "This is some mock post content." + }, + "replyCount": 0, + "repostCount": 0, + "likeCount": 0, + "quoteCount": 0, + "indexedAt": "2025-01-01T00:00:00.000Z", + "viewer": { + "threadMuted": false, + "embeddingDisabled": false + }, + "labels": [] + }, + "reply": { + "root": { + "$type": "app.bsky.feed.defs#postView", + "uri": "at://did:plc:mockRootDid/app.bsky.feed.post/mockRootUri", + "cid": "mockRootCid", + "author": { + "did": "did:plc:mockRootDid", + "handle": "mockRootUser.bsky.social", + "displayName": "Mock Root User", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:mockRootDid/mockRootAvatar@jpeg", + "viewer": { + "muted": false, + "blockedBy": false, + "following": "at://did:plc:mockOtherFollow/app.bsky.graph.follow/mockRootFollowId" + }, + "labels": [], + "createdAt": "2025-01-01T01:11:00.000Z" + }, + "record": { + "$type": "app.bsky.feed.post", + "createdAt": "2025-01-01T01:12:00.000Z", + "langs": ["en"], + "text": "Mock text for root post." + }, + "replyCount": 9, + "repostCount": 8, + "likeCount": 36, + "quoteCount": 0, + "indexedAt": "2025-01-01T01:13:00.000Z", + "viewer": { + "threadMuted": false, + "embeddingDisabled": false + }, + "labels": [] + }, + "parent": { + "$type": "app.bsky.feed.defs#postView", + "uri": "at://did:plc:mockParentDid/app.bsky.feed.post/mockParentUri", + "cid": "mockParentCid", + "author": { + "did": "did:plc:mockParentDid", + "handle": "mockParentUser.bsky.social", + "displayName": "Mock Parent User", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:mockParentDid/mockParentAvatar@jpeg", + "viewer": { + "muted": false, + "blockedBy": false + }, + "labels": [], + "createdAt": "2025-01-01T02:15:00.000Z" + }, + "record": { + "$type": "app.bsky.feed.post", + "createdAt": "2025-01-01T02:16:00.000Z", + "langs": ["en"], + "reply": { + "parent": { + "cid": "mockRootCid", + "uri": "at://did:plc:mockRootDid/app.bsky.feed.post/mockRootUri" + }, + "root": { + "cid": "mockRootCid", + "uri": "at://did:plc:mockRootDid/app.bsky.feed.post/mockRootUri" + } + }, + "text": "Mock text for parent post." + }, + "replyCount": 1, + "repostCount": 0, + "likeCount": 1, + "quoteCount": 0, + "indexedAt": "2025-01-01T02:17:00.000Z", + "viewer": { + "threadMuted": false, + "embeddingDisabled": false + }, + "labels": [] + }, + "grandparentAuthor": { + "did": "did:plc:mockDid", + "handle": "mockUser.bsky.social", + "displayName": "Mock User", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:mockDid/mockAvatar@jpeg", + "viewer": { + "muted": false, + "blockedBy": false, + "following": "at://did:plc:anotherMockDid/app.bsky.graph.follow/mockGrandparentFollowId" + }, + "labels": [], + "createdAt": "2025-01-01T03:00:00.000Z" + } + } +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c607001294094..7414e453b0078 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,8 +94,8 @@ importers: specifier: ^12.3.4 version: 12.5.0(enquirer@2.4.1) pnpm: - specifier: 9.14.3 - version: 9.14.3 + specifier: 9.14.2 + version: 9.14.2 putout: specifier: '>=36' version: 36.13.1(eslint@8.57.1)(typescript@5.6.3) @@ -1230,7 +1230,11 @@ importers: components/bluecart_api: {} - components/bluesky: {} + components/bluesky: + dependencies: + '@pipedream/platform': + specifier: 3.0.3 + version: 3.0.3 components/bluesky_by_unshape: dependencies: @@ -23118,8 +23122,8 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - pnpm@9.14.3: - resolution: {integrity: sha512-wPU+6ZR37ZabgrKJrQEaXRa/FiPJV+fynqvo0MALV0wpuMf1T2xn7nEMc/KFyBVNB85EtG/iwO60dqkEQbrDcQ==} + pnpm@9.14.2: + resolution: {integrity: sha512-biuvd9Brk2IpQVLIUcTyeO3jerHro6Vf2jF6SheyCfTbuXP7JQp3q8Rjo0H8sfF/F8+iQJHE6zGc2g2bhCeDhw==} engines: {node: '>=18.12'} hasBin: true @@ -30708,8 +30712,6 @@ snapshots: '@putout/operator-filesystem': 5.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3)) '@putout/operator-json': 2.2.0 putout: 36.13.1(eslint@8.57.1)(typescript@5.6.3) - transitivePeerDependencies: - - supports-color '@putout/operator-regexp@1.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3))': dependencies: @@ -40518,7 +40520,7 @@ snapshots: pluralize@8.0.0: {} - pnpm@9.14.3: {} + pnpm@9.14.2: {} points-on-curve@0.2.0: {}