diff --git a/components/belco/actions/add-note-to-conversation/add-note-to-conversation.mjs b/components/belco/actions/add-note-to-conversation/add-note-to-conversation.mjs new file mode 100644 index 0000000000000..a92c8150b9a11 --- /dev/null +++ b/components/belco/actions/add-note-to-conversation/add-note-to-conversation.mjs @@ -0,0 +1,35 @@ +import belco from "../../belco.app.mjs"; + +export default { + key: "belco-add-note-to-conversation", + name: "Add Note to Conversation", + description: "Add a note to a conversation specified by ID. [See the documentation](https://developers.belco.io/reference/put_conversations-conversationid-addnote)", + version: "0.0.1", + type: "action", + props: { + belco, + conversationId: { + propDefinition: [ + belco, + "conversationId", + ], + }, + body: { + type: "string", + label: "Body", + description: "The note body", + }, + }, + async run({ $ }) { + const response = await this.belco.addNoteToConversation({ + $, + conversationId: this.conversationId, + data: { + body: this.body, + }, + }); + + $.export("$summary", `Added note to conversation successfully: ${this.conversationId}`); + return response; + }, +}; diff --git a/components/belco/actions/close-conversation/close-conversation.mjs b/components/belco/actions/close-conversation/close-conversation.mjs new file mode 100644 index 0000000000000..b979ff2f12139 --- /dev/null +++ b/components/belco/actions/close-conversation/close-conversation.mjs @@ -0,0 +1,32 @@ +import belco from "../../belco.app.mjs"; + +export default { + key: "belco-close-conversation", + name: "Close Conversation", + description: "Close a conversation specified by ID. [See the documentation](https://developers.belco.io/reference/put_conversations-conversationid-close)", + version: "0.0.1", + type: "action", + props: { + belco, + conversationId: { + propDefinition: [ + belco, + "conversationId", + () => ({ + excludeStatus: [ + "closed", + ], + }), + ], + }, + }, + async run({ $ }) { + const response = await this.belco.closeConversation({ + $, + conversationId: this.conversationId, + }); + + $.export("$summary", `Closed conversation successfully: ${this.conversationId}`); + return response; + }, +}; diff --git a/components/belco/actions/create-conversation/create-conversation.mjs b/components/belco/actions/create-conversation/create-conversation.mjs new file mode 100644 index 0000000000000..da2ab60e1da8a --- /dev/null +++ b/components/belco/actions/create-conversation/create-conversation.mjs @@ -0,0 +1,95 @@ +import belco from "../../belco.app.mjs"; + +export default { + key: "belco-create-conversation", + name: "Create Conversation", + description: "Create a conversation from Belco. [See the documentation](https://developers.belco.io/reference/post_conversations)", + version: "0.0.1", + type: "action", + props: { + belco, + shopId: { + propDefinition: [ + belco, + "shopId", + ], + }, + channel: { + propDefinition: [ + belco, + "channel", + ], + }, + type: { + propDefinition: [ + belco, + "type", + ], + }, + fromType: { + propDefinition: [ + belco, + "fromType", + ], + }, + from: { + propDefinition: [ + belco, + "from", + ({ + fromType, shopId, + }) => ({ + fromType, + shopId, + }), + ], + }, + to: { + propDefinition: [ + belco, + "to", + ({ + toType, shopId, + }) => ({ + toType, + shopId, + }), + ], + }, + subject: { + propDefinition: [ + belco, + "subject", + ], + }, + body: { + propDefinition: [ + belco, + "body", + ], + }, + }, + async run({ $ }) { + const response = await this.belco.createConversation({ + $, + data: { + shopId: this.shopId, + channel: this.channel, + type: this.type, + from: { + type: this.fromType, + _id: this.from, + }, + to: { + type: "contact", + _id: this.to, + }, + subject: this.subject, + body: this.body, + }, + }); + + $.export("$summary", `New conversation created successfully with ID: ${response._id}`); + return response; + }, +}; diff --git a/components/belco/actions/list-all-conversations/list-all-conversations.mjs b/components/belco/actions/list-all-conversations/list-all-conversations.mjs new file mode 100644 index 0000000000000..4dd4811307c70 --- /dev/null +++ b/components/belco/actions/list-all-conversations/list-all-conversations.mjs @@ -0,0 +1,42 @@ +import belco from "../../belco.app.mjs"; + +export default { + key: "belco-list-all-conversations", + name: "List All Conversations", + description: "Get a list of conversations from Belco. [See the documentation](https://developers.belco.io/reference/get_conversations)", + version: "0.0.1", + type: "action", + props: { + belco, + limit: { + type: "integer", + label: "Limit", + description: "Maximum number of conversations to retrieve", + default: 50, + min: 1, + max: 100, + }, + skip: { + type: "integer", + label: "Skip", + description: "Number of conversations to skip", + default: 0, + min: 0, + }, + }, + async run({ $ }) { + const params = { + limit: this.limit, + skip: this.skip, + }; + + const response = await this.belco.listConversations({ + $, + params, + }); + + $.export("$summary", `Retrieved ${response.conversations.length} conversations`); + + return response; + }, +}; diff --git a/components/belco/actions/reopen-conversation/reopen-conversation.mjs b/components/belco/actions/reopen-conversation/reopen-conversation.mjs new file mode 100644 index 0000000000000..71aead3e3abe0 --- /dev/null +++ b/components/belco/actions/reopen-conversation/reopen-conversation.mjs @@ -0,0 +1,32 @@ +import belco from "../../belco.app.mjs"; + +export default { + key: "belco-reopen-conversation", + name: "Reopen Conversation", + description: "Reopen a conversation specified by ID. [See the documentation](https://developers.belco.io/reference/put_conversations-conversationid-open)", + version: "0.0.1", + type: "action", + props: { + belco, + conversationId: { + propDefinition: [ + belco, + "conversationId", + () => ({ + includeStatus: [ + "closed", + ], + }), + ], + }, + }, + async run({ $ }) { + const response = await this.belco.reopenConversation({ + $, + conversationId: this.conversationId, + }); + + $.export("$summary", `Reopened conversation successfully: ${this.conversationId}`); + return response; + }, +}; diff --git a/components/belco/actions/reply-to-conversation/reply-to-conversation.mjs b/components/belco/actions/reply-to-conversation/reply-to-conversation.mjs new file mode 100644 index 0000000000000..bc251e76ba057 --- /dev/null +++ b/components/belco/actions/reply-to-conversation/reply-to-conversation.mjs @@ -0,0 +1,35 @@ +import belco from "../../belco.app.mjs"; + +export default { + key: "belco-reply-to-conversation", + name: "Reply to Conversation", + description: "Reply to a conversation specified by ID. [See the documentation](https://developers.belco.io/reference/put_conversations-conversationid-reply)", + version: "0.0.1", + type: "action", + props: { + belco, + conversationId: { + propDefinition: [ + belco, + "conversationId", + ], + }, + body: { + type: "string", + label: "Body", + description: "The reply message body", + }, + }, + async run({ $ }) { + const response = await this.belco.replyToConversation({ + $, + conversationId: this.conversationId, + data: { + body: this.body, + }, + }); + + $.export("$summary", `Replied to conversation successfully: ${this.conversationId}`); + return response; + }, +}; diff --git a/components/belco/actions/retrieve-conversation/retrieve-conversation.mjs b/components/belco/actions/retrieve-conversation/retrieve-conversation.mjs new file mode 100644 index 0000000000000..bcf2b3e24ee77 --- /dev/null +++ b/components/belco/actions/retrieve-conversation/retrieve-conversation.mjs @@ -0,0 +1,27 @@ +import belco from "../../belco.app.mjs"; + +export default { + key: "belco-retrieve-conversation", + name: "Retrieve Conversation", + description: "Retrieve a conversation specified by ID. [See the documentation](https://developers.belco.io/reference/get_conversations-conversationid)", + version: "0.0.1", + type: "action", + props: { + belco, + conversationId: { + propDefinition: [ + belco, + "conversationId", + ], + }, + }, + async run({ $ }) { + const response = await this.belco.getConversation({ + $, + conversationId: this.conversationId, + }); + + $.export("$summary", `Retrieved conversation successfully: ${this.conversationId}`); + return response; + }, +}; diff --git a/components/belco/actions/send-message/send-message.mjs b/components/belco/actions/send-message/send-message.mjs new file mode 100644 index 0000000000000..4c7621d8a0328 --- /dev/null +++ b/components/belco/actions/send-message/send-message.mjs @@ -0,0 +1,95 @@ +import belco from "../../belco.app.mjs"; + +export default { + key: "belco-send-message", + name: "Send Message", + description: "Send a message to a conversation specified by ID. [See the documentation](https://developers.belco.io/reference/post_conversations-sendmessage)", + version: "0.0.1", + type: "action", + props: { + belco, + shopId: { + propDefinition: [ + belco, + "shopId", + ], + }, + channel: { + propDefinition: [ + belco, + "channel", + ], + }, + type: { + propDefinition: [ + belco, + "type", + ], + }, + fromType: { + propDefinition: [ + belco, + "fromType", + ], + }, + from: { + propDefinition: [ + belco, + "from", + ({ + fromType, shopId, + }) => ({ + fromType, + shopId, + }), + ], + }, + to: { + propDefinition: [ + belco, + "to", + ({ + toType, shopId, + }) => ({ + toType, + shopId, + }), + ], + }, + subject: { + propDefinition: [ + belco, + "subject", + ], + }, + body: { + propDefinition: [ + belco, + "body", + ], + }, + }, + async run({ $ }) { + const response = await this.belco.sendMessage({ + $, + data: { + shopId: this.shopId, + channel: this.channel, + type: this.type, + from: { + type: this.fromType, + _id: this.from, + }, + to: { + type: "contact", + _id: this.to, + }, + subject: this.subject, + body: this.body, + }, + }); + + $.export("$summary", `Sent message successfully: ${this.subject || "No subject"}`); + return response; + }, +}; diff --git a/components/belco/belco.app.mjs b/components/belco/belco.app.mjs index a10c11fb466ef..ca08161e788b8 100644 --- a/components/belco/belco.app.mjs +++ b/components/belco/belco.app.mjs @@ -1,11 +1,268 @@ +import { axios } from "@pipedream/platform"; +import { + CHANNEL_OPTIONS, + CONVERSATION_TYPE_OPTIONS, + FROM_TYPE_OPTIONS, + LIMIT, +} from "./common/constants.mjs"; + export default { type: "app", app: "belco", - propDefinitions: {}, + propDefinitions: { + shopId: { + type: "string", + label: "Shop ID", + description: "The shop ID to use for the conversation", + async options() { + const shops = await this.listShops(); + return shops.map(({ + _id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + from: { + type: "string", + label: "From", + description: "The sender's identifier", + async options({ + fromType, shopId, + }) { + let response, items; + switch (fromType) { + case "user": + items = await this.listUsers(); + break; + case "team": + response = await this.listTeams(); + items = response.teams; + break; + case "contact": + response = await this.listContacts({ + shopId, + }); + items = response.contacts; + break; + } + return items.map(({ + _id, id, profile, pseudonym, name, + }) => ({ + label: pseudonym || name || `${profile.firstName} ${profile.lastName}`, + value: id || _id, + })); + }, + }, + to: { + type: "string", + label: "To", + description: "The recipient's identifier", + async options({ shopId }) { + const { contacts } = await this.listContacts({ + shopId, + }); + return contacts.map(({ + _id: value, pseudonym: label, + }) => ({ + label, + value, + })); + }, + }, + conversationId: { + type: "string", + label: "Conversation ID", + description: "Select a conversation to use for the action", + async options({ + page, includeStatus, excludeStatus, + }) { + const { conversations } = await this.listConversations({ + params: { + limit: LIMIT, + skip: page * LIMIT, + }, + }); + return conversations + .filter((item) => item.channel && (includeStatus + ? includeStatus.includes(item.status) + : true) && (excludeStatus + ? !excludeStatus.includes(item.status) + : true)) + .map(({ + _id, subject, + }) => ({ + label: `${_id} - ${subject || "No subject"}`, + value: _id, + })); + }, + }, + channel: { + type: "string", + label: "Channel", + description: "The channel type", + options: CHANNEL_OPTIONS, + }, + type: { + type: "string", + label: "Type", + description: "The conversation type", + options: CONVERSATION_TYPE_OPTIONS, + optional: true, + }, + fromType: { + type: "string", + label: "From Type", + description: "The type of the sender", + options: FROM_TYPE_OPTIONS, + }, + subject: { + type: "string", + label: "Subject", + description: "The conversation subject", + optional: true, + }, + body: { + type: "string", + label: "Body", + description: "The message body", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.belco.io/v1"; + }, + _headers() { + return { + Authorization: `Bearer ${this.$auth.api_token}`, + accept: "application/json", + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + listShops() { + return this._makeRequest({ + path: "/shops", + }); + }, + listUsers(opts = {}) { + return this._makeRequest({ + path: "/users", + ...opts, + }); + }, + listTeams(opts = {}) { + return this._makeRequest({ + path: "/teams", + ...opts, + }); + }, + listContacts({ + shopId, ...opts + }) { + return this._makeRequest({ + path: `/shops/${shopId}/contacts`, + ...opts, + }); + }, + listConversations(opts = {}) { + return this._makeRequest({ + path: "/conversations", + ...opts, + }); + }, + getConversation({ + conversationId, ...opts + }) { + return this._makeRequest({ + path: `/conversations/${conversationId}`, + ...opts, + }); + }, + createConversation(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/conversations", + ...opts, + }); + }, + sendMessage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/conversations/sendMessage", + ...opts, + }); + }, + closeConversation({ + conversationId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/conversations/${conversationId}/close`, + ...opts, + }); + }, + reopenConversation({ + conversationId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/conversations/${conversationId}/open`, + ...opts, + }); + }, + replyToConversation({ + conversationId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/conversations/${conversationId}/reply`, + ...opts, + }); + }, + addNoteToConversation({ + conversationId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/conversations/${conversationId}/addNote`, + ...opts, + }); + }, + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + params.limit = LIMIT; + params.skip = page * LIMIT; + page++; + const { conversations } = await fn({ + params, + ...opts, + }); + for (const conversation of conversations) { + yield conversation; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = (conversations.length === LIMIT); + + } while (hasMore); }, }, -}; \ No newline at end of file +}; diff --git a/components/belco/common/constants.mjs b/components/belco/common/constants.mjs new file mode 100644 index 0000000000000..90061fc7bbeec --- /dev/null +++ b/components/belco/common/constants.mjs @@ -0,0 +1,50 @@ +export const LIMIT = 100; + +export const CHANNEL_OPTIONS = [ + { + label: "Email", + value: "email", + }, + { + label: "Chat", + value: "chat", + }, + { + label: "Phone", + value: "phone", + }, +]; + +export const CONVERSATION_TYPE_OPTIONS = [ + { + label: "Auto Message", + value: "auto-message", + }, + { + label: "Outbound Message", + value: "outbound-message", + }, + { + label: "Inbound Message", + value: "inbound-message", + }, + { + label: "Follow Up", + value: "follow-up", + }, + { + label: "Note", + value: "note", + }, +]; + +export const FROM_TYPE_OPTIONS = [ + { + label: "User", + value: "user", + }, + { + label: "Contact", + value: "contact", + }, +]; diff --git a/components/belco/package.json b/components/belco/package.json index 66d2fd4213ea3..637c2c9130ff5 100644 --- a/components/belco/package.json +++ b/components/belco/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/belco", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Belco Components", "main": "belco.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0" } -} \ No newline at end of file +} diff --git a/components/belco/sources/new-conversation/new-conversation.mjs b/components/belco/sources/new-conversation/new-conversation.mjs new file mode 100644 index 0000000000000..095b8086d9ca4 --- /dev/null +++ b/components/belco/sources/new-conversation/new-conversation.mjs @@ -0,0 +1,70 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import belco from "../../belco.app.mjs"; + +export default { + key: "belco-new-conversation", + name: "New Conversation", + description: "Emit new conversation event when a new conversation is created. [See the documentation](https://developers.belco.io/reference/get_conversations)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + belco, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastDate() { + return this.db.get("lastDate") || "1970-01-01T00:00:00.000Z"; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + async emitEvent(maxResults = false) { + const lastDate = this._getLastDate(); + + const response = this.belco.paginate({ + fn: this.belco.listConversations, + }); + + let responseArray = []; + for await (const conversation of response) { + responseArray.push(conversation); + } + + responseArray = responseArray + .filter((conversation) => (conversation.createdAt > lastDate)) + .filter((conversation) => (conversation.channel)) + .sort((a, b) => (a.createdAt - b.createdAt)); + console.log("responseArray: ", responseArray); + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastDate(responseArray[0].createdAt); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item._id, + summary: `New conversation: ${item.subject || item._id}`, + ts: Date.parse(item.createdAt), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/belco/sources/new-conversation/test-event.mjs b/components/belco/sources/new-conversation/test-event.mjs new file mode 100644 index 0000000000000..91476116faeed --- /dev/null +++ b/components/belco/sources/new-conversation/test-event.mjs @@ -0,0 +1,40 @@ +export default { + "_id": "6EwvPh2xajPmR2otP", + "createdAt": "2025-07-10T16:33:32.644Z", + "lastItem": { + "reference": { + "_id": "6EwvPh2xajPmR2otP", + "type": "user" + }, + "_id": "6EwvPh2xajPmR2otP", + "type": "message", + "body": "Body message", + "via": "web" + }, + "channel": "chat", + "lastMessage": { + "reference": { + "_id": "6EwvPh2xajPmR2otP", + "type": "user" + }, + "_id": "6EwvPh2xajPmR2otP", + "type": "message", + "body": "Body message", + "via": "web" + }, + "customerId": "6EwvPh2xajPmR2otP", + "firstItem": { + "reference": { + "_id": "6EwvPh2xajPmR2otP", + "type": "user" + }, + "_id": "6EwvPh2xajPmR2otP", + "type": "message", + "body": "Body message", + "via": "web" + }, + "shopId": "6EwvPh2xajPmR2otP", + "type": "outbound-message", + "assignedTo": "6EwvPh2xajPmR2otP", + "status": "open" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64e43dccbdf0c..1580e5d444fcf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1444,7 +1444,11 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/belco: {} + components/belco: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 components/benchmark_email: dependencies: @@ -9846,8 +9850,7 @@ importers: components/paypro: {} - components/payrexx: - specifiers: {} + components/payrexx: {} components/paystack: dependencies: @@ -15883,14 +15886,6 @@ importers: specifier: ^6.0.0 version: 6.2.0 - modelcontextprotocol/node_modules2/@modelcontextprotocol/sdk/dist/cjs: {} - - modelcontextprotocol/node_modules2/@modelcontextprotocol/sdk/dist/esm: {} - - modelcontextprotocol/node_modules2/zod-to-json-schema/dist/cjs: {} - - modelcontextprotocol/node_modules2/zod-to-json-schema/dist/esm: {} - packages/ai: dependencies: '@pipedream/sdk': @@ -37179,6 +37174,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: