n8n-nodes-whatsapp-buttons
Advanced tools
Comparing version 0.1.6 to 0.2.0
@@ -5,5 +5,4 @@ import { ICredentialType, INodeProperties } from "n8n-workflow"; | ||
displayName: string; | ||
icon: string; | ||
documentationUrl: string; | ||
properties: INodeProperties[]; | ||
} |
@@ -8,3 +8,2 @@ "use strict"; | ||
this.displayName = "WhatsAppButtons API"; | ||
this.icon = "file:whatsappbuttons.svg"; | ||
this.documentationUrl = "https://docs.n8n.io/integrations/creating-nodes/build/declarative-style-node/"; | ||
@@ -20,4 +19,4 @@ this.properties = [ | ||
{ | ||
displayName: "Phone Number ID", | ||
name: "phoneNumberID", | ||
displayName: "Business Account ID", | ||
name: "businessAccountID", | ||
type: "string", | ||
@@ -24,0 +23,0 @@ typeOptions: { password: false }, |
@@ -1,10 +0,14 @@ | ||
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from "n8n-workflow"; | ||
export declare type SectionArray = Array<{ | ||
import type { ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription } from "n8n-workflow"; | ||
import { IExecuteFunctions } from "n8n-workflow"; | ||
export type ParameterArray = Array<{ | ||
parameterValue: string; | ||
}>; | ||
export type SectionArray = Array<{ | ||
sectionTitle: string; | ||
buttonInSection: MidButtonArray; | ||
}>; | ||
export declare type MidButtonArray = { | ||
export type MidButtonArray = { | ||
buttons: ButtonArray; | ||
}; | ||
export declare type ButtonArray = Array<{ | ||
export type ButtonArray = Array<{ | ||
buttonTitle: string; | ||
@@ -15,3 +19,9 @@ buttonDescription: string; | ||
description: INodeTypeDescription; | ||
methods: { | ||
loadOptions: { | ||
getPhoneNumbers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>; | ||
getTemplates(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>; | ||
}; | ||
}; | ||
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>; | ||
} |
@@ -8,2 +8,5 @@ "use strict"; | ||
const axios_1 = __importDefault(require("axios")); | ||
const WhatsAppTemplateBuilder_1 = require("./WhatsAppTemplateBuilder"); | ||
const n8n_workflow_1 = require("n8n-workflow"); | ||
const baseURL = "https://graph.facebook.com/v20.0"; | ||
class WhatsAppButtons { | ||
@@ -13,7 +16,7 @@ constructor() { | ||
displayName: "WhatsApp Buttons", | ||
name: 'whatsAppButtons', | ||
name: "whatsAppButtons", | ||
icon: "file:whatsappbuttons.svg", | ||
group: ["transform"], | ||
version: 1, | ||
subtitle: "0.1.6", | ||
subtitle: "0.2.0", | ||
description: "Send Message With Buttons", | ||
@@ -45,2 +48,10 @@ defaults: { | ||
}, | ||
{ | ||
name: "Template", | ||
value: "template", | ||
}, | ||
{ | ||
name: "Message", | ||
value: "message", | ||
}, | ||
], | ||
@@ -68,5 +79,37 @@ default: "interactive", | ||
required: true, | ||
displayOptions: { | ||
show: { | ||
action: ["interactive", "listButtons", "message"], | ||
}, | ||
}, | ||
description: "Enter the message to be sent", | ||
}, | ||
{ | ||
displayName: "Sender Phone Number (or ID)", | ||
name: "senderPhoneDynamicOption", | ||
type: "options", | ||
required: true, | ||
default: "", | ||
description: "The ID of the business account's phone number from which the message will be sent from", | ||
typeOptions: { | ||
loadOptionsMethod: "getPhoneNumbers", | ||
}, | ||
}, | ||
{ | ||
displayName: "Templates", | ||
name: "templates", | ||
type: "options", | ||
default: "", | ||
placeholder: "Select Template To Send", | ||
description: "Pull and Select a template to send", | ||
typeOptions: { | ||
loadOptionsMethod: "getTemplates", | ||
}, | ||
displayOptions: { | ||
show: { | ||
action: ["template"], | ||
}, | ||
}, | ||
}, | ||
{ | ||
displayName: "Recipient Phone Number", | ||
@@ -105,12 +148,5 @@ name: "phoneNumber", | ||
}, | ||
description: 'Enter the image URL', | ||
description: "Enter the image URL", | ||
}, | ||
{ | ||
displayName: "Footer", | ||
name: "footer", | ||
type: "string", | ||
default: "", | ||
description: "Will be presented at the bottom of the message", | ||
}, | ||
{ | ||
displayName: "Max 3 Buttons", | ||
@@ -215,123 +251,284 @@ name: "plainButton", | ||
}, | ||
{ | ||
displayName: "Template Parameter List", | ||
name: "templateParameterList", | ||
placeholder: "Add Parameter", | ||
type: "fixedCollection", | ||
description: "Each template has a number of parameters shown as {{1}}, for every parameter add a text to replace it with", | ||
typeOptions: { | ||
multipleValues: true, | ||
}, | ||
displayOptions: { | ||
show: { | ||
action: ["template"], | ||
}, | ||
}, | ||
default: {}, | ||
options: [ | ||
{ | ||
name: "parameters", | ||
displayName: "Parameters", | ||
values: [ | ||
{ | ||
displayName: "Parameter Value", | ||
name: "parameterValue", | ||
type: "string", | ||
default: "", | ||
description: "Value to replace the template parameter (e.g., {{1}}, {{2}})", | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
displayName: "Should Use Footer", | ||
name: "footerToggle", | ||
type: "boolean", | ||
default: false, | ||
description: "Whether to add a footer message", | ||
}, | ||
{ | ||
displayName: "Footer", | ||
name: "footer", | ||
type: "string", | ||
default: "", | ||
description: "Will be presented at the bottom of the message", | ||
displayOptions: { | ||
show: { | ||
footerToggle: [true], | ||
}, | ||
}, | ||
}, | ||
{ | ||
displayName: "Should Use Proxy URL", | ||
name: "proxyUrlToggle", | ||
type: "boolean", | ||
default: false, | ||
description: "Whether to add a proxy URL that send the request to it instead of whatsapp servers", | ||
}, | ||
{ | ||
displayName: "Proxy URL", | ||
name: "proxyUrl", | ||
type: "string", | ||
default: "", | ||
description: "Use this proxy to send the message to this URL instead of sending it to WhatsApp servers, meaning by using this value the you will be responsible for delivering the message", | ||
displayOptions: { | ||
show: { | ||
proxyUrlToggle: [true], | ||
}, | ||
}, | ||
}, | ||
], | ||
}; | ||
this.methods = { | ||
loadOptions: { | ||
async getPhoneNumbers() { | ||
const credentials = await this.getCredentials("whatsAppButtonsApi"); | ||
const response = await axios_1.default.get(`${baseURL}/${credentials.businessAccountID}/phone_numbers`, { | ||
headers: { | ||
Authorization: `Bearer ${credentials.apiKey}`, | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
const phoneNumbers = response.data.data; | ||
if (phoneNumbers.length === 0) { | ||
return [ | ||
{ | ||
name: "No Phone Numbers Available", | ||
value: "", | ||
}, | ||
]; | ||
} | ||
return phoneNumbers.map((option) => ({ | ||
name: `${option.display_phone_number} - ${option.verified_name}`, | ||
value: JSON.stringify(option), | ||
})); | ||
}, | ||
async getTemplates() { | ||
const credentials = await this.getCredentials("whatsAppButtonsApi"); | ||
const response = await axios_1.default.get(`${baseURL}/${credentials.businessAccountID}/message_templates`, { | ||
headers: { | ||
Authorization: `Bearer ${credentials.apiKey}`, | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
const templates = response.data.data; | ||
if (templates.length === 0) { | ||
return [ | ||
{ | ||
name: "No Templates Available", | ||
value: "", | ||
}, | ||
]; | ||
} | ||
return templates.map((option) => ({ | ||
name: option.name, | ||
value: JSON.stringify(option), | ||
})); | ||
}, | ||
}, | ||
}; | ||
} | ||
async execute() { | ||
const baseURL = "https://graph.facebook.com/v17.0"; | ||
const action = this.getNodeParameter("action", 0); | ||
const message = this.getNodeParameter("message", 0); | ||
const phoneNumber = this.getNodeParameter("phoneNumber", 0); | ||
const whatsappPhoneNumberIdString = this.getNodeParameter("senderPhoneDynamicOption", 0); | ||
const whatsappPhoneNumber = JSON.parse(whatsappPhoneNumberIdString); | ||
const proxyUrl = this.getNodeParameter("proxyUrl", 0, ""); | ||
const proxyUrlToggle = this.getNodeParameter("proxyUrlToggle", 0, false); | ||
const credentials = await this.getCredentials("whatsAppButtonsApi"); | ||
const headerAction = this.getNodeParameter("headerAction", 0); | ||
const footer = this.getNodeParameter("footer", 0, ""); | ||
const headerImageURL = this.getNodeParameter("headerImageURL", 0, ""); | ||
const credentials = await this.getCredentials("whatsAppButtonsApi"); | ||
let responseData; | ||
let request; | ||
let message = ""; | ||
const builder = new WhatsAppTemplateBuilder_1.WhatsAppTemplateBuilder(); | ||
const url = proxyUrl !== "" | ||
? proxyUrl | ||
: `${baseURL}/${whatsappPhoneNumber.id}/messages`; | ||
const shouldUseProxyURL = proxyUrl !== "" && proxyUrlToggle; | ||
try { | ||
switch (action) { | ||
case "message": | ||
message = this.getNodeParameter("message", 0); | ||
const handleSendingMessageRequest = async (url, phoneNumber, fromPhoneId, headerAction, headerImageURL, footer, messageText, credentials) => { | ||
const templateBuilder = builder | ||
.setRecipient(phoneNumber) | ||
.setSender(fromPhoneId) | ||
.setPlainMessage(messageText); | ||
let json = {}; | ||
if (shouldUseProxyURL) { | ||
json = { | ||
body: templateBuilder.build(), | ||
}; | ||
} | ||
else { | ||
json = templateBuilder.build(); | ||
} | ||
return axios_1.default.post(url, JSON.stringify(json), { | ||
headers: { | ||
Authorization: `Bearer ${credentials.apiKey}`, | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
}; | ||
responseData = (await handleSendingMessageRequest(url, phoneNumber, whatsappPhoneNumber.id, headerAction, headerImageURL, footer, message, credentials)).data; | ||
break; | ||
case "interactive": | ||
console.log("interactive tapped"); | ||
message = this.getNodeParameter("message", 0); | ||
const buttonFields = this.getNodeParameter("plainButton.fieldValues", 0, ""); | ||
request = handleInteractiveButtonsRequest(`${baseURL}/${credentials.phoneNumberID}/messages`, message, phoneNumber, headerAction, headerImageURL, footer, buttonFields, credentials); | ||
responseData = (await request).data; | ||
const handleInteractiveButtonsRequest = async (url, message, phoneNumber, fromPhoneId, headerAction, headerImageURL, footer, queryParameters, credentials) => { | ||
const templateBuilder = builder | ||
.setRecipient(phoneNumber) | ||
.setSender(fromPhoneId) | ||
.addInteractiveButton(message, queryParameters.map((parameter, index) => { | ||
return { | ||
id: `button_${index.toString()}`, | ||
title: parameter.buttonTitle, | ||
}; | ||
})); | ||
let json = {}; | ||
if (shouldUseProxyURL) { | ||
json = { | ||
body: templateBuilder.build(), | ||
}; | ||
} | ||
else { | ||
json = templateBuilder.build(); | ||
} | ||
return axios_1.default.post(url, JSON.stringify(json), { | ||
headers: { | ||
Authorization: `Bearer ${credentials.apiKey}`, | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
}; | ||
responseData = (await handleInteractiveButtonsRequest(url, message, phoneNumber, whatsappPhoneNumber.id, headerAction, headerImageURL, footer, buttonFields, credentials)).data; | ||
break; | ||
case "listButtons": | ||
console.log("listButtons tapped"); | ||
message = this.getNodeParameter("message", 0); | ||
const buttonFieldsWithDescription = this.getNodeParameter("buttonWithDescription.section", 0, ""); | ||
const listTitle = this.getNodeParameter("listTitle", 0); | ||
request = handleListButtonsRequest(`${baseURL}/${credentials.phoneNumberID}/messages`, message, phoneNumber, headerAction, headerImageURL, footer, listTitle, buttonFieldsWithDescription, credentials); | ||
responseData = (await request).data; | ||
const handleListButtonsRequest = async (url, message, phoneNumber, fromPhoneId, headerAction, headerImageURL, footer, listTitle, sections, credentials) => { | ||
let sectionsDict = []; | ||
sections.forEach((section) => { | ||
const buttons = section.buttonInSection.buttons.map((button, index) => { | ||
return { | ||
id: `button_${index.toString()}`, | ||
title: button.buttonTitle, | ||
description: button.buttonDescription, | ||
}; | ||
}); | ||
sectionsDict.push({ | ||
title: section.sectionTitle, | ||
rows: buttons, | ||
}); | ||
}); | ||
const templateBuilder = builder | ||
.setRecipient(phoneNumber) | ||
.setSender(fromPhoneId) | ||
.addInteractiveList(message, listTitle, sectionsDict, headerImageURL === "" ? null : headerImageURL, footer === "" ? null : footer); | ||
let json = {}; | ||
if (shouldUseProxyURL) { | ||
json = { | ||
body: templateBuilder.build(), | ||
}; | ||
} | ||
else { | ||
json = templateBuilder.build(); | ||
} | ||
return axios_1.default.post(url, JSON.stringify(json), { | ||
headers: { | ||
Authorization: `Bearer ${credentials.apiKey}`, | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
}; | ||
responseData = (await handleListButtonsRequest(url, message, phoneNumber, whatsappPhoneNumber.id, headerAction, headerImageURL, footer, listTitle, buttonFieldsWithDescription, credentials)).data; | ||
break; | ||
case "template": | ||
const selectedTemplateNode = this.getNodeParameter("templates", 0); | ||
const selectedTemplate = JSON.parse(selectedTemplateNode); | ||
const parameters = this.getNodeParameter("templateParameterList.parameters", 0, ""); | ||
const handleSendingTemplateRequest = async (url, phoneNumber, fromPhoneId, headerAction, headerImageURL, footer, template, credentials, parameters) => { | ||
const templateBuilder = builder | ||
.setRecipient(phoneNumber) | ||
.setSender(fromPhoneId) | ||
.setTemplateName(template.name, template.language); | ||
if (parameters && Array.isArray(parameters)) { | ||
templateBuilder.addTemplateBodyParameter(); | ||
parameters.forEach((parameter) => { | ||
templateBuilder.addTemplateBodyTextParameter(parameter.parameterValue); | ||
}); | ||
} | ||
if (headerAction === "image" && headerImageURL !== "") { | ||
templateBuilder.addTemplateHeaderParameter(); | ||
templateBuilder.addTemplateHeaderImageParameter(headerImageURL); | ||
} | ||
let json = {}; | ||
if (shouldUseProxyURL) { | ||
json = { | ||
body: templateBuilder.build(), | ||
templateSchema: template, | ||
}; | ||
} | ||
else { | ||
json = templateBuilder.build(); | ||
} | ||
return axios_1.default.post(url, JSON.stringify(json), { | ||
headers: { | ||
Authorization: `Bearer ${credentials.apiKey}`, | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
}; | ||
responseData = (await handleSendingTemplateRequest(url, phoneNumber, whatsappPhoneNumber.id, headerAction, headerImageURL, footer, selectedTemplate, credentials, parameters)).data; | ||
break; | ||
} | ||
const outputData = [{ json: responseData }]; | ||
return this.prepareOutputData(outputData); | ||
return this.prepareOutputData([{ json: responseData }]); | ||
} | ||
catch (error) { | ||
console.error(`Error making ${action} request to WhatsAppButtons API:`, error.message); | ||
throw error; | ||
throw new n8n_workflow_1.NodeApiError(this.getNode(), error); | ||
} | ||
function buildBaseJSON(phoneNumber, message, headerImageURL, footer) { | ||
const json = { | ||
messaging_product: "whatsapp", | ||
recipient_type: "individual", | ||
to: "", | ||
type: "interactive", | ||
interactive: { | ||
type: "", | ||
body: { | ||
text: "", | ||
}, | ||
action: {}, | ||
}, | ||
}; | ||
json.to = phoneNumber; | ||
json.interactive.body.text = message; | ||
if (headerAction === "image") { | ||
json.interactive.header = { | ||
type: "image", | ||
image: { | ||
link: headerImageURL, | ||
}, | ||
}; | ||
} | ||
if (footer.length > 0) { | ||
json.interactive.footer = { | ||
text: footer, | ||
}; | ||
} | ||
return json; | ||
} | ||
function handleInteractiveButtonsRequest(url, message, phoneNumber, headerAction, headerImageURL, footer, queryParameters, credentials) { | ||
const json = buildBaseJSON(phoneNumber, message, headerImageURL, footer); | ||
json.interactive.type = 'button'; | ||
if (queryParameters.length > 0) { | ||
let buttons = []; | ||
queryParameters.forEach((button, index) => { | ||
buttons.push({ | ||
type: "reply", | ||
reply: { | ||
id: `button_${index.toString()}`, | ||
title: button.buttonTitle, | ||
}, | ||
}); | ||
}); | ||
json.interactive.action.buttons = buttons; | ||
} | ||
console.log(JSON.stringify(json)); | ||
return axios_1.default.post(url, JSON.stringify(json), { | ||
headers: { | ||
Authorization: `Bearer ${credentials.apiKey}`, | ||
"Content-Type": "application/json" | ||
}, | ||
}); | ||
} | ||
function handleListButtonsRequest(url, message, phoneNumber, headerAction, headerImageURL, footer, listTitle, sections, credentials) { | ||
const json = buildBaseJSON(phoneNumber, message, headerImageURL, footer); | ||
json.interactive.type = 'list'; | ||
let sectionsDict = []; | ||
sections.forEach((section) => { | ||
const buttons = section.buttonInSection.buttons.map((button, index) => { | ||
return { | ||
id: `button_${index.toString()}`, | ||
title: button.buttonTitle, | ||
description: button.buttonDescription | ||
}; | ||
}); | ||
sectionsDict.push({ | ||
title: section.sectionTitle, | ||
rows: buttons | ||
}); | ||
}); | ||
json.interactive.action = { | ||
button: listTitle, | ||
sections: sectionsDict | ||
}; | ||
console.log(JSON.stringify(json)); | ||
return axios_1.default.post(url, JSON.stringify(json), { | ||
headers: { | ||
Authorization: `Bearer ${credentials.apiKey}`, | ||
"Content-Type": "application/json" | ||
}, | ||
}); | ||
} | ||
} | ||
@@ -338,0 +535,0 @@ } |
{ | ||
"name": "n8n-nodes-whatsapp-buttons", | ||
"version": "0.1.6", | ||
"version": "0.2.0", | ||
"description": "Encapsulate WhatsApp Business API for sending messages with actions", | ||
@@ -18,4 +18,10 @@ "keywords": [ | ||
}, | ||
"engines": { | ||
"node": ">=18.10", | ||
"pnpm": ">=9.1" | ||
}, | ||
"packageManager": "pnpm@9.1.4", | ||
"main": "index.js", | ||
"scripts": { | ||
"preinstall": "npx only-allow pnpm", | ||
"build": "tsc && gulp build:icons", | ||
@@ -26,3 +32,3 @@ "dev": "tsc --watch", | ||
"lintfix": "eslint nodes credentials package.json --fix", | ||
"prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json" | ||
"prepublishOnly": "pnpm build && pnpm lint -c .eslintrc.prepublish.js nodes credentials package.json" | ||
}, | ||
@@ -42,14 +48,16 @@ "files": [ | ||
"devDependencies": { | ||
"@types/express": "^4.17.6", | ||
"@types/request-promise-native": "~1.0.15", | ||
"@typescript-eslint/eslint-plugin": "^6.14.0", | ||
"@typescript-eslint/parser": "^6.14.0", | ||
"eslint": "^8.55.0", | ||
"eslint-plugin-n8n-nodes-base": "*", | ||
"@types/node": "^22.5.4", | ||
"@typescript-eslint/parser": "^7.15.0", | ||
"axios": "^1.7.7", | ||
"eslint": "^8.56.0", | ||
"eslint-plugin-n8n-nodes-base": "^1.16.1", | ||
"gulp": "^4.0.2", | ||
"n8n-core": "*", | ||
"n8n-workflow": "*", | ||
"prettier": "^2.7.1", | ||
"typescript": "~4.8.4" | ||
} | ||
"prettier": "^3.3.2", | ||
"typescript": "^5.5.3" | ||
}, | ||
"peerDependencies": { | ||
"n8n-workflow": "*" | ||
}, | ||
"dependencies": {} | ||
} |
{ | ||
"name": "n8n-nodes-whatsapp-buttons", | ||
"version": "0.1.6", | ||
"version": "0.2.0", | ||
"description": "Encapsulate WhatsApp Business API for sending messages with actions", | ||
@@ -18,4 +18,10 @@ "keywords": [ | ||
}, | ||
"engines": { | ||
"node": ">=18.10", | ||
"pnpm": ">=9.1" | ||
}, | ||
"packageManager": "pnpm@9.1.4", | ||
"main": "index.js", | ||
"scripts": { | ||
"preinstall": "npx only-allow pnpm", | ||
"build": "tsc && gulp build:icons", | ||
@@ -26,3 +32,3 @@ "dev": "tsc --watch", | ||
"lintfix": "eslint nodes credentials package.json --fix", | ||
"prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json" | ||
"prepublishOnly": "pnpm build && pnpm lint -c .eslintrc.prepublish.js nodes credentials package.json" | ||
}, | ||
@@ -42,14 +48,17 @@ "files": [ | ||
"devDependencies": { | ||
"@types/express": "^4.17.6", | ||
"@types/request-promise-native": "~1.0.15", | ||
"@typescript-eslint/eslint-plugin": "^6.14.0", | ||
"@typescript-eslint/parser": "^6.14.0", | ||
"eslint": "^8.55.0", | ||
"eslint-plugin-n8n-nodes-base": "*", | ||
"@types/node": "^22.5.4", | ||
"@typescript-eslint/parser": "^7.15.0", | ||
"axios": "^1.7.7", | ||
"eslint": "^8.56.0", | ||
"eslint-plugin-n8n-nodes-base": "^1.16.1", | ||
"gulp": "^4.0.2", | ||
"n8n-core": "*", | ||
"n8n-workflow": "*", | ||
"prettier": "^2.7.1", | ||
"typescript": "~4.8.4" | ||
"prettier": "^3.3.2", | ||
"typescript": "^5.5.3" | ||
}, | ||
"peerDependencies": { | ||
"n8n-workflow": "*" | ||
}, | ||
"dependencies": { | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
258262
9
51
3291
1
2
1