From 0e7cdc4238b141e57fb94e0c616fdd6fe8b3ddda Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Tue, 11 Feb 2025 13:46:36 -0500 Subject: [PATCH 1/4] streamlabs init --- .../actions/send-alert/send-alert.mjs | 45 +++++ .../actions/start-stream/start-stream.mjs | 34 ++++ .../actions/update-overlay/update-overlay.mjs | 33 ++++ .../new-follower-instant.mjs | 73 ++++++++ .../new-subscriber-instant.mjs | 113 ++++++++++++ .../new-tip-instant/new-tip-instant.mjs | 117 ++++++++++++ components/streamlabs/streamlabs.app.mjs | 171 +++++++++++++++++- 7 files changed, 583 insertions(+), 3 deletions(-) create mode 100644 components/streamlabs/actions/send-alert/send-alert.mjs create mode 100644 components/streamlabs/actions/start-stream/start-stream.mjs create mode 100644 components/streamlabs/actions/update-overlay/update-overlay.mjs create mode 100644 components/streamlabs/sources/new-follower-instant/new-follower-instant.mjs create mode 100644 components/streamlabs/sources/new-subscriber-instant/new-subscriber-instant.mjs create mode 100644 components/streamlabs/sources/new-tip-instant/new-tip-instant.mjs diff --git a/components/streamlabs/actions/send-alert/send-alert.mjs b/components/streamlabs/actions/send-alert/send-alert.mjs new file mode 100644 index 0000000000000..f6ef4177cb45d --- /dev/null +++ b/components/streamlabs/actions/send-alert/send-alert.mjs @@ -0,0 +1,45 @@ +import streamlabs from "../../streamlabs.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "streamlabs-send-alert", + name: "Send Alert", + description: "Sends an alert to the stream overlay with a custom message, image, and sound. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + streamlabs: { + type: "app", + app: "streamlabs", + }, + alertMessageContent: { + propDefinition: [ + streamlabs, + "alertMessageContent", + ], + }, + alertImageUrl: { + propDefinition: [ + streamlabs, + "alertImageUrl", + ], + optional: true, + }, + alertSoundUrl: { + propDefinition: [ + streamlabs, + "alertSoundUrl", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.streamlabs.sendAlert({ + alertMessageContent: this.alertMessageContent, + alertImageUrl: this.alertImageUrl, + alertSoundUrl: this.alertSoundUrl, + }); + $.export("$summary", `Alert sent with message: ${this.alertMessageContent}`); + return response; + }, +}; diff --git a/components/streamlabs/actions/start-stream/start-stream.mjs b/components/streamlabs/actions/start-stream/start-stream.mjs new file mode 100644 index 0000000000000..7339b670199b1 --- /dev/null +++ b/components/streamlabs/actions/start-stream/start-stream.mjs @@ -0,0 +1,34 @@ +import streamlabs from "../../streamlabs.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "streamlabs-start-stream", + name: "Start Live Stream", + description: "Starts a live stream. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + streamlabs, + streamTitle: { + propDefinition: [ + streamlabs, + "streamTitle", + ], + }, + gameCategory: { + propDefinition: [ + streamlabs, + "gameCategory", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.streamlabs.startLiveStream({ + streamTitle: this.streamTitle, + gameCategory: this.gameCategory, + }); + $.export("$summary", `Started live stream with title "${this.streamTitle}"`); + return response; + }, +}; diff --git a/components/streamlabs/actions/update-overlay/update-overlay.mjs b/components/streamlabs/actions/update-overlay/update-overlay.mjs new file mode 100644 index 0000000000000..e6393cf0b81ea --- /dev/null +++ b/components/streamlabs/actions/update-overlay/update-overlay.mjs @@ -0,0 +1,33 @@ +import streamlabs from "../../streamlabs.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "streamlabs-update-overlay", + name: "Update Stream Overlay", + description: "Updates a specific stream overlay with new values, such as text, images, or stats. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + streamlabs, + overlayId: { + propDefinition: [ + streamlabs, + "overlayId", + ], + }, + contentUpdates: { + propDefinition: [ + streamlabs, + "contentUpdates", + ], + }, + }, + async run({ $ }) { + const response = await this.streamlabs.updateOverlay({ + overlayId: this.overlayId, + contentUpdates: this.contentUpdates, + }); + $.export("$summary", `Updated overlay ${this.overlayId} successfully`); + return response; + }, +}; diff --git a/components/streamlabs/sources/new-follower-instant/new-follower-instant.mjs b/components/streamlabs/sources/new-follower-instant/new-follower-instant.mjs new file mode 100644 index 0000000000000..a6993c4eb0778 --- /dev/null +++ b/components/streamlabs/sources/new-follower-instant/new-follower-instant.mjs @@ -0,0 +1,73 @@ +import streamlabs from "../../streamlabs.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "streamlabs-new-follower-instant", + name: "New Follower", + description: "Emit a new event when a viewer follows the streamer's channel. [See the documentation]($docsLink)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + streamlabs: { + type: "app", + app: "streamlabs", + }, + http: { + type: "$.interface.http", + customResponse: false, + }, + db: "$.service.db", + }, + hooks: { + async deploy() { + const followEvents = await this.streamlabs.emitFollowEvent({ + paginate: true, + max: 50, + }); + for (const follow of followEvents.reverse()) { + this.$emit(follow, { + id: follow.id || follow.ts, + summary: `New follower: ${follow.username}`, + ts: follow.timestamp + ? Date.parse(follow.timestamp) + : Date.now(), + }); + } + }, + async activate() { + const webhook = await this.streamlabs._makeRequest({ + method: "POST", + path: "/webhooks", + data: { + event: "follow_event", + callback_url: this.http.url, + }, + }); + const webhookId = webhook.id; + if (webhookId) { + await this.db.set("webhookId", webhookId); + } + }, + async deactivate() { + const webhookId = await this.db.get("webhookId"); + if (webhookId) { + await this.streamlabs._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); + await this.db.delete("webhookId"); + } + }, + }, + async run(event) { + const followEvent = event.data; + this.$emit(followEvent, { + id: followEvent.id || followEvent.ts, + summary: `New follower: ${followEvent.username}`, + ts: followEvent.timestamp + ? Date.parse(followEvent.timestamp) + : Date.now(), + }); + }, +}; diff --git a/components/streamlabs/sources/new-subscriber-instant/new-subscriber-instant.mjs b/components/streamlabs/sources/new-subscriber-instant/new-subscriber-instant.mjs new file mode 100644 index 0000000000000..9c0f2e7d00582 --- /dev/null +++ b/components/streamlabs/sources/new-subscriber-instant/new-subscriber-instant.mjs @@ -0,0 +1,113 @@ +import streamlabs from "../../streamlabs.app.mjs"; +import crypto from "crypto"; +import { axios } from "@pipedream/platform"; + +export default { + key: "streamlabs-new-subscriber-instant", + name: "New Subscriber (Instant)", + description: "Emit new event when a viewer subscribes to the streamer's channel. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + streamlabs: { + type: "app", + app: "streamlabs", + }, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + subscriptionPlanTier: { + propDefinition: [ + "streamlabs", + "subscriptionPlanTier", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + const params = this.subscriptionPlanTier + ? { + tier: this.subscriptionPlanTier, + } + : {}; + const subscriptions = await this.streamlabs.paginate(this.streamlabs.emitSubscribeEvent, params); + const recentSubscriptions = subscriptions.slice(-50); + for (const subscription of recentSubscriptions) { + this.$emit(subscription, { + id: subscription.id || subscription.ts, + summary: `New subscription from ${subscription.username}`, + ts: new Date(subscription.created_at).getTime(), + }); + } + }, + async activate() { + const callbackUrl = this.http.endpoint; + const data = { + url: callbackUrl, + event: "subscribe", + ...(this.subscriptionPlanTier + ? { + tier: this.subscriptionPlanTier, + } + : {}), + }; + const webhook = await this.streamlabs._makeRequest({ + method: "POST", + path: "/webhooks", + data, + }); + await this.db.set("webhookId", webhook.id); + }, + async deactivate() { + const webhookId = await this.db.get("webhookId"); + if (webhookId) { + await this.streamlabs._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); + await this.db.set("webhookId", null); + } + }, + }, + async run(event) { + const rawBody = event.rawBody; + const signature = event.headers["X-Streamlabs-Signature"]; + + const secret = this.streamlabs.$auth.secret; + const computedSignature = crypto.createHmac("sha256", secret).update(rawBody) + .digest("hex"); + + if (computedSignature !== signature) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + + const subscription = event.body; + + if (this.subscriptionPlanTier && subscription.tier !== this.subscriptionPlanTier) { + this.http.respond({ + status: 200, + body: "Ignored", + }); + return; + } + + this.$emit(subscription, { + id: subscription.id || subscription.ts, + summary: `New subscription from ${subscription.username}`, + ts: Date.parse(subscription.created_at) || Date.now(), + }); + + this.http.respond({ + status: 200, + body: "OK", + }); + }, +}; diff --git a/components/streamlabs/sources/new-tip-instant/new-tip-instant.mjs b/components/streamlabs/sources/new-tip-instant/new-tip-instant.mjs new file mode 100644 index 0000000000000..8b4893eaafc82 --- /dev/null +++ b/components/streamlabs/sources/new-tip-instant/new-tip-instant.mjs @@ -0,0 +1,117 @@ +import streamlabs from "../../streamlabs.app.mjs"; +import { axios } from "@pipedream/platform"; +import crypto from "crypto"; + +export default { + key: "streamlabs-new-tip-instant", + name: "New Tip Received (Instant)", + description: "Emit new event when a viewer sends a tip to the streamer in real-time. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + streamlabs: { + type: "app", + app: "streamlabs", + }, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + minTipAmount: { + propDefinition: [ + streamlabs, + "minTipAmount", + ], + optional: true, + }, + }, + methods: { + async _createWebhook() { + const webhookUrl = this.http.endpoint; + const response = await this.streamlabs._makeRequest({ + method: "POST", + path: "/webhooks", + data: { + url: webhookUrl, + event: "tip", + secret: this.streamlabs.$auth.webhook_secret, + }, + }); + return response.id; + }, + async _deleteWebhook(webhookId) { + await this.streamlabs._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); + }, + _verifySignature(rawBody, signature) { + const computedSignature = crypto + .createHmac("sha256", this.streamlabs.$auth.webhook_secret) + .update(rawBody) + .digest("hex"); + return computedSignature === signature; + }, + }, + hooks: { + async deploy() { + const recentTips = await this.streamlabs.emitTipEvent({ + minTipAmount: this.minTipAmount, + paginate: true, + max: 50, + }); + for (const tip of recentTips) { + this.$emit(tip, { + id: tip.id || tip.timestamp, + summary: `New tip from ${tip.username}: $${tip.amount}`, + ts: Date.parse(tip.timestamp) || Date.now(), + }); + } + }, + async activate() { + const webhookId = await this._createWebhook(); + await this.db.set("webhookId", webhookId); + }, + async deactivate() { + const webhookId = await this.db.get("webhookId"); + if (webhookId) { + await this._deleteWebhook(webhookId); + await this.db.delete("webhookId"); + } + }, + }, + async run(event) { + const signature = event.headers["x-streamlabs-signature"]; + const rawBody = event.rawBody; + if (!this._verifySignature(rawBody, signature)) { + await this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + + const tip = event.body; + + if (this.minTipAmount && tip.amount < this.minTipAmount) { + await this.http.respond({ + status: 200, + body: "Tip below minimum amount", + }); + return; + } + + this.$emit(tip, { + id: tip.id || tip.timestamp, + summary: `New tip from ${tip.username}: $${tip.amount}`, + ts: Date.parse(tip.timestamp) || Date.now(), + }); + + await this.http.respond({ + status: 200, + body: "OK", + }); + }, +}; diff --git a/components/streamlabs/streamlabs.app.mjs b/components/streamlabs/streamlabs.app.mjs index 11983339a57f6..aab80cdf38b58 100644 --- a/components/streamlabs/streamlabs.app.mjs +++ b/components/streamlabs/streamlabs.app.mjs @@ -1,10 +1,175 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "streamlabs", - propDefinitions: {}, + version: "0.0.{{ts}}", + propDefinitions: { + minTipAmount: { + type: "integer", + label: "Minimum Tip Amount", + description: "Filter tips by a minimum amount", + optional: true, + }, + subscriptionPlanTier: { + type: "integer", + label: "Subscription Plan Tier", + description: "Filter subscriptions by plan tier", + optional: true, + }, + alertMessageContent: { + type: "string", + label: "Alert Message Content", + description: "The message content for the alert", + }, + alertImageUrl: { + type: "string", + label: "Alert Image URL", + description: "URL of the image to include in the alert", + optional: true, + }, + alertSoundUrl: { + type: "string", + label: "Alert Sound URL", + description: "URL of the sound to play in the alert", + optional: true, + }, + overlayId: { + type: "string", + label: "Overlay ID", + description: "The ID of the overlay to update", + }, + contentUpdates: { + type: "object", + label: "Content Updates", + description: "The content updates for the overlay (e.g., text, images, stats)", + }, + streamTitle: { + type: "string", + label: "Stream Title", + description: "The title of the live stream", + }, + gameCategory: { + type: "string", + label: "Game/Category", + description: "The game or category information for the live stream", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { + _baseUrl() { + return "https://streamlabs.com/api/v2.0"; + }, + async _makeRequest(opts = {}) { + const { + $, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.access_token}`, + }, + }); + }, + async emitTipEvent(opts = {}) { + const { minTipAmount } = opts; + const params = minTipAmount + ? { + min_amount: minTipAmount, + } + : {}; + return this._makeRequest({ + method: "GET", + path: "/tip_event", + params, + ...opts, + }); + }, + async emitFollowEvent(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/follow_event", + ...opts, + }); + }, + async emitSubscribeEvent(opts = {}) { + const { subscriptionPlanTier } = opts; + const params = subscriptionPlanTier + ? { + tier: subscriptionPlanTier, + } + : {}; + return this._makeRequest({ + method: "GET", + path: "/subscribe_event", + params, + ...opts, + }); + }, + async sendAlert(opts = {}) { + const { + alertMessageContent, alertImageUrl, alertSoundUrl, + } = opts; + const data = { + message: alertMessageContent, + }; + if (alertImageUrl) data.image_url = alertImageUrl; + if (alertSoundUrl) data.sound_url = alertSoundUrl; + return this._makeRequest({ + method: "POST", + path: "/alerts", + data, + ...opts, + }); + }, + async updateOverlay(opts = {}) { + const { + overlayId, contentUpdates, + } = opts; + return this._makeRequest({ + method: "PUT", + path: `/overlays/${overlayId}`, + data: contentUpdates, + ...opts, + }); + }, + async startLiveStream(opts = {}) { + const { + streamTitle, gameCategory, + } = opts; + const data = { + title: streamTitle, + }; + if (gameCategory) data.game_category = gameCategory; + return this._makeRequest({ + method: "POST", + path: "/streams/start", + data, + ...opts, + }); + }, + paginate(fn, ...opts) { + let results = []; + const fetchPage = async (params = {}) => { + const response = await fn({ + ...params, + ...opts, + }); + if (response && response.length > 0) { + results = results.concat(response); + if (response.next_page) { + await fetchPage({ + page: response.next_page, + }); + } + } + }; + return fetchPage().then(() => results); + }, + async authKeys() { console.log(Object.keys(this.$auth)); }, }, From c8a8c53be14a853a1f56e8083c24cc7ba4495045 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Tue, 11 Feb 2025 17:18:34 -0500 Subject: [PATCH 2/4] new components --- .../create-donation/create-donation.mjs | 74 ++++++++ .../actions/send-alert/send-alert.mjs | 79 +++++--- .../send-test-alert/send-test-alert.mjs | 64 +++++++ .../actions/start-stream/start-stream.mjs | 34 ---- .../actions/update-overlay/update-overlay.mjs | 33 ---- components/streamlabs/common/currencies.mjs | 90 +++++++++ components/streamlabs/package.json | 18 ++ .../new-follower-instant.mjs | 73 -------- .../new-subscriber-instant.mjs | 113 ------------ .../new-tip-instant/new-tip-instant.mjs | 117 ------------ components/streamlabs/streamlabs.app.mjs | 174 +++--------------- 11 files changed, 327 insertions(+), 542 deletions(-) create mode 100644 components/streamlabs/actions/create-donation/create-donation.mjs create mode 100644 components/streamlabs/actions/send-test-alert/send-test-alert.mjs delete mode 100644 components/streamlabs/actions/start-stream/start-stream.mjs delete mode 100644 components/streamlabs/actions/update-overlay/update-overlay.mjs create mode 100644 components/streamlabs/common/currencies.mjs create mode 100644 components/streamlabs/package.json delete mode 100644 components/streamlabs/sources/new-follower-instant/new-follower-instant.mjs delete mode 100644 components/streamlabs/sources/new-subscriber-instant/new-subscriber-instant.mjs delete mode 100644 components/streamlabs/sources/new-tip-instant/new-tip-instant.mjs diff --git a/components/streamlabs/actions/create-donation/create-donation.mjs b/components/streamlabs/actions/create-donation/create-donation.mjs new file mode 100644 index 0000000000000..f4e6226937400 --- /dev/null +++ b/components/streamlabs/actions/create-donation/create-donation.mjs @@ -0,0 +1,74 @@ +import streamlabs from "../../streamlabs.app.mjs"; +import currencies from "../../common/currencies.mjs"; + +export default { + key: "streamlabs-create-donation", + name: "Create Donation", + description: "Create a donation for the authenticated user. [See the documentation](https://dev.streamlabs.com/reference/donations-1)", + version: "0.0.1", + type: "action", + props: { + streamlabs, + name: { + type: "string", + label: "Name", + description: "The name of the donor. Has to be between 2-25 length and should only contain utf8 characters", + }, + identifier: { + type: "string", + label: "Identifier", + description: "An identifier for this donor, which is used to group donations with the same donor. For example, if you create more than one donation with the same identifier, they will be grouped together as if they came from the same donor. Typically this is best suited as an email address, or a unique hash.", + }, + amount: { + type: "string", + label: "Amount", + description: "The amount of this donation", + }, + currency: { + type: "string", + label: "Currency", + description: "The 3 letter currency code for this donation. Must be one of the [supported currency codes](https://dev.streamlabs.com/docs/currency-codes)", + options: currencies, + }, + message: { + type: "string", + label: "Message", + description: "The message from the donor. Must be < 255 characters", + optional: true, + }, + createdAt: { + type: "string", + label: "Created At", + description: "A timestamp that identifies when this donation was made. If left blank, it will default to now. Enter in ISO-8601 format (e.g., `2018-02-18T02:30:00-07:00` or `2018-02-18T08:00:00Z`, where Z stands for UTC)", + optional: true, + }, + skipAlert: { + type: "string", + label: "Skip Alert", + description: "Set to `yes` if you need to skip the alert. Default is `no`", + options: [ + "yes", + "no", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.streamlabs.createDonation({ + $, + data: { + name: this.name, + identifier: this.identifier, + amount: parseFloat(this.amount), + currency: this.currency, + message: this.message, + createdAt: this.createdAt && Date.parse(this.createdAt), + skip_alert: this.skipAlert, + }, + }); + if (response?.donation_id) { + $.export("$summary", `Successfully created donation with ID: ${response.donation_id}`); + } + return response; + }, +}; diff --git a/components/streamlabs/actions/send-alert/send-alert.mjs b/components/streamlabs/actions/send-alert/send-alert.mjs index f6ef4177cb45d..90464f67a3405 100644 --- a/components/streamlabs/actions/send-alert/send-alert.mjs +++ b/components/streamlabs/actions/send-alert/send-alert.mjs @@ -1,45 +1,74 @@ import streamlabs from "../../streamlabs.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "streamlabs-send-alert", name: "Send Alert", - description: "Sends an alert to the stream overlay with a custom message, image, and sound. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Sends an alert to the stream overlay with a custom message, image, and sound. [See the documentation](https://dev.streamlabs.com/reference/alerts)", + version: "0.0.1", type: "action", props: { - streamlabs: { - type: "app", - app: "streamlabs", - }, - alertMessageContent: { - propDefinition: [ - streamlabs, - "alertMessageContent", + streamlabs, + type: { + type: "string", + label: "Type", + description: "determines which alert box this alert will show up in", + options: [ + "follow", + "subscription", + "donation", + "host", ], }, - alertImageUrl: { - propDefinition: [ - streamlabs, - "alertImageUrl", - ], + message: { + type: "string", + label: "Message", + description: "The message to show with this alert", + }, + imageHref: { + type: "string", + label: "Image HREF", + description: "The href pointing to an image resource to play when this alert shows", optional: true, }, - alertSoundUrl: { - propDefinition: [ - streamlabs, - "alertSoundUrl", - ], + soundHref: { + type: "string", + label: "Sound HREF", + description: "The href pointing to a sound resource to play when this alert shows", + optional: true, + }, + userMessage: { + type: "string", + label: "User Message", + description: "Acting as the second heading, this shows below message", + optional: true, + }, + duration: { + type: "string", + label: "Duration", + description: "How many seconds this alert should be displayed. Value should be in milliseconds. Ex: `1000` for 1 second.", + optional: true, + }, + specialTextColor: { + type: "string", + label: "Special Text Color", + description: "The color to use for special tokens. Must be a valid CSS color string", optional: true, }, }, async run({ $ }) { const response = await this.streamlabs.sendAlert({ - alertMessageContent: this.alertMessageContent, - alertImageUrl: this.alertImageUrl, - alertSoundUrl: this.alertSoundUrl, + $, + data: { + type: this.type, + message: this.message, + image_href: this.imageHref, + sound_href: this.soundHref, + user_message: this.userMessage, + duration: this.duration, + special_text_color: this.specialTextColor, + }, }); - $.export("$summary", `Alert sent with message: ${this.alertMessageContent}`); + $.export("$summary", `Alert sent with message: ${this.message}`); return response; }, }; diff --git a/components/streamlabs/actions/send-test-alert/send-test-alert.mjs b/components/streamlabs/actions/send-test-alert/send-test-alert.mjs new file mode 100644 index 0000000000000..3ededd67bcfb5 --- /dev/null +++ b/components/streamlabs/actions/send-test-alert/send-test-alert.mjs @@ -0,0 +1,64 @@ +import streamlabs from "../../streamlabs.app.mjs"; + +export default { + key: "streamlabs-send-test-alert", + name: "Send Test Alert", + description: "Send a test alert to the stream overlay in StreamLabs. [See the documentation](https://dev.streamlabs.com/reference/alertssend_test_alert)", + version: "0.0.1", + type: "action", + props: { + streamlabs, + platform: { + type: "string", + label: "Platform", + description: "The streaming platform", + options: [ + "twitch", + "youtube", + ], + reloadProps: true, + }, + }, + additionalProps() { + if (!this.platform) { + return {}; + } + const props = { + type: { + type: "string", + label: "Type", + description: "The type of the alert", + }, + }; + if (this.platform === "twitch") { + props.type.options = [ + "follow", + "subscription", + "donation", + "host", + "bits", + "raid", + ]; + } + if (this.platform === "youtube") { + props.type.options = [ + "subscription", + "sponsor", + "superchat", + "donation", + ]; + } + return props; + }, + async run({ $ }) { + const response = await this.streamlabs.sendTestAlert({ + $, + data: { + platform: this.platform, + type: this.type, + }, + }); + $.export("$summary", "Successfully sent test alert"); + return response; + }, +}; diff --git a/components/streamlabs/actions/start-stream/start-stream.mjs b/components/streamlabs/actions/start-stream/start-stream.mjs deleted file mode 100644 index 7339b670199b1..0000000000000 --- a/components/streamlabs/actions/start-stream/start-stream.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import streamlabs from "../../streamlabs.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "streamlabs-start-stream", - name: "Start Live Stream", - description: "Starts a live stream. [See the documentation]()", - version: "0.0.{{ts}}", - type: "action", - props: { - streamlabs, - streamTitle: { - propDefinition: [ - streamlabs, - "streamTitle", - ], - }, - gameCategory: { - propDefinition: [ - streamlabs, - "gameCategory", - ], - optional: true, - }, - }, - async run({ $ }) { - const response = await this.streamlabs.startLiveStream({ - streamTitle: this.streamTitle, - gameCategory: this.gameCategory, - }); - $.export("$summary", `Started live stream with title "${this.streamTitle}"`); - return response; - }, -}; diff --git a/components/streamlabs/actions/update-overlay/update-overlay.mjs b/components/streamlabs/actions/update-overlay/update-overlay.mjs deleted file mode 100644 index e6393cf0b81ea..0000000000000 --- a/components/streamlabs/actions/update-overlay/update-overlay.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import streamlabs from "../../streamlabs.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "streamlabs-update-overlay", - name: "Update Stream Overlay", - description: "Updates a specific stream overlay with new values, such as text, images, or stats. [See the documentation]()", - version: "0.0.{{ts}}", - type: "action", - props: { - streamlabs, - overlayId: { - propDefinition: [ - streamlabs, - "overlayId", - ], - }, - contentUpdates: { - propDefinition: [ - streamlabs, - "contentUpdates", - ], - }, - }, - async run({ $ }) { - const response = await this.streamlabs.updateOverlay({ - overlayId: this.overlayId, - contentUpdates: this.contentUpdates, - }); - $.export("$summary", `Updated overlay ${this.overlayId} successfully`); - return response; - }, -}; diff --git a/components/streamlabs/common/currencies.mjs b/components/streamlabs/common/currencies.mjs new file mode 100644 index 0000000000000..8a86e5edfa79c --- /dev/null +++ b/components/streamlabs/common/currencies.mjs @@ -0,0 +1,90 @@ +export default [ + { + value: "USD", + label: "US Dollar", + }, + { + value: "AUD", + label: "Australian Dollar", + }, + { + value: "BRL", + label: "Brazilian Real", + }, + { + value: "CAD", + label: "Canadian Dollar", + }, + { + value: "CZK", + label: "Czech Koruna", + }, + { + value: "DKK", + label: "Danish Krone", + }, + { + value: "EUR", + label: "Euro", + }, + { + value: "HKD", + label: "Hong Kong Dollar", + }, + { + value: "ILS", + label: "Israeli New Sheqel", + }, + { + value: "MYR", + label: "Malaysian Ringgit", + }, + { + value: "MXN", + label: "Mexican Peso", + }, + { + value: "NOK", + label: "Norwegian Krone", + }, + { + value: "NZD", + label: "New Zealand Dollar", + }, + { + value: "PHP", + label: "Philippine Peso", + }, + { + value: "PLN", + label: "Polish Zloty", + }, + { + value: "GBP", + label: "Pound Sterling", + }, + { + value: "RUB", + label: "Russian Ruble", + }, + { + value: "SGD", + label: "Singapore Dollar", + }, + { + value: "SEK", + label: "Swedish Krona", + }, + { + value: "CHF", + label: "Swiss Franc", + }, + { + value: "THB", + label: "Thai Baht", + }, + { + value: "TRY", + label: "Turkish Lira", + }, +]; diff --git a/components/streamlabs/package.json b/components/streamlabs/package.json new file mode 100644 index 0000000000000..b6f33111e3558 --- /dev/null +++ b/components/streamlabs/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/streamlabs", + "version": "0.0.1", + "description": "Pipedream Streamlabs Components", + "main": "streamlabs.app.mjs", + "keywords": [ + "pipedream", + "streamlabs" + ], + "homepage": "https://pipedream.com/apps/streamlabs", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/streamlabs/sources/new-follower-instant/new-follower-instant.mjs b/components/streamlabs/sources/new-follower-instant/new-follower-instant.mjs deleted file mode 100644 index a6993c4eb0778..0000000000000 --- a/components/streamlabs/sources/new-follower-instant/new-follower-instant.mjs +++ /dev/null @@ -1,73 +0,0 @@ -import streamlabs from "../../streamlabs.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "streamlabs-new-follower-instant", - name: "New Follower", - description: "Emit a new event when a viewer follows the streamer's channel. [See the documentation]($docsLink)", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - streamlabs: { - type: "app", - app: "streamlabs", - }, - http: { - type: "$.interface.http", - customResponse: false, - }, - db: "$.service.db", - }, - hooks: { - async deploy() { - const followEvents = await this.streamlabs.emitFollowEvent({ - paginate: true, - max: 50, - }); - for (const follow of followEvents.reverse()) { - this.$emit(follow, { - id: follow.id || follow.ts, - summary: `New follower: ${follow.username}`, - ts: follow.timestamp - ? Date.parse(follow.timestamp) - : Date.now(), - }); - } - }, - async activate() { - const webhook = await this.streamlabs._makeRequest({ - method: "POST", - path: "/webhooks", - data: { - event: "follow_event", - callback_url: this.http.url, - }, - }); - const webhookId = webhook.id; - if (webhookId) { - await this.db.set("webhookId", webhookId); - } - }, - async deactivate() { - const webhookId = await this.db.get("webhookId"); - if (webhookId) { - await this.streamlabs._makeRequest({ - method: "DELETE", - path: `/webhooks/${webhookId}`, - }); - await this.db.delete("webhookId"); - } - }, - }, - async run(event) { - const followEvent = event.data; - this.$emit(followEvent, { - id: followEvent.id || followEvent.ts, - summary: `New follower: ${followEvent.username}`, - ts: followEvent.timestamp - ? Date.parse(followEvent.timestamp) - : Date.now(), - }); - }, -}; diff --git a/components/streamlabs/sources/new-subscriber-instant/new-subscriber-instant.mjs b/components/streamlabs/sources/new-subscriber-instant/new-subscriber-instant.mjs deleted file mode 100644 index 9c0f2e7d00582..0000000000000 --- a/components/streamlabs/sources/new-subscriber-instant/new-subscriber-instant.mjs +++ /dev/null @@ -1,113 +0,0 @@ -import streamlabs from "../../streamlabs.app.mjs"; -import crypto from "crypto"; -import { axios } from "@pipedream/platform"; - -export default { - key: "streamlabs-new-subscriber-instant", - name: "New Subscriber (Instant)", - description: "Emit new event when a viewer subscribes to the streamer's channel. [See the documentation]()", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - streamlabs: { - type: "app", - app: "streamlabs", - }, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - subscriptionPlanTier: { - propDefinition: [ - "streamlabs", - "subscriptionPlanTier", - ], - optional: true, - }, - }, - hooks: { - async deploy() { - const params = this.subscriptionPlanTier - ? { - tier: this.subscriptionPlanTier, - } - : {}; - const subscriptions = await this.streamlabs.paginate(this.streamlabs.emitSubscribeEvent, params); - const recentSubscriptions = subscriptions.slice(-50); - for (const subscription of recentSubscriptions) { - this.$emit(subscription, { - id: subscription.id || subscription.ts, - summary: `New subscription from ${subscription.username}`, - ts: new Date(subscription.created_at).getTime(), - }); - } - }, - async activate() { - const callbackUrl = this.http.endpoint; - const data = { - url: callbackUrl, - event: "subscribe", - ...(this.subscriptionPlanTier - ? { - tier: this.subscriptionPlanTier, - } - : {}), - }; - const webhook = await this.streamlabs._makeRequest({ - method: "POST", - path: "/webhooks", - data, - }); - await this.db.set("webhookId", webhook.id); - }, - async deactivate() { - const webhookId = await this.db.get("webhookId"); - if (webhookId) { - await this.streamlabs._makeRequest({ - method: "DELETE", - path: `/webhooks/${webhookId}`, - }); - await this.db.set("webhookId", null); - } - }, - }, - async run(event) { - const rawBody = event.rawBody; - const signature = event.headers["X-Streamlabs-Signature"]; - - const secret = this.streamlabs.$auth.secret; - const computedSignature = crypto.createHmac("sha256", secret).update(rawBody) - .digest("hex"); - - if (computedSignature !== signature) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - - const subscription = event.body; - - if (this.subscriptionPlanTier && subscription.tier !== this.subscriptionPlanTier) { - this.http.respond({ - status: 200, - body: "Ignored", - }); - return; - } - - this.$emit(subscription, { - id: subscription.id || subscription.ts, - summary: `New subscription from ${subscription.username}`, - ts: Date.parse(subscription.created_at) || Date.now(), - }); - - this.http.respond({ - status: 200, - body: "OK", - }); - }, -}; diff --git a/components/streamlabs/sources/new-tip-instant/new-tip-instant.mjs b/components/streamlabs/sources/new-tip-instant/new-tip-instant.mjs deleted file mode 100644 index 8b4893eaafc82..0000000000000 --- a/components/streamlabs/sources/new-tip-instant/new-tip-instant.mjs +++ /dev/null @@ -1,117 +0,0 @@ -import streamlabs from "../../streamlabs.app.mjs"; -import { axios } from "@pipedream/platform"; -import crypto from "crypto"; - -export default { - key: "streamlabs-new-tip-instant", - name: "New Tip Received (Instant)", - description: "Emit new event when a viewer sends a tip to the streamer in real-time. [See the documentation]()", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - streamlabs: { - type: "app", - app: "streamlabs", - }, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - minTipAmount: { - propDefinition: [ - streamlabs, - "minTipAmount", - ], - optional: true, - }, - }, - methods: { - async _createWebhook() { - const webhookUrl = this.http.endpoint; - const response = await this.streamlabs._makeRequest({ - method: "POST", - path: "/webhooks", - data: { - url: webhookUrl, - event: "tip", - secret: this.streamlabs.$auth.webhook_secret, - }, - }); - return response.id; - }, - async _deleteWebhook(webhookId) { - await this.streamlabs._makeRequest({ - method: "DELETE", - path: `/webhooks/${webhookId}`, - }); - }, - _verifySignature(rawBody, signature) { - const computedSignature = crypto - .createHmac("sha256", this.streamlabs.$auth.webhook_secret) - .update(rawBody) - .digest("hex"); - return computedSignature === signature; - }, - }, - hooks: { - async deploy() { - const recentTips = await this.streamlabs.emitTipEvent({ - minTipAmount: this.minTipAmount, - paginate: true, - max: 50, - }); - for (const tip of recentTips) { - this.$emit(tip, { - id: tip.id || tip.timestamp, - summary: `New tip from ${tip.username}: $${tip.amount}`, - ts: Date.parse(tip.timestamp) || Date.now(), - }); - } - }, - async activate() { - const webhookId = await this._createWebhook(); - await this.db.set("webhookId", webhookId); - }, - async deactivate() { - const webhookId = await this.db.get("webhookId"); - if (webhookId) { - await this._deleteWebhook(webhookId); - await this.db.delete("webhookId"); - } - }, - }, - async run(event) { - const signature = event.headers["x-streamlabs-signature"]; - const rawBody = event.rawBody; - if (!this._verifySignature(rawBody, signature)) { - await this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - - const tip = event.body; - - if (this.minTipAmount && tip.amount < this.minTipAmount) { - await this.http.respond({ - status: 200, - body: "Tip below minimum amount", - }); - return; - } - - this.$emit(tip, { - id: tip.id || tip.timestamp, - summary: `New tip from ${tip.username}: $${tip.amount}`, - ts: Date.parse(tip.timestamp) || Date.now(), - }); - - await this.http.respond({ - status: 200, - body: "OK", - }); - }, -}; diff --git a/components/streamlabs/streamlabs.app.mjs b/components/streamlabs/streamlabs.app.mjs index aab80cdf38b58..c44c4097f0270 100644 --- a/components/streamlabs/streamlabs.app.mjs +++ b/components/streamlabs/streamlabs.app.mjs @@ -3,174 +3,54 @@ import { axios } from "@pipedream/platform"; export default { type: "app", app: "streamlabs", - version: "0.0.{{ts}}", - propDefinitions: { - minTipAmount: { - type: "integer", - label: "Minimum Tip Amount", - description: "Filter tips by a minimum amount", - optional: true, - }, - subscriptionPlanTier: { - type: "integer", - label: "Subscription Plan Tier", - description: "Filter subscriptions by plan tier", - optional: true, - }, - alertMessageContent: { - type: "string", - label: "Alert Message Content", - description: "The message content for the alert", - }, - alertImageUrl: { - type: "string", - label: "Alert Image URL", - description: "URL of the image to include in the alert", - optional: true, - }, - alertSoundUrl: { - type: "string", - label: "Alert Sound URL", - description: "URL of the sound to play in the alert", - optional: true, - }, - overlayId: { - type: "string", - label: "Overlay ID", - description: "The ID of the overlay to update", - }, - contentUpdates: { - type: "object", - label: "Content Updates", - description: "The content updates for the overlay (e.g., text, images, stats)", - }, - streamTitle: { - type: "string", - label: "Stream Title", - description: "The title of the live stream", - }, - gameCategory: { - type: "string", - label: "Game/Category", - description: "The game or category information for the live stream", - optional: true, - }, - }, + propDefinitions: {}, methods: { _baseUrl() { - return "https://streamlabs.com/api/v2.0"; - }, - async _makeRequest(opts = {}) { - const { - $, method = "GET", path = "/", headers, ...otherOpts - } = opts; + return "https://streamlabs.com/api/v1.0"; + }, + _accessToken() { + return this.$auth.oauth_access_token; + }, + _makeRequest({ + $ = this, + path, + params, + data, + ...otherOpts + }) { return axios($, { ...otherOpts, - method, - url: this._baseUrl() + path, - headers: { - ...headers, - Authorization: `Bearer ${this.$auth.access_token}`, + url: `${this._baseUrl()}${path}`, + params: { + access_token: this._accessToken(), + ...params, + }, + data: { + access_token: this._accessToken(), + ...data, }, }); }, - async emitTipEvent(opts = {}) { - const { minTipAmount } = opts; - const params = minTipAmount - ? { - min_amount: minTipAmount, - } - : {}; - return this._makeRequest({ - method: "GET", - path: "/tip_event", - params, - ...opts, - }); - }, - async emitFollowEvent(opts = {}) { - return this._makeRequest({ - method: "GET", - path: "/follow_event", - ...opts, - }); - }, - async emitSubscribeEvent(opts = {}) { - const { subscriptionPlanTier } = opts; - const params = subscriptionPlanTier - ? { - tier: subscriptionPlanTier, - } - : {}; - return this._makeRequest({ - method: "GET", - path: "/subscribe_event", - params, - ...opts, - }); - }, - async sendAlert(opts = {}) { - const { - alertMessageContent, alertImageUrl, alertSoundUrl, - } = opts; - const data = { - message: alertMessageContent, - }; - if (alertImageUrl) data.image_url = alertImageUrl; - if (alertSoundUrl) data.sound_url = alertSoundUrl; + sendAlert(opts = {}) { return this._makeRequest({ method: "POST", path: "/alerts", - data, ...opts, }); }, - async updateOverlay(opts = {}) { - const { - overlayId, contentUpdates, - } = opts; + createDonation(opts = {}) { return this._makeRequest({ - method: "PUT", - path: `/overlays/${overlayId}`, - data: contentUpdates, + method: "POST", + path: "/donations", ...opts, }); }, - async startLiveStream(opts = {}) { - const { - streamTitle, gameCategory, - } = opts; - const data = { - title: streamTitle, - }; - if (gameCategory) data.game_category = gameCategory; + sendTestAlert(opts = {}) { return this._makeRequest({ method: "POST", - path: "/streams/start", - data, + path: "/alerts/send_test_alert", ...opts, }); }, - paginate(fn, ...opts) { - let results = []; - const fetchPage = async (params = {}) => { - const response = await fn({ - ...params, - ...opts, - }); - if (response && response.length > 0) { - results = results.concat(response); - if (response.next_page) { - await fetchPage({ - page: response.next_page, - }); - } - } - }; - return fetchPage().then(() => results); - }, - async authKeys() { - console.log(Object.keys(this.$auth)); - }, }, }; From 172bf9f52452386a5e075d5290b36ac540129ebc Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Tue, 11 Feb 2025 17:25:09 -0500 Subject: [PATCH 3/4] pnpm-lock.yaml --- pnpm-lock.yaml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11b0cd8871854..a944b5ef26431 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2454,8 +2454,7 @@ importers: specifier: ^0.1.6 version: 0.1.6 - components/current_rms: - specifiers: {} + components/current_rms: {} components/customer_fields: dependencies: @@ -7284,8 +7283,7 @@ importers: components/odoo: {} - components/office_365_management: - specifiers: {} + components/office_365_management: {} components/offlight: dependencies: @@ -10421,6 +10419,12 @@ importers: specifier: ^1.1.1 version: 1.6.6 + components/streamlabs: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 + components/streamtime: dependencies: '@pipedream/platform': @@ -12645,8 +12649,7 @@ importers: specifier: ^1.0.5 version: 1.0.5 - components/zoho_tables: - specifiers: {} + components/zoho_tables: {} components/zoho_workdrive: dependencies: @@ -32274,6 +32277,8 @@ 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: From c45d29a08e337eec64575133ee59879a94a887d0 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Tue, 11 Feb 2025 17:35:57 -0500 Subject: [PATCH 4/4] remove unnecessary makeRequest params --- components/streamlabs/streamlabs.app.mjs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/streamlabs/streamlabs.app.mjs b/components/streamlabs/streamlabs.app.mjs index c44c4097f0270..7b2f37cdf4ba0 100644 --- a/components/streamlabs/streamlabs.app.mjs +++ b/components/streamlabs/streamlabs.app.mjs @@ -14,17 +14,12 @@ export default { _makeRequest({ $ = this, path, - params, data, ...otherOpts }) { return axios($, { ...otherOpts, url: `${this._baseUrl()}${path}`, - params: { - access_token: this._accessToken(), - ...params, - }, data: { access_token: this._accessToken(), ...data,