Giter Site home page Giter Site logo

tawn33y / whatsapp-cloud-api Goto Github PK

View Code? Open in Web Editor NEW
184.0 11.0 50.0 1.1 MB

A Node.js library for creating bots and sending/receiving messages using the Whatsapp Cloud API.

Home Page: https://www.npmjs.com/package/whatsapp-cloud-api

License: GNU General Public License v3.0

JavaScript 4.54% Shell 0.28% TypeScript 95.19%
api cloud whatsapp whatsapp-bot bot bot-framework chatbot whatsapp-cloud bot-api business-api cloud-api whatsapp-api whatsapp-business whatsapp-business-api whatsapp-cloud-api nodejs

whatsapp-cloud-api's Introduction

whatsapp-cloud-api

whatsapp-cloud-api is a Node.js library for creating bots and sending/receiving messages using the Whatsapp Cloud API.

Contains built-in Typescript declarations.

run tests, lint, build npm publish npm npm bundle size npm

Install

Using npm:

npm i whatsapp-cloud-api

Using yarn:

yarn add whatsapp-cloud-api

Usage

import { createBot } from 'whatsapp-cloud-api';
// or if using require:
// const { createBot } = require('whatsapp-cloud-api');

(async () => {
  try {
    // replace the values below
    const from = 'YOUR_WHATSAPP_PHONE_NUMBER_ID';
    const token = 'YOUR_TEMPORARY_OR_PERMANENT_ACCESS_TOKEN';
    const to = 'PHONE_NUMBER_OF_RECIPIENT';
    const webhookVerifyToken = 'YOUR_WEBHOOK_VERIFICATION_TOKEN';

    // Create a bot that can send messages
    const bot = createBot(from, token);

    // Send text message
    const result = await bot.sendText(to, 'Hello world');

    // Start express server to listen for incoming messages
    // NOTE: See below under `Documentation/Tutorial` to learn how
    // you can verify the webhook URL and make the server publicly available
    await bot.startExpressServer({
      webhookVerifyToken,
    });

    // Listen to ALL incoming messages
    // NOTE: remember to always run: await bot.startExpressServer() first
    bot.on('message', async (msg) => {
      console.log(msg);

      if (msg.type === 'text') {
        await bot.sendText(msg.from, 'Received your text message!');
      } else if (msg.type === 'image') {
        await bot.sendText(msg.from, 'Received your image!');
      }
    });
  } catch (err) {
    console.log(err);
  }
})();

Documentation

Examples

Sending other message types (read more in API reference):

// Send image
const result = await bot.sendImage(to, 'https://picsum.photos/200/300', {
  caption: 'Random jpg',
});

// Send location
const result = await bot.sendLocation(to, 40.7128, -74.0060, {
  name: 'New York',
});

// Send template
const result = await bot.sendTemplate(to, 'hello_world', 'en_us');

Customized express server (read more below):

import cors from 'cors';

// Create bot...
const bot = createBot(...);

// Customize server
await bot.startExpressServer({
  webhookVerifyToken: 'my-verification-token',
  port: 3000,
  webhookPath: `/custom/webhook`,
  useMiddleware: (app) => {
    app.use(cors()),
  },
});

Listening to other message types (read more in API reference):

const bot = createBot(...);

await bot.startExpressServer({ webhookVerifyToken });

// Listen to incoming text messages ONLY
bot.on('text', async (msg) => {
  console.log(msg);
  await bot.sendText(msg.from, 'Received your text!');
});

// Listen to incoming image messages ONLY
bot.on('image', async (msg) => {
  console.log(msg);
  await bot.sendText(msg.from, 'Received your image!');
});

Notes

1. Verifying your Webhook URL

By default, the endpoint for whatsapp-related requests will be: /webhook/whatsapp. This means that locally, your URL will be: http://localhost/webhook/whatsapp.

You can use a reverse proxy to make the server publicly available. An example of this is ngrok.

You can read more on the Tutorial.

2. Handling incoming messages

The implementation above creates an express server for you through which it listens to incoming messages. There may be plans to support other types of server in future (PRs are welcome! :)).

You can change the port as follows:

await bot.startExpressServer({
  port: 3000,
});

By default, all requests are handled by the POST|GET /webhook/whatsapp endpoint. You can change this as below:

await bot.startExpressServer({
  webhookPath: `/custom/webhook`,
});

Note: Remember the leading /; i.e. don't use custom/whatsapp; instead use /custom/whatsapp.

If you are already running an express server in your application, you can avoid creating a new one by using it as below:

// your code...
import express from 'express';
const app = express();

...

// use the `app` variable below:
await bot.startExpressServer({
  app,
});

To add middleware:

import cors from 'cors';

await bot.startExpressServer({
  useMiddleware: (app) => {
    app.use(cors()),
  },
});

Full customized setup:

import cors from 'cors';

await bot.startExpressServer({
  webhookVerifyToken: 'my-verification-token',
  port: 3000,
  webhookPath: `/custom/webhook`,
  useMiddleware: (app) => {
    app.use(cors()),
  },
});

3. on() listener

This library uses a single process pubsub, which means that it won't work well if you're deploying on multi-instance clusters, e.g. distributed Kubernetes clusters. In future, there may be plans to export/support a pubsub reference which can be stored in extenal storage, e.g. redis (PRs are welcome! :)).

Development

# install npm modules
npm i

# eslint
npm run lint

# typescript check
npm run ts-check

# test
## Read 'Local Testing' below before running this
npm t

# build
npm run build

Local Testing

Create a .env file in the root of your project:

FROM_PHONE_NUMBER_ID=""
ACCESS_TOKEN=""
VERSION=""
TO=""
WEBHOOK_VERIFY_TOKEN=""
WEBHOOK_PATH=""

Attribution

Library API inspired by node-telegram-bot-api.

Pull Requests

Any and all PRs are open.

whatsapp-cloud-api's People

Contributors

guskuma avatar tawn33y avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

whatsapp-cloud-api's Issues

sendSticker fails to send without throwing error

i am able to successfully
SendText and SendVideo, however bot.sendSticker, using the url of a webp file fails:

try{
await bot.sendSticker(from, msgUrl);
console.log(sticker sent);
res.status(200).send('Sticker Sent');
}catch(err){
console.log(get/sendsticker error:,err);
res.status(200).send({message:'Error sending Sticker',"to":from,"text":msgText});
}

Results:
no error is thrown,
"sticker sent" displayed,
but the sticker is not sent to my whatsapp ....
using url: https://pelicansecured.com/mobilemenu/assets/imgs/delivery_on_the_way.webp

Cleaner API

  • Show full Typescript definition for function arguments on hover: i.e.
// instead of:
sendText(options: TextMessageOptions)
// show:
sendText(to: string, text: string)
  • Remove unnecessary boilerplate & remodel functions into a more friendly format, e.g.
// instead of:
sendMedia({ to: 'xx', type: 'image', link: 'https://...' })
// use:
sendImage('xx', 'https://...')
  • Add API reference

Receive shopping cart message

Hi! 👋

Firstly, thanks for your work on this project! 🙂

Today I used patch-package to patch [email protected] for the project I'm working on.

Here is the diff that solved my problem:

diff --git a/node_modules/whatsapp-cloud-api/dist/startExpressServer.js b/node_modules/whatsapp-cloud-api/dist/startExpressServer.js
index 23b17a1..c7ae54d 100644
--- a/node_modules/whatsapp-cloud-api/dist/startExpressServer.js
+++ b/node_modules/whatsapp-cloud-api/dist/startExpressServer.js
@@ -71,6 +71,12 @@ const startExpressServer = (options) => new Promise((resolve) => {
                     ...(rest.interactive.list_reply || rest.interactive.button_reply),
                 };
                 break;
+            case 'order':
+                event = type;
+                data = {
+                    order: rest.order,
+                };
+                break;
             default:
                 break;
         }

This issue body was partially generated by patch-package.

How to unregister (kill) a bot?

I am trying to accomplish a multibot configuration in which bots could be created and deleted dynamically. Is there a way to destroy a bot or even unregister its webhookPath?

Reduce bundle size

The current implementation adds unnecessary files & folders in build, such as images.
Need to make this leaner.

Add try-catch for failing tests

Some tests currently fail with no error and just throws an empty string, unless you use a try-catch statement. Need to fix this in the main code, but for now, can simply add a try-catch in the tests to know why the test failed.

e.g.
image

conversation.id

how can I get conversation.id to know if a message within the current 24 hour window or newly created window?

Update npm packages

It's been 6 months since the last publish. Need to update npm packages to ensure everything is up to date & there are no security vulnerabilities.

help with list_reply

Hello, I have a question when I send a list of options (list_reply) and the user enters an "account status" option and then enters the document number. How do I get that value going through the previous validations again?

image

Fix bugs

Talked to a user of the library (Mohssine) and we identified the following bugs:

  • Sometimes, webhook response sends html instead of plain text
  • In README, properly illustrate how to use a try-catch and wrap everything in an async block. Also show how to use require in case one cannot use import
  • Write a tutorial on how to start using the library from scratch for newbies who may not understand this well (how to get a phone number, subscribe to a webhook, etc)

Other bugs/nice features to have:

  • Export types for Bot and bot.on() interfaces
  • On the sendReplyButton & sendList functions, make the following optional:
    • options
    • options.footer
  • On the sendReplyButton & sendList functions, make id and title to have type string | number
  • Importing types for Bot requires installing @types/express locally when it shouldn't
  • Add sendText in addition to sendMessage

Download Media

I think this is a good project, But the fact that it doesn't have a direct function to handle media download, or get you media URL is weird . Try and add it or if there is one already then make it clear.

Read express port from environment

From #19

Add functionality to read the server port from process.env

Suggested change from #19:

// startExpressServer.js line 100

// change
const port = options?.port || 3000;

// to
const port = options?.port || process.env.PORT || 3000;

I can't send a list

I do not know how to do it

let sections = [{
            "title": "Afternoon slots",
            "rows": [{"id": "1",
                    "title": "13:00",
                    "description": "Collection: Crouch End Bakery"
                },
                {"id": "2",
                    "title": "13:30",
                    "description": "Collection: Hampstead Bakery"
                },
                {"id": "3",
                    "title": "13:00",
                    "description": "Collection: Hampstead Bakery"
                }
            ]
        },
        {
            "title": "Evening slots",
            "rows": [{"id": "4",
                    "title": "18:00",
                    "description": "Collection: Crouch End Bakery"
                },
                {"id": "5",
                    "title": "18:30",
                    "description": "Collection: Hampstead Bakery"
                },
                {"id": "6",
                    "title": "17:00",
                    "description": "Collection: Crouch End Bakery"
                }
            ]
        }
    ];
const result4 = await bot.sendList(to, 'asdf', 'fdsa', sections, '');

what is the mistake?

Contacts type message payload changing type when context is added

When receiving a forwarded "contacts" type message, Message "data" property that was supposed to be a Contact array, turns into an object filled with Contacts and a "context" property.

Adding context to message.data is causing this issue:

This can be solved by moving "context" property to Message. @tawn33y tell me if this is ok for you and I can do it and open a PR.

Thanks in advance.

Template buttons are not being handled

While it is now possible to handle responses to simple message buttons that we send directly to the user, the same is not possible if we send buttons as part of a WhatsApp template.
This is because templates with buttons have a different JSON structure and different types than a conventional message.

possible error when adding routes

Hi, I'm starting the following:

const { createBot } = require('whatsapp-cloud-api');
const axios = require('axios');
const express = require('express');

const app = express();

const from = process.env.WHATSAPP_PHONE_NUMBER_ID;
const token = process.env.WHATSAPP_TOKEN;
const webhookVerifyToken = process.env.VERIFY_TOKEN;

// Create a bot that can send messages
const bot = createBot(from, token);
(async function() {
await bot.startExpressServer({
webhookVerifyToken: process.env.VERIFY_TOKEN,
port: process.env.PORT || 2040,
webhookPath: /webhook,
});
})();

app.get('/hello', (req, res) => {
res.send('Hello World!')
})
but when consulting the /hello route it gives me 404 as a result, is there an error with the route handler? thank you

how to pass variables

for(let i=0;i<2;i++){
const result = await bot.sendTemplate(to[i], 'charge_alert', 'en_US', [
{
"type": "body",
"parameters": [{
"type": "text",
"text": {me}
},
{
"type": "text",
"text": {friend}
},
{
"type": "text",
"text": {msg}

                    }
             ]
   }
])

}

} catch (err) {
console.log(err);
}

#All works fine except when variable are passed it does't work

receive image and save it

Hello everyone, is it possible to save an image that the user sends?

data: {
mime_type: 'image/jpeg',
sha256: '7P5nRXlN6yADTMqgDrqzycfaNWsddEQHOtHGN6hZxM4=',
id: '5512756692174447'
}

I don't understand how to do it
thanks

sendText - Generic error step 2 tutorial

Hello, i just followed the tutorial steps and i have this response after step 2

{
error: {
message: '(#131000) Generic error',
type: 'OAuthException',
code: 131000,
error_data: { messaging_product: 'whatsapp', details: 'Generic error' },
error_subcode: 2494002,
fbtrace_id: 'AC1dtC9ImEGwkBiss2Xt0mA'
}
}

I could make it work using Postman.

Run CI tests on new PR

Currently, CI tests are not run on PRs, especially those coming from external branches. Need to fix this.

Create tests for sender name property

@guskuma recently added a name property for the sender of a message received via webhook. See PR here: #25

Need to:

  • Create tests for this
  • Fix eslint errors disabled by no-eslint-next-line
  • Make the property compulsory, instead of optional, but default it to undefined

How to use this module, to handle different WhatsApp cloud API tokens from different users at the same time.

Greetings,

My MERN SAAS app where I'm using this module on the backend nodejs is getting completed. But now I'm not sure on how the backend nodejs is going to handle multiple users with different [ phone numbers, WhatsApp API tokens, etc. ]. are going to use the SAAS platform, Because the module needs the tokens at start-up time and keeps them in memory throughout.

What architecture approach do you suggest that I can use to easily handle this situation? any suggestions will be highly appreciated.

Getting error on sending template

Hi

I have created a template and it is active.

I am sending template as below :
bot.sendTemplate(
mobileNumber,
"cg_quest_text",
"en_US",
[
{
"type": "body",
"parameters": [{
"type": "text",
"text": "a) Career Counselling"
}]
}
])

I get error as below from meta :
message: '(#132001) Template name does not exist in the translation',
type: 'OAuthException',
code: 132001,
error_data: {
messaging_product: 'whatsapp',
details: 'template name (cg_quest_text) does not exist in en_US'
},
fbtrace_id: 'A5Ld_C-mecwFT9GzebFGU0m'
}

Please guide as to what could be the issue.

Create status events

When 'statuses' field is not null a new event should be triggered with statuses payload. Currently it's being ignored.
It can be done adding new 'statuses' event to PubSubEvents.

How to check if a business has initiated a conversation with a client?

In WhatsApp Business API, you cannot send a freeform text message to a client UNLESS you have initiated a conversation.

Initiating a conversation with a client requires either the user to contact the business account first or the business to send a templated pre-approved templated message to the user.

Now the question is, how do I check if a conversation has been initialized with a certain phone number? If I recall correctly, the websocket payload provides an extra information whether or not the message has been sent to an established conversation.

ERR_UNHANDLED_REJECTION when trying to receive the Message from the bot

Hi Everyone, currently Im trying to receive "Received your text message!" that the bot sends when receives a message.

Right now I'm receiving start message when I start the server. But when trying to send the message, I got this output.

image

Right now My code is this ->

`
const { createBot } = require('whatsapp-cloud-api');

(async () => {
try {
// replace the values below
const from = "";
const token = "";
const to = "";
// const to = "524426581099";
const webhookVerifyToken = "";

// Create a bot that can send messages
const bot = createBot(from, token);

// Send text message
const result = await bot.sendText(to, "Hello world");

// Start express server to listen for incoming messages
// NOTE: See below under `Documentation/Tutorial` to learn how
// you can verify the webhook URL and make the server publicly available
await bot.startExpressServer({
  webhookVerifyToken,
});

// Listen to ALL incoming messages
// NOTE: remember to always run: await bot.startExpressServer() first
bot.on("message", async (msg) => {
  console.log(msg);

  if (msg.type === "text") {
    await bot.sendText(msg.from, "Received your text message!");
  } else if (msg.type === "image") {
    await bot.sendText(msg.from, "Received your image!");
  }
});

} catch (err) {
console.log(err);
}
})();
`

Does not output error on invalid phone number ID

in this simple app:

import { createBot } from "whatsapp-cloud-api";

const WA_PHONE_NUMBER_ID = process.env.WA_PHONE_NUMBER_ID;
const WA_CLOUD_API_ACCESS_TOKEN = process.env.WA_CLOUD_API_ACCESS_TOKEN;
const WA_WEBHOOK_TOKEN = process.env.WA_WEBHOOK_TOKEN;

if (!WA_PHONE_NUMBER_ID) {
	throw new Error("Invalid WA_PHONE_NUMBER_ID");
}

if (!WA_CLOUD_API_ACCESS_TOKEN) {
	throw new Error("Invalid WA_CLOUD_API_ACCESS_TOKEN");
}

if (!WA_WEBHOOK_TOKEN) {
	throw new Error("Invalid WA_WEBHOOK_TOKEN");
}

const wa = createBot(WA_PHONE_NUMBER_ID, WA_CLOUD_API_ACCESS_TOKEN);

wa.startExpressServer({ webhookPath: "/whatsapp/webhook", webhookVerifyToken: WA_WEBHOOK_TOKEN })
	.then(() => console.log("WhatsApp Bot started"))
	.catch((error) => console.error(error));

wa.on("text", async (message) => {
	console.log(`Got message: ${message.data.text}`);
	await wa.sendText(message.from, "Got message.");
});

using export WA_PHONE_NUMBER_ID="111111111111111" (valid ID used in app, hidden here), the application runs without errors and successfully sends messages.
however, with export WA_PHONE_NUMBER_ID="111111111111111\n" (notice extra \n, could be any invalid ID), no error is output but the application fails to send messages.

an error output is expected. if i make a curl request:

>  curl -X POST "https://graph.facebook.com/v17.0/${WA_PHONE_NUMBER_ID}/messages" \
-H "Authorization: Bearer $WA_CLOUD_API_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"messaging_product": "whatsapp", "recipient_type": "individual", "to": "xxxxxxxxxxxx", "type": "text", "text": {"body": "Hello"}}'

i get a 400 error with the following message:

{
  "error": {
    "message": "Unsupported post request. Object with ID '111111111111111\\n' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https://developers.facebook.com/docs/graph-api",
    "type": "GraphMethodException",
    "code": 100,
    "error_subcode": 33,
    "fbtrace_id": "xxxxxx"
  }
}

Not able to connect to webhook

I followed all the steps in the tutorial section and I was able to send messages, but my webhook endpoint was not verified. I've tried many ways, and the only way that works is using Glitch, as recommended in the API documentation. Do you know any solution that could solve my issue?

I seem to always get 2 failed tests when running `npm t`

Hello, I tried to clone this repository to work on adding the status feature.
But i cannot seem to get npm t running.

This is the result

 FAIL  dist/createBot.test.js (37.59 s)
  send functions
    √ sends text (628 ms)
    √ sends message (502 ms)
    √ sends image (580 ms)
    √ sends document (553 ms)
    √ sends audio (610 ms)
    √ sends video (522 ms)
    √ sends sticker (654 ms)
    √ sends location (517 ms)
    √ sends template (589 ms)
    √ sends contacts (508 ms)
    √ sends reply button (612 ms)
    √ sends list (540 ms)
  server functions
    √ invalid webhook token (37 ms)
    × verify webhook token (3 ms)
    √ send invalid body (16 ms)
    × listen for new messages (30014 ms)

  ● server functions › verify webhook token

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    connect ECONNREFUSED 127.0.0.1:80



  ● server functions › listen for new messages

    thrown: "Exceeded timeout of 30000 ms for a test.
    Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."

      265 |
      266 |   // eslint-disable-next-line no-async-promise-executor
    > 267 |   test('listen for new messages', (): Promise<void> => new Promise(async (resolve, reject) => {
          |   ^
      268 |     const payloads = [
      269 |       {
      270 |         from: '12345678',

      at src/createBot.test.ts:267:3
      at Object.<anonymous> (src/createBot.test.ts:194:1)

Test Suites: 1 failed, 1 total
Tests:       2 failed, 14 passed, 16 total
Snapshots:   0 total
Time:        37.783 s, estimated 39 s
Ran all test suites matching /dist/i.

I tried using various webhook_path but it seem to keep getting 403 (forbidden)
Here is my webhook path

WEBHOOK_PATH="http://localhost:3000/webhook/whatsapp"

I also tried manually using rest api

GET http://localhost:3000/webhook/whatsapp?hub.mode=subscribe&hub.challenge=random&hub.verify_token=41ESBjuy5E

But gets 403 Forbidden instead.

I am running on Windows 10.

I also want to know what WEBHOOK_PATH do you use?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.