From 02d727c8621bcc3539296252028b971033c5b50a Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 7 Jan 2025 14:41:18 -0300 Subject: [PATCH 1/3] typefully init --- .../actions/create-draft/create-draft.mjs | 52 ++++ .../schedule-draft-next-slot.mjs | 52 ++++ .../actions/schedule-draft/schedule-draft.mjs | 59 +++++ components/typefully/package.json | 2 +- .../new-draft-published.mjs | 107 ++++++++ .../new-draft-scheduled.mjs | 88 +++++++ components/typefully/typefully.app.mjs | 228 +++++++++++++++++- 7 files changed, 584 insertions(+), 4 deletions(-) create mode 100644 components/typefully/actions/create-draft/create-draft.mjs create mode 100644 components/typefully/actions/schedule-draft-next-slot/schedule-draft-next-slot.mjs create mode 100644 components/typefully/actions/schedule-draft/schedule-draft.mjs create mode 100644 components/typefully/sources/new-draft-published/new-draft-published.mjs create mode 100644 components/typefully/sources/new-draft-scheduled/new-draft-scheduled.mjs diff --git a/components/typefully/actions/create-draft/create-draft.mjs b/components/typefully/actions/create-draft/create-draft.mjs new file mode 100644 index 0000000000000..3dc0543963bc0 --- /dev/null +++ b/components/typefully/actions/create-draft/create-draft.mjs @@ -0,0 +1,52 @@ +import typefully from "../../typefully.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "typefully-create-draft", + name: "Create Draft", + description: "Creates a new draft in Typefully. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + typefully, + content: { + propDefinition: [ + typefully, + "content", + ], + }, + threadify: { + propDefinition: [ + typefully, + "threadify", + ], + optional: true, + }, + share: { + propDefinition: [ + typefully, + "share", + ], + optional: true, + }, + autoRetweetEnabled: { + propDefinition: [ + typefully, + "autoRetweetEnabled", + ], + optional: true, + }, + autoPlugEnabled: { + propDefinition: [ + typefully, + "autoPlugEnabled", + ], + optional: true, + }, + }, + async run({ $ }) { + const draft = await this.typefully.createDraft(); + $.export("$summary", `Created draft with ID: ${draft.id} and content: "${this.content}"`); + return draft; + }, +}; diff --git a/components/typefully/actions/schedule-draft-next-slot/schedule-draft-next-slot.mjs b/components/typefully/actions/schedule-draft-next-slot/schedule-draft-next-slot.mjs new file mode 100644 index 0000000000000..462c73deb06f4 --- /dev/null +++ b/components/typefully/actions/schedule-draft-next-slot/schedule-draft-next-slot.mjs @@ -0,0 +1,52 @@ +import typefully from "../../typefully.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "typefully-schedule-draft-next-slot", + name: "Schedule Draft Next Slot", + description: "Schedules an existing draft for publication in the next available time slot. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + typefully, + content: { + propDefinition: [ + typefully, + "content", + ], + }, + threadify: { + propDefinition: [ + typefully, + "threadify", + ], + optional: true, + }, + share: { + propDefinition: [ + typefully, + "share", + ], + optional: true, + }, + autoRetweetEnabled: { + propDefinition: [ + typefully, + "autoRetweetEnabled", + ], + optional: true, + }, + autoPlugEnabled: { + propDefinition: [ + typefully, + "autoPlugEnabled", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.typefully.scheduleDraftNextAvailableSlot(); + $.export("$summary", "Draft scheduled successfully"); + return response; + }, +}; diff --git a/components/typefully/actions/schedule-draft/schedule-draft.mjs b/components/typefully/actions/schedule-draft/schedule-draft.mjs new file mode 100644 index 0000000000000..ac68d97715112 --- /dev/null +++ b/components/typefully/actions/schedule-draft/schedule-draft.mjs @@ -0,0 +1,59 @@ +import typefully from "../../typefully.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "typefully-schedule-draft", + name: "Schedule Draft", + description: "Schedules a draft for publication at a specific date and time. [See the documentation](https://support.typefully.com/en/articles/8718287-typefully-api)", + version: "0.0.{{ts}}", + type: "action", + props: { + typefully, + content: { + propDefinition: [ + "typefully", + "content", + ], + }, + scheduleDate: { + propDefinition: [ + "typefully", + "scheduleDate", + ], + optional: false, + }, + threadify: { + propDefinition: [ + "typefully", + "threadify", + ], + optional: true, + }, + share: { + propDefinition: [ + "typefully", + "share", + ], + optional: true, + }, + autoRetweetEnabled: { + propDefinition: [ + "typefully", + "autoRetweetEnabled", + ], + optional: true, + }, + autoPlugEnabled: { + propDefinition: [ + "typefully", + "autoPlugEnabled", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.typefully.scheduleDraftAtSpecificDate(); + $.export("$summary", `Draft scheduled for ${this.scheduleDate}`); + return response; + }, +}; diff --git a/components/typefully/package.json b/components/typefully/package.json index 0d329147b4171..8d46af8b85237 100644 --- a/components/typefully/package.json +++ b/components/typefully/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/typefully/sources/new-draft-published/new-draft-published.mjs b/components/typefully/sources/new-draft-published/new-draft-published.mjs new file mode 100644 index 0000000000000..3ba5ef374d633 --- /dev/null +++ b/components/typefully/sources/new-draft-published/new-draft-published.mjs @@ -0,0 +1,107 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import typefully from "../../typefully.app.mjs"; + +export default { + key: "typefully-new-draft-published", + name: "New Draft Published", + description: "Emit new event when a draft is published to Twitter via Typefully. [See the documentation](#)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + typefully, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + kind: { + propDefinition: [ + "typefully", + "kind", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + try { + const drafts = await this.typefully.getRecentlyPublishedDrafts({ + params: { + ...(this.kind + ? { + kind: this.kind, + } + : {}), + }, + }); + const sortedDrafts = drafts.sort((a, b) => new Date(b.published_at) - new Date(a.published_at)); + const draftsToEmit = sortedDrafts.slice(0, 50); + + for (const draft of draftsToEmit) { + this.$emit(draft, { + id: draft.id || new Date(draft.published_at).getTime(), + summary: `Draft Published: ${draft.content.substring(0, 50)}...`, + ts: new Date(draft.published_at).getTime(), + }); + } + + if (draftsToEmit.length > 0) { + const latestTimestamp = new Date(draftsToEmit[0].published_at).getTime(); + await this.db.set("last_published_ts", latestTimestamp); + } + } catch (error) { + this.$emit(error, { + summary: "Error during deploy", + ts: Date.now(), + }); + } + }, + async activate() { + // No webhook subscription needed for polling source + }, + async deactivate() { + // No webhook unsubscription needed for polling source + }, + }, + async run() { + try { + const lastTs = (await this.db.get("last_published_ts")) || 0; + const drafts = await this.typefully.getRecentlyPublishedDrafts({ + params: { + ...(this.kind + ? { + kind: this.kind, + } + : {}), + }, + }); + + const newDrafts = drafts.filter((draft) => new Date(draft.published_at).getTime() > lastTs); + const sortedNewDrafts = newDrafts.sort((a, b) => new Date(a.published_at) - new Date(b.published_at)); + + for (const draft of sortedNewDrafts) { + this.$emit(draft, { + id: draft.id || new Date(draft.published_at).getTime(), + summary: `Draft Published: ${draft.content.substring(0, 50)}...`, + ts: new Date(draft.published_at).getTime(), + }); + } + + if (drafts.length > 0) { + const latestDraft = drafts[0]; + const latestTs = new Date(latestDraft.published_at).getTime(); + await this.db.set("last_published_ts", latestTs); + } + } catch (error) { + this.$emit(error, { + summary: "Error during run", + ts: Date.now(), + }); + } + }, +}; diff --git a/components/typefully/sources/new-draft-scheduled/new-draft-scheduled.mjs b/components/typefully/sources/new-draft-scheduled/new-draft-scheduled.mjs new file mode 100644 index 0000000000000..6964201194534 --- /dev/null +++ b/components/typefully/sources/new-draft-scheduled/new-draft-scheduled.mjs @@ -0,0 +1,88 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import typefully from "../../typefully.app.mjs"; + +export default { + key: "typefully-new-draft-scheduled", + name: "New Draft Scheduled", + description: "Emit a new event when a draft is scheduled for publication or added to the queue. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + typefully: { + type: "app", + app: "typefully", + }, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + contentFilter: { + propDefinition: [ + "typefully", + "contentFilter", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + try { + const drafts = await this.typefully.getRecentlyScheduledDrafts(); + const latestDrafts = drafts.slice(-50).reverse(); + for (const draft of latestDrafts) { + this.$emit( + draft, + { + id: draft.id, + summary: `Scheduled draft: ${draft.content}`, + ts: draft.schedule_date + ? Date.parse(draft.schedule_date) + : Date.now(), + }, + ); + } + } catch (error) { + this.$emit(error, { + summary: "Error during deploy hook", + ts: Date.now(), + }); + } + }, + async activate() { + // No activation steps required for polling source + }, + async deactivate() { + // No deactivation steps required for polling source + }, + }, + async run() { + try { + const drafts = await this.typefully.getRecentlyScheduledDrafts({ + content_filter: this.contentFilter, + }); + for (const draft of drafts) { + this.$emit( + draft, + { + id: draft.id, + summary: `Scheduled draft: ${draft.content}`, + ts: draft.schedule_date + ? Date.parse(draft.schedule_date) + : Date.now(), + }, + ); + } + } catch (error) { + this.$emit(error, { + summary: "Error during run method", + ts: Date.now(), + }); + } + }, +}; diff --git a/components/typefully/typefully.app.mjs b/components/typefully/typefully.app.mjs index 8d5b67d9a762a..ce055815b0edf 100644 --- a/components/typefully/typefully.app.mjs +++ b/components/typefully/typefully.app.mjs @@ -1,11 +1,233 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "typefully", - propDefinitions: {}, + version: "0.0.{{ts}}", + propDefinitions: { + kind: { + type: "string", + label: "Kind", + description: "Filter notifications by kind", + optional: true, + async options() { + return [ + { + label: "Inbox", + value: "inbox", + }, + { + label: "Activity", + value: "activity", + }, + ]; + }, + }, + contentFilter: { + type: "string", + label: "Content Filter", + description: "Filter drafts by content type", + optional: true, + async options() { + return [ + { + label: "Threads", + value: "threads", + }, + { + label: "Tweets", + value: "tweets", + }, + ]; + }, + }, + content: { + type: "string", + label: "Content", + description: "The content of the draft", + }, + threadify: { + type: "boolean", + label: "Threadify", + description: "Automatically split content into a thread", + optional: true, + }, + share: { + type: "boolean", + label: "Share", + description: "Include a share URL in the draft", + optional: true, + }, + autoRetweetEnabled: { + type: "boolean", + label: "Auto Retweet Enabled", + description: "Enable auto-retweet for the draft", + optional: true, + }, + autoPlugEnabled: { + type: "boolean", + label: "Auto Plug Enabled", + description: "Enable auto-plug for the draft", + optional: true, + }, + scheduleDate: { + type: "string", + label: "Schedule Date", + description: "Date to schedule the draft (ISO format) or 'next-free-slot'", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.typefully.com/v1"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: `${this._baseUrl()}${path}`, + headers: { + ...headers, + "Authorization": `Bearer ${this.$auth.access_token}`, + "Content-Type": "application/json", + }, + }); + }, + async createDraft(opts = {}) { + const data = { + content: this.content, + ...(this.threadify !== undefined && { + threadify: this.threadify, + }), + ...(this.share !== undefined && { + share: this.share, + }), + ...(this.autoRetweetEnabled !== undefined && { + auto_retweet_enabled: this.autoRetweetEnabled, + }), + ...(this.autoPlugEnabled !== undefined && { + auto_plug_enabled: this.autoPlugEnabled, + }), + ...(this.scheduleDate !== undefined && { + "schedule-date": this.scheduleDate, + }), + }; + return this._makeRequest({ + method: "POST", + path: "/drafts/", + data, + ...opts, + }); + }, + async scheduleDraftNextAvailableSlot(opts = {}) { + const data = { + "content": this.content, + "schedule-date": "next-free-slot", + ...(this.threadify !== undefined && { + threadify: this.threadify, + }), + ...(this.share !== undefined && { + share: this.share, + }), + ...(this.autoRetweetEnabled !== undefined && { + auto_retweet_enabled: this.autoRetweetEnabled, + }), + ...(this.autoPlugEnabled !== undefined && { + auto_plug_enabled: this.autoPlugEnabled, + }), + }; + return this._makeRequest({ + method: "POST", + path: "/drafts/", + data, + ...opts, + }); + }, + async scheduleDraftAtSpecificDate(opts = {}) { + if (!this.scheduleDate) { + throw new Error("scheduleDate is required and must be an ISO formatted date."); + } + const data = { + "content": this.content, + "schedule-date": this.scheduleDate, + ...(this.threadify !== undefined && { + threadify: this.threadify, + }), + ...(this.share !== undefined && { + share: this.share, + }), + ...(this.autoRetweetEnabled !== undefined && { + auto_retweet_enabled: this.autoRetweetEnabled, + }), + ...(this.autoPlugEnabled !== undefined && { + auto_plug_enabled: this.autoPlugEnabled, + }), + }; + return this._makeRequest({ + method: "POST", + path: "/drafts/", + data, + ...opts, + }); + }, + async getRecentlyScheduledDrafts(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/drafts/recently-scheduled/", + params: { + ...(this.contentFilter !== undefined && { + content_filter: this.contentFilter, + }), + ...opts.params, + }, + ...opts, + }); + }, + async getRecentlyPublishedDrafts(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/drafts/recently-published/", + ...opts, + }); + }, + async emitDraftPublishedEvent() { + // Implementation to emit event when a draft is published + // This method should interface with Pipedream's event emitting system + }, + async emitDraftScheduledEvent() { + // Implementation to emit event when a draft is scheduled or queued + // This method should interface with Pipedream's event emitting system + }, + async getNotifications(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/notifications/", + params: { + ...(this.kind !== undefined && { + kind: this.kind, + }), + ...opts.params, + }, + ...opts, + }); + }, + async paginate(fn, ...opts) { + let results = []; + let response = await fn(...opts); + results.push(...response.items); + while (response.hasMore) { + response = await fn(...opts, { + page: response.nextPage, + }); + results.push(...response.items); + } + return results; + }, }, -}; \ No newline at end of file +}; From 4490a8d588e827fad6860ae6a70183ba7b0787da Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 8 Jan 2025 13:10:51 -0300 Subject: [PATCH 2/3] [Components] typefully #15184 Sources - New Draft Published - New Draft Scheduled Actions - Create Draft - Schedule Draft Next Slot - Schedule Draft --- .../actions/create-draft/create-draft.mjs | 21 ++- .../schedule-draft-next-slot.mjs | 20 ++- .../actions/schedule-draft/schedule-draft.mjs | 43 +++-- components/typefully/package.json | 5 +- components/typefully/sources/common/base.mjs | 57 +++++++ .../new-draft-published.mjs | 109 ++---------- .../new-draft-published/test-event.mjs | 15 ++ .../new-draft-scheduled.mjs | 90 ++-------- .../new-draft-scheduled/test-event.mjs | 15 ++ components/typefully/typefully.app.mjs | 157 ++---------------- 10 files changed, 189 insertions(+), 343 deletions(-) create mode 100644 components/typefully/sources/common/base.mjs create mode 100644 components/typefully/sources/new-draft-published/test-event.mjs create mode 100644 components/typefully/sources/new-draft-scheduled/test-event.mjs diff --git a/components/typefully/actions/create-draft/create-draft.mjs b/components/typefully/actions/create-draft/create-draft.mjs index 3dc0543963bc0..9f0c2a3827856 100644 --- a/components/typefully/actions/create-draft/create-draft.mjs +++ b/components/typefully/actions/create-draft/create-draft.mjs @@ -1,11 +1,10 @@ import typefully from "../../typefully.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "typefully-create-draft", name: "Create Draft", - description: "Creates a new draft in Typefully. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Creates a new draft in Typefully. [See the documentation](https://support.typefully.com/en/articles/8718287-typefully-api#h_df59629cbf)", + version: "0.0.1", type: "action", props: { typefully, @@ -45,8 +44,18 @@ export default { }, }, async run({ $ }) { - const draft = await this.typefully.createDraft(); - $.export("$summary", `Created draft with ID: ${draft.id} and content: "${this.content}"`); - return draft; + const response = await this.typefully.createDraft({ + $, + data: { + content: this.content, + threadify: this.threadify, + share: this.share, + auto_retweet_enabled: this.autoRetweetEnabled, + auto_plug_enabled: this.autoPlugEnabled, + }, + }); + + $.export("$summary", `Created draft with ID: ${response.id}.`); + return response; }, }; diff --git a/components/typefully/actions/schedule-draft-next-slot/schedule-draft-next-slot.mjs b/components/typefully/actions/schedule-draft-next-slot/schedule-draft-next-slot.mjs index 462c73deb06f4..ade6b6b3c45f1 100644 --- a/components/typefully/actions/schedule-draft-next-slot/schedule-draft-next-slot.mjs +++ b/components/typefully/actions/schedule-draft-next-slot/schedule-draft-next-slot.mjs @@ -1,11 +1,10 @@ import typefully from "../../typefully.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "typefully-schedule-draft-next-slot", name: "Schedule Draft Next Slot", - description: "Schedules an existing draft for publication in the next available time slot. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Schedules an existing draft for publication in the next available time slot. [See the documentation](https://support.typefully.com/en/articles/8718287-typefully-api#h_df59629cbf)", + version: "0.0.1", type: "action", props: { typefully, @@ -45,8 +44,19 @@ export default { }, }, async run({ $ }) { - const response = await this.typefully.scheduleDraftNextAvailableSlot(); - $.export("$summary", "Draft scheduled successfully"); + const response = await this.typefully.createDraft({ + $, + data: { + "content": this.content, + "threadify": this.threadify, + "share": this.share, + "schedule-date": "next-free-slot", + "auto_retweet_enabled": this.autoRetweetEnabled, + "auto_plug_enabled": this.autoPlugEnabled, + }, + }); + + $.export("$summary", `Draft scheduled successfully with ID: ${response.id}.`); return response; }, }; diff --git a/components/typefully/actions/schedule-draft/schedule-draft.mjs b/components/typefully/actions/schedule-draft/schedule-draft.mjs index ac68d97715112..7cfa0aafe00c3 100644 --- a/components/typefully/actions/schedule-draft/schedule-draft.mjs +++ b/components/typefully/actions/schedule-draft/schedule-draft.mjs @@ -1,59 +1,68 @@ import typefully from "../../typefully.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "typefully-schedule-draft", name: "Schedule Draft", - description: "Schedules a draft for publication at a specific date and time. [See the documentation](https://support.typefully.com/en/articles/8718287-typefully-api)", - version: "0.0.{{ts}}", + description: "Schedules a draft for publication at a specific date and time. [See the documentation](https://support.typefully.com/en/articles/8718287-typefully-api#h_df59629cbf)", + version: "0.0.1", type: "action", props: { typefully, content: { propDefinition: [ - "typefully", + typefully, "content", ], }, - scheduleDate: { - propDefinition: [ - "typefully", - "scheduleDate", - ], - optional: false, - }, threadify: { propDefinition: [ - "typefully", + typefully, "threadify", ], optional: true, }, share: { propDefinition: [ - "typefully", + typefully, "share", ], optional: true, }, + scheduleDate: { + propDefinition: [ + typefully, + "scheduleDate", + ], + }, autoRetweetEnabled: { propDefinition: [ - "typefully", + typefully, "autoRetweetEnabled", ], optional: true, }, autoPlugEnabled: { propDefinition: [ - "typefully", + typefully, "autoPlugEnabled", ], optional: true, }, }, async run({ $ }) { - const response = await this.typefully.scheduleDraftAtSpecificDate(); - $.export("$summary", `Draft scheduled for ${this.scheduleDate}`); + const response = await this.typefully.createDraft({ + $, + data: { + "content": this.content, + "threadify": this.threadify, + "share": this.share, + "schedule-date": this.scheduleDate, + "auto_retweet_enabled": this.autoRetweetEnabled, + "auto_plug_enabled": this.autoPlugEnabled, + }, + }); + + $.export("$summary", `Draft scheduled successfully with ID: ${response.id}.`); return response; }, }; diff --git a/components/typefully/package.json b/components/typefully/package.json index 8d46af8b85237..04f55f3dd9362 100644 --- a/components/typefully/package.json +++ b/components/typefully/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/typefully", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Typefully Components", "main": "typefully.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/typefully/sources/common/base.mjs b/components/typefully/sources/common/base.mjs new file mode 100644 index 0000000000000..39c8b4bb5420e --- /dev/null +++ b/components/typefully/sources/common/base.mjs @@ -0,0 +1,57 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import typefully from "../../typefully.app.mjs"; + +export default { + props: { + typefully, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastId() { + return this.db.get("lastId") || 0; + }, + _setLastId(lastId) { + this.db.set("lastId", lastId); + }, + async emitEvent(maxResults = false) { + const lastId = this._getLastId(); + const fn = await this.getFunction(); + const response = await fn(); + + let responseArray = []; + for await (const item of response) { + if (item.id <= lastId) break; + responseArray.push(item); + } + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastId(responseArray[0].id); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(new Date()), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/typefully/sources/new-draft-published/new-draft-published.mjs b/components/typefully/sources/new-draft-published/new-draft-published.mjs index 3ba5ef374d633..79a6e2db5f90c 100644 --- a/components/typefully/sources/new-draft-published/new-draft-published.mjs +++ b/components/typefully/sources/new-draft-published/new-draft-published.mjs @@ -1,107 +1,22 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import typefully from "../../typefully.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "typefully-new-draft-published", name: "New Draft Published", - description: "Emit new event when a draft is published to Twitter via Typefully. [See the documentation](#)", - version: "0.0.{{ts}}", + description: "Emit new event when a draft is published to Twitter via Typefully.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - typefully, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + methods: { + ...common.methods, + getFunction() { + return this.typefully.getRecentlyPublishedDrafts; }, - kind: { - propDefinition: [ - "typefully", - "kind", - ], - optional: true, + getSummary(draft) { + return `Draft Published: ${draft.id}`; }, }, - hooks: { - async deploy() { - try { - const drafts = await this.typefully.getRecentlyPublishedDrafts({ - params: { - ...(this.kind - ? { - kind: this.kind, - } - : {}), - }, - }); - const sortedDrafts = drafts.sort((a, b) => new Date(b.published_at) - new Date(a.published_at)); - const draftsToEmit = sortedDrafts.slice(0, 50); - - for (const draft of draftsToEmit) { - this.$emit(draft, { - id: draft.id || new Date(draft.published_at).getTime(), - summary: `Draft Published: ${draft.content.substring(0, 50)}...`, - ts: new Date(draft.published_at).getTime(), - }); - } - - if (draftsToEmit.length > 0) { - const latestTimestamp = new Date(draftsToEmit[0].published_at).getTime(); - await this.db.set("last_published_ts", latestTimestamp); - } - } catch (error) { - this.$emit(error, { - summary: "Error during deploy", - ts: Date.now(), - }); - } - }, - async activate() { - // No webhook subscription needed for polling source - }, - async deactivate() { - // No webhook unsubscription needed for polling source - }, - }, - async run() { - try { - const lastTs = (await this.db.get("last_published_ts")) || 0; - const drafts = await this.typefully.getRecentlyPublishedDrafts({ - params: { - ...(this.kind - ? { - kind: this.kind, - } - : {}), - }, - }); - - const newDrafts = drafts.filter((draft) => new Date(draft.published_at).getTime() > lastTs); - const sortedNewDrafts = newDrafts.sort((a, b) => new Date(a.published_at) - new Date(b.published_at)); - - for (const draft of sortedNewDrafts) { - this.$emit(draft, { - id: draft.id || new Date(draft.published_at).getTime(), - summary: `Draft Published: ${draft.content.substring(0, 50)}...`, - ts: new Date(draft.published_at).getTime(), - }); - } - - if (drafts.length > 0) { - const latestDraft = drafts[0]; - const latestTs = new Date(latestDraft.published_at).getTime(); - await this.db.set("last_published_ts", latestTs); - } - } catch (error) { - this.$emit(error, { - summary: "Error during run", - ts: Date.now(), - }); - } - }, + sampleEmit, }; diff --git a/components/typefully/sources/new-draft-published/test-event.mjs b/components/typefully/sources/new-draft-published/test-event.mjs new file mode 100644 index 0000000000000..27370baa8303b --- /dev/null +++ b/components/typefully/sources/new-draft-published/test-event.mjs @@ -0,0 +1,15 @@ +export default { + "id": 123, + "status": "published", + "html": "content test", + "num_tweets": 1, + "last_edited": null, + "scheduled_date": null, + "published_on": "2025-01-09T10:22:11Z", + "share_url": "https://typefully.com/t/nb9Lxx13df", + "twitter_url": null, + "linkedin_url": null, + "text_first_tweet": "content", + "html_first_tweet": "content", + "text_preview_linkedin": null +} \ No newline at end of file diff --git a/components/typefully/sources/new-draft-scheduled/new-draft-scheduled.mjs b/components/typefully/sources/new-draft-scheduled/new-draft-scheduled.mjs index 6964201194534..790400310a02a 100644 --- a/components/typefully/sources/new-draft-scheduled/new-draft-scheduled.mjs +++ b/components/typefully/sources/new-draft-scheduled/new-draft-scheduled.mjs @@ -1,88 +1,22 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import typefully from "../../typefully.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "typefully-new-draft-scheduled", name: "New Draft Scheduled", - description: "Emit a new event when a draft is scheduled for publication or added to the queue. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Emit new event when a draft is scheduled for publication.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - typefully: { - type: "app", - app: "typefully", + methods: { + ...common.methods, + getFunction() { + return this.typefully.getRecentlyScheduledDrafts; }, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + getSummary(draft) { + return `Scheduled draft: ${draft.id}`; }, - contentFilter: { - propDefinition: [ - "typefully", - "contentFilter", - ], - optional: true, - }, - }, - hooks: { - async deploy() { - try { - const drafts = await this.typefully.getRecentlyScheduledDrafts(); - const latestDrafts = drafts.slice(-50).reverse(); - for (const draft of latestDrafts) { - this.$emit( - draft, - { - id: draft.id, - summary: `Scheduled draft: ${draft.content}`, - ts: draft.schedule_date - ? Date.parse(draft.schedule_date) - : Date.now(), - }, - ); - } - } catch (error) { - this.$emit(error, { - summary: "Error during deploy hook", - ts: Date.now(), - }); - } - }, - async activate() { - // No activation steps required for polling source - }, - async deactivate() { - // No deactivation steps required for polling source - }, - }, - async run() { - try { - const drafts = await this.typefully.getRecentlyScheduledDrafts({ - content_filter: this.contentFilter, - }); - for (const draft of drafts) { - this.$emit( - draft, - { - id: draft.id, - summary: `Scheduled draft: ${draft.content}`, - ts: draft.schedule_date - ? Date.parse(draft.schedule_date) - : Date.now(), - }, - ); - } - } catch (error) { - this.$emit(error, { - summary: "Error during run method", - ts: Date.now(), - }); - } }, + sampleEmit, }; diff --git a/components/typefully/sources/new-draft-scheduled/test-event.mjs b/components/typefully/sources/new-draft-scheduled/test-event.mjs new file mode 100644 index 0000000000000..d9274837f569e --- /dev/null +++ b/components/typefully/sources/new-draft-scheduled/test-event.mjs @@ -0,0 +1,15 @@ +export default { + "id": 123, + "status": "scheduled", + "html": "content test", + "num_tweets": 1, + "last_edited": null, + "scheduled_date": "2025-01-09T10:22:11Z", + "published_on": null, + "share_url": "https://typefully.com/t/nb9Lxx13df", + "twitter_url": null, + "linkedin_url": null, + "text_first_tweet": "content", + "html_first_tweet": "content", + "text_preview_linkedin": null +} \ No newline at end of file diff --git a/components/typefully/typefully.app.mjs b/components/typefully/typefully.app.mjs index ce055815b0edf..42f7acff460b9 100644 --- a/components/typefully/typefully.app.mjs +++ b/components/typefully/typefully.app.mjs @@ -3,13 +3,11 @@ import { axios } from "@pipedream/platform"; export default { type: "app", app: "typefully", - version: "0.0.{{ts}}", propDefinitions: { kind: { type: "string", label: "Kind", description: "Filter notifications by kind", - optional: true, async options() { return [ { @@ -27,7 +25,6 @@ export default { type: "string", label: "Content Filter", description: "Filter drafts by content type", - optional: true, async options() { return [ { @@ -44,190 +41,72 @@ export default { content: { type: "string", label: "Content", - description: "The content of the draft", + description: "You can split into multiple tweets by adding 4 consecutive newlines between tweets in the content", }, threadify: { type: "boolean", label: "Threadify", - description: "Automatically split content into a thread", - optional: true, + description: "Content will be automatically split into multiple tweets", }, share: { type: "boolean", label: "Share", - description: "Include a share URL in the draft", - optional: true, + description: "If true, returned payload will include a share_url", }, autoRetweetEnabled: { type: "boolean", label: "Auto Retweet Enabled", - description: "Enable auto-retweet for the draft", - optional: true, + description: "If true, the post will have an AutoRT enabled, according to the one set on Typefully for the account", }, autoPlugEnabled: { type: "boolean", label: "Auto Plug Enabled", - description: "Enable auto-plug for the draft", - optional: true, + description: "If true, the post will have an AutoPlug enabled, according to the one set on Typefully for the account", }, scheduleDate: { type: "string", label: "Schedule Date", - description: "Date to schedule the draft (ISO format) or 'next-free-slot'", - optional: true, + description: "Date to schedule the draft (ISO format - YYYY-MM-DDTHH:MM:SSZ)", }, }, methods: { - authKeys() { - console.log(Object.keys(this.$auth)); - }, _baseUrl() { return "https://api.typefully.com/v1"; }, - async _makeRequest(opts = {}) { - const { - $ = this, method = "GET", path = "/", headers, ...otherOpts - } = opts; - return axios($, { - ...otherOpts, - method, - url: `${this._baseUrl()}${path}`, - headers: { - ...headers, - "Authorization": `Bearer ${this.$auth.access_token}`, - "Content-Type": "application/json", - }, - }); - }, - async createDraft(opts = {}) { - const data = { - content: this.content, - ...(this.threadify !== undefined && { - threadify: this.threadify, - }), - ...(this.share !== undefined && { - share: this.share, - }), - ...(this.autoRetweetEnabled !== undefined && { - auto_retweet_enabled: this.autoRetweetEnabled, - }), - ...(this.autoPlugEnabled !== undefined && { - auto_plug_enabled: this.autoPlugEnabled, - }), - ...(this.scheduleDate !== undefined && { - "schedule-date": this.scheduleDate, - }), + _headers() { + return { + "x-api-key": `${this.$auth.api_key}`, }; - return this._makeRequest({ - method: "POST", - path: "/drafts/", - data, - ...opts, - }); }, - async scheduleDraftNextAvailableSlot(opts = {}) { - const data = { - "content": this.content, - "schedule-date": "next-free-slot", - ...(this.threadify !== undefined && { - threadify: this.threadify, - }), - ...(this.share !== undefined && { - share: this.share, - }), - ...(this.autoRetweetEnabled !== undefined && { - auto_retweet_enabled: this.autoRetweetEnabled, - }), - ...(this.autoPlugEnabled !== undefined && { - auto_plug_enabled: this.autoPlugEnabled, - }), - }; - return this._makeRequest({ - method: "POST", - path: "/drafts/", - data, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), ...opts, }); }, - async scheduleDraftAtSpecificDate(opts = {}) { - if (!this.scheduleDate) { - throw new Error("scheduleDate is required and must be an ISO formatted date."); - } - const data = { - "content": this.content, - "schedule-date": this.scheduleDate, - ...(this.threadify !== undefined && { - threadify: this.threadify, - }), - ...(this.share !== undefined && { - share: this.share, - }), - ...(this.autoRetweetEnabled !== undefined && { - auto_retweet_enabled: this.autoRetweetEnabled, - }), - ...(this.autoPlugEnabled !== undefined && { - auto_plug_enabled: this.autoPlugEnabled, - }), - }; + createDraft(opts = {}) { return this._makeRequest({ method: "POST", path: "/drafts/", - data, ...opts, }); }, - async getRecentlyScheduledDrafts(opts = {}) { + getRecentlyScheduledDrafts(opts = {}) { return this._makeRequest({ method: "GET", path: "/drafts/recently-scheduled/", - params: { - ...(this.contentFilter !== undefined && { - content_filter: this.contentFilter, - }), - ...opts.params, - }, ...opts, }); }, - async getRecentlyPublishedDrafts(opts = {}) { + getRecentlyPublishedDrafts(opts = {}) { return this._makeRequest({ method: "GET", path: "/drafts/recently-published/", ...opts, }); }, - async emitDraftPublishedEvent() { - // Implementation to emit event when a draft is published - // This method should interface with Pipedream's event emitting system - }, - async emitDraftScheduledEvent() { - // Implementation to emit event when a draft is scheduled or queued - // This method should interface with Pipedream's event emitting system - }, - async getNotifications(opts = {}) { - return this._makeRequest({ - method: "GET", - path: "/notifications/", - params: { - ...(this.kind !== undefined && { - kind: this.kind, - }), - ...opts.params, - }, - ...opts, - }); - }, - async paginate(fn, ...opts) { - let results = []; - let response = await fn(...opts); - results.push(...response.items); - while (response.hasMore) { - response = await fn(...opts, { - page: response.nextPage, - }); - results.push(...response.items); - } - return results; - }, }, }; From 79133614a0b658ff5b3fd8196bffe967b9bd6c74 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 8 Jan 2025 13:16:31 -0300 Subject: [PATCH 3/3] pnpm update --- pnpm-lock.yaml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f87619c2ceb73..fa9ec7f5d1689 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11054,7 +11054,11 @@ importers: specifier: ^6.2.13 version: 6.2.13 - components/typefully: {} + components/typefully: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/typless: {} @@ -24694,22 +24698,22 @@ packages: superagent@3.8.1: resolution: {integrity: sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==} engines: {node: '>= 4.0'} - deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net superagent@4.1.0: resolution: {integrity: sha512-FT3QLMasz0YyCd4uIi5HNe+3t/onxMyEho7C3PSqmti3Twgy2rXT4fmkTz6wRL6bTF4uzPcfkUCa8u4JWHw8Ag==} engines: {node: '>= 6.0'} - deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net superagent@5.3.1: resolution: {integrity: sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==} engines: {node: '>= 7.0.0'} - deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please downgrade to v7.1.5 if you need IE/ActiveXObject support OR upgrade to v8.0.0 as we no longer support IE and published an incorrect patch version (see https://github.com/visionmedia/superagent/issues/1731) + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}