diff --git a/components/wati/.gitignore b/components/wati/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/wati/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/wati/actions/add-contact/add-contact.mjs b/components/wati/actions/add-contact/add-contact.mjs new file mode 100644 index 0000000000000..ec617afa928f9 --- /dev/null +++ b/components/wati/actions/add-contact/add-contact.mjs @@ -0,0 +1,53 @@ +import { ConfigurationError } from "@pipedream/platform"; +import wati from "../../wati.app.mjs"; + +export default { + key: "wati-add-contact", + name: "Add Contact", + description: "Adds a new contact on the WATI platform. [See the documentation](https://docs.wati.io/reference/post_api-v1-addcontact-whatsappnumber)", + version: "0.0.1", + type: "action", + props: { + wati, + whatsappNumber: { + propDefinition: [ + wati, + "whatsappNumber", + ], + }, + name: { + type: "string", + label: "Name", + description: "The name of the contact", + optional: true, + }, + customParams: { + propDefinition: [ + wati, + "customParams", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.wati.addContact({ + $, + whatsappNumber: this.whatsappNumber, + data: { + name: this.name, + customParams: this.customParams && Object.entries(this.customParams).map(([ + key, + value, + ]) => ({ + name: key, + value, + })), + }, + }); + if (!response.result) { + throw new ConfigurationError(response.info); + } + $.export("$summary", `Successfully added contact with phone number: ${this.whatsappNumber}`); + return response; + }, +}; diff --git a/components/wati/actions/send-template-message/send-template-message.mjs b/components/wati/actions/send-template-message/send-template-message.mjs new file mode 100644 index 0000000000000..3c02cc3974538 --- /dev/null +++ b/components/wati/actions/send-template-message/send-template-message.mjs @@ -0,0 +1,63 @@ +import { ConfigurationError } from "@pipedream/platform"; +import wati from "../../wati.app.mjs"; + +export default { + key: "wati-send-template-message", + name: "Send WhatsApp Template Message", + description: "Enables sending of WhatsApp messages using a pre-approved template. [See the documentation](https://docs.wati.io/reference/post_api-v2-sendtemplatemessage)", + version: "0.0.1", + type: "action", + props: { + wati, + whatsappNumber: { + propDefinition: [ + wati, + "whatsappNumber", + ], + }, + customParams: { + propDefinition: [ + wati, + "customParams", + ], + label: "Parameters", + description: "An object with template's custom params.", + }, + templateName: { + propDefinition: [ + wati, + "templateName", + ], + }, + broadcastName: { + type: "string", + label: "Broadcast Name", + description: "The name of broadcast.", + }, + }, + async run({ $ }) { + const response = await this.wati.sendTemplateMessage({ + $, + params: { + whatsappNumber: this.whatsappNumber, + }, + data: { + parameters: this.customParams && Object.entries(this.customParams).map(([ + key, + value, + ]) => ({ + name: key, + value, + })), + template_name: this.templateName, + broadcast_name: this.broadcastName, + }, + }); + if (!response.result) { + throw new ConfigurationError(response.info); + } + + $.export("$summary", `Successfully sent template message to ${this.whatsappNumber}`); + return response; + }, +}; diff --git a/components/wati/actions/update-contact-attribute/update-contact-attribute.mjs b/components/wati/actions/update-contact-attribute/update-contact-attribute.mjs new file mode 100644 index 0000000000000..d1254ea3183bc --- /dev/null +++ b/components/wati/actions/update-contact-attribute/update-contact-attribute.mjs @@ -0,0 +1,47 @@ +import { ConfigurationError } from "@pipedream/platform"; +import wati from "../../wati.app.mjs"; + +export default { + key: "wati-update-contact-attribute", + name: "Update Contact Attribute", + description: "Allows updating attributes/tags related to an existing contact. [See the documentation](https://docs.wati.io/reference/post_api-v1-updatecontactattributes-whatsappnumber)", + version: "0.0.1", + type: "action", + props: { + wati, + whatsappNumber: { + propDefinition: [ + wati, + "whatsappNumber", + ], + }, + customParams: { + propDefinition: [ + wati, + "customParams", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.wati.updateContactAttributes({ + $, + whatsappNumber: this.whatsappNumber, + data: { + customParams: this.customParams && Object.entries(this.customParams).map(([ + key, + value, + ]) => ({ + name: key, + value, + })), + }, + }); + if (!response.result) { + throw new ConfigurationError(response.info); + } + + $.export("$summary", `Successfully updated attributes for contact ${this.whatsappNumber}`); + return response; + }, +}; diff --git a/components/wati/app/wati.app.ts b/components/wati/app/wati.app.ts deleted file mode 100644 index 208ee0d199f21..0000000000000 --- a/components/wati/app/wati.app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ - type: "app", - app: "wati", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, -}); \ No newline at end of file diff --git a/components/wati/package.json b/components/wati/package.json index 031b83cd95569..90b471a941a4e 100644 --- a/components/wati/package.json +++ b/components/wati/package.json @@ -1,16 +1,18 @@ { "name": "@pipedream/wati", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream WATI Components", - "main": "dist/app/wati.app.mjs", + "main": "wati.app.mjs", "keywords": [ "pipedream", "wati" ], - "files": ["dist"], "homepage": "https://pipedream.com/apps/wati", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/wati/sources/common/base.mjs b/components/wati/sources/common/base.mjs new file mode 100644 index 0000000000000..8e10715b02532 --- /dev/null +++ b/components/wati/sources/common/base.mjs @@ -0,0 +1,61 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import wati from "../../wati.app.mjs"; + +export default { + props: { + wati, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + prepareData(data) { + return data; + }, + _getLastDate() { + return this.db.get("lastDate") || 0; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + async emitEvent(maxResults = false) { + const lastDate = this._getLastDate(); + const dateField = this.getDateField(); + + const response = this.wati.paginate( + this.getPaginateOpts(maxResults), + ); + + let responseArray = []; + for await (const item of response) { + responseArray.push(item); + } + + responseArray = this.prepareData(responseArray, lastDate, maxResults); + + if (responseArray.length) { + this._setLastDate(responseArray[0][dateField]); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item[dateField]), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/wati/sources/new-contact-created/new-contact-created.mjs b/components/wati/sources/new-contact-created/new-contact-created.mjs new file mode 100644 index 0000000000000..0b5c33e00917d --- /dev/null +++ b/components/wati/sources/new-contact-created/new-contact-created.mjs @@ -0,0 +1,33 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "wati-new-contact-created", + name: "New Contact Created", + description: "Emit new event when a contact is created from an incoming WhatsApp message.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getPaginateOpts(maxResults) { + return { + fn: this.wati.listContacts, + itemsField: "result", + optsField: "data", + maxResults, + }; + }, + getDateField() { + return "created"; + }, + checkBreak(item, lastDate) { + return Date.parse(item.created) < lastDate; + }, + getSummary(item) { + return `New contact created: ${item.wAid}`; + }, + }, + sampleEmit, +}; diff --git a/components/wati/sources/new-contact-created/test-event.mjs b/components/wati/sources/new-contact-created/test-event.mjs new file mode 100644 index 0000000000000..3a696c5a7d163 --- /dev/null +++ b/components/wati/sources/new-contact-created/test-event.mjs @@ -0,0 +1,33 @@ +export default { + "id": "670934c1d464c11dd46c3b7f", + "wAid": "17759865200", + "firstName": "+17759865200", + "fullName": "+17759865200", + "phone": "17759865200", + "source": null, + "contactStatus": "VALID", + "photo": null, + "created": "Oct-11-2024", + "customParams": [ + { + "name": "name", + "value": "+17759865200" + }, + { + "name": "phone", + "value": "17759865200" + } + ], + "optedIn": false, + "isDeleted": false, + "lastUpdated": "2024-10-11T15:09:36.047Z", + "allowBroadcast": true, + "allowSMS": true, + "teamIds": [ + "6708393ad464c11dd46b3d73" + ], + "isInFlow": false, + "lastFlowId": null, + "currentFlowNodeId": null, + "selectedHubspotId": null +} \ No newline at end of file diff --git a/components/wati/sources/new-incoming-message/new-incoming-message.mjs b/components/wati/sources/new-incoming-message/new-incoming-message.mjs new file mode 100644 index 0000000000000..448456aa51882 --- /dev/null +++ b/components/wati/sources/new-incoming-message/new-incoming-message.mjs @@ -0,0 +1,52 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "wati-new-incoming-message", + name: "New Incoming Message", + description: "Emit new event when there is an incoming message on your number.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + contactId: { + propDefinition: [ + common.props.wati, + "contactId", + ], + }, + }, + methods: { + ...common.methods, + getPaginateOpts() { + return { + fn: this.wati.listContactMessages, + whatsappNumber: `+${this.contactId}`, + itemsField: [ + "messages", + ], + optsField: "params", + }; + }, + getDateField() { + return "timestamp"; + }, + prepareData(data, lastDate, maxResults) { + data = data + .filter((item) => item.statusString === "SENT" && Date.parse(item.created) > lastDate) + .sort((a, b) => Date.parse(b.created) - Date.parse(a.created)); + + if (maxResults && data.length > maxResults) data.length = maxResults; + return data; + }, + checkBreak(item, lastDate) { + return Date.parse(item.timestamp) < lastDate; + }, + getSummary(item) { + return `New message: ${item.text || "No content"}`; + }, + }, + sampleEmit, +}; diff --git a/components/wati/sources/new-incoming-message/test-event.mjs b/components/wati/sources/new-incoming-message/test-event.mjs new file mode 100644 index 0000000000000..c478a5e1f2f00 --- /dev/null +++ b/components/wati/sources/new-incoming-message/test-event.mjs @@ -0,0 +1,26 @@ +export default { + "replySourceMessage": null, + "messageReferral": null, + "text": "Know the Pricing", + "type": "text", + "data": null, + "timestamp": "1728656646", + "owner": false, + "statusString": "SENT", + "avatarUrl": null, + "assignedId": null, + "operatorName": null, + "localMessageId": null, + "failedDetail": null, + "referenceOrderId": null, + "contacts": null, + "messageProducts": null, + "orderProducts": null, + "interactiveData": null, + "orderDetailsViewModel": null, + "id": "67093506d464c11dd46c3bcf", + "created": "2024-10-11T14:24:06.891Z", + "conversationId": "670934f5d464c11dd46c3bc6", + "ticketId": "67093506d464c11dd46c3bcc", + "eventType": "message" +} \ No newline at end of file diff --git a/components/wati/wati.app.mjs b/components/wati/wati.app.mjs new file mode 100644 index 0000000000000..295552c153504 --- /dev/null +++ b/components/wati/wati.app.mjs @@ -0,0 +1,144 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "wati", + propDefinitions: { + contactId: { + type: "string", + label: "Contact Id", + description: "The Id of the contact.", + async options({ page }) { + const { result: { items } } = await this.listContacts({ + data: { + pageSize: 100, + pageNumber: page, + }, + }); + + return items.map(({ wAid }) => wAid); + }, + }, + whatsappNumber: { + type: "string", + label: "WhatsApp Number", + description: "Your WhatsApp number with country code.", + }, + customParams: { + type: "object", + label: "Custom Params", + description: "An object with contact's custom fields.", + }, + templateName: { + type: "string", + label: "Template Name", + description: "The name of template.", + async options({ page }) { + const { messageTemplates: data } = await this.listTemplates({ + params: { + pageSize: page + 1, + }, + }); + + return data.map(({ elementName }) => elementName); + }, + }, + }, + methods: { + _baseUrl() { + return `${this.$auth.api_endpoint}/api/v1`; + }, + _headers() { + return { + Authorization: `${this.$auth.access_token}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + listContacts(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contacts", + ...opts, + }); + }, + listContactMessages({ + whatsappNumber, ...opts + }) { + return this._makeRequest({ + path: `/getMessages/${whatsappNumber}`, + ...opts, + }); + }, + listTemplates(opts = {}) { + return this._makeRequest({ + path: "/getMessageTemplates", + ...opts, + }); + }, + addContact({ + whatsappNumber, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/addContact/${whatsappNumber}`, + ...opts, + }); + }, + sendTemplateMessage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/sendTemplateMessage", + ...opts, + }); + }, + updateContactAttributes({ + whatsappNumber, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/updateContactAttributes/${whatsappNumber}`, + ...opts, + }); + }, + async *paginate({ + fn, itemsField, optsField, maxResults = null, data = {}, params = {}, ...otherOpts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + const opts = { + data, + params, + ...otherOpts, + }; + + opts[optsField].pageSize = 100; + + do { + opts[optsField].pageNumber = page++; + const response = await fn(opts); + const items = response[itemsField].items; + + for (const d of items) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = items.length; + + } while (hasMore); + }, + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87407ed7e9880..9a8b902fe0ad6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11010,7 +11010,10 @@ importers: specifiers: {} components/wati: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.3 + dependencies: + '@pipedream/platform': 3.0.3 components/watsonx_ai: specifiers: {}