Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

And in the seventh day, he said, "may there be parity between the branches" #45

Merged
merged 13 commits into from
Apr 19, 2024
Merged
60 changes: 32 additions & 28 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages

name: Node.js Package
name: Publish to NPM

on:
release:
types: [created]
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci
- run: npm test
test:
runs-on: ubuntu-latest
environment: Staging Environment
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm i
- run: npm test
env:
accessToken: ${{secrets.ACCESSTOKEN}}
graphAPIVersion: ${{secrets.GRAPHAPIVERSION}}
senderPhoneNumberId: ${{secrets.SENDERPHONENUMBERID}}
WABA_ID: ${{secrets.WABA_ID}}

publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
publish-npm:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
registry-url: https://registry.npmjs.org/
- run: npm i
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN_IN_GITHUB_ACTIONS}}
192 changes: 152 additions & 40 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@
const unirest = require('unirest');
const signale = require('signale');
const fs = require('fs');
const request = require('request');
const messageParser = require('./msg_parser.js');
const { Readable } = require('stream');

// check if file exists
const fileExists = ({ path }) => {
try {
fs.accessSync(path, fs.constants.F_OK);
return true;
} catch (err) {
return false;
}
};

class WhatsappCloud {
constructor({
Expand Down Expand Up @@ -146,33 +158,6 @@ class WhatsappCloud {
}
};

this._uploadMedia = async ({ file_path, file_name }) => {
return new Promise((resolve, reject) => {
const mediaFile = fs.createReadStream(file_path);
// type = type || 'image';
unirest(
'POST',
`https://graph.facebook.com/${this.graphAPIVersion}/${this.senderPhoneNumberId}/media`
)
.headers({
Authorization: `Bearer ${this.accessToken}`,
})
.field('messaging_product', 'whatsapp')
.attach('file', mediaFile)
.end((res) => {
if (res.error) {
reject(res.error);
} else {
let response = JSON.parse(res.raw_body);
resolve({
status: 'success',
media_id: response.id,
file_name: file_name || null,
});
}
});
});
};
this._retrieveMediaUrl = async ({ media_id }) => {
const response = await this._fetchAssistant({
baseUrl: `https://graph.facebook.com/${this.graphAPIVersion}`,
Expand Down Expand Up @@ -512,17 +497,42 @@ class WhatsappCloud {
return response;
}

async sendImage({ recipientPhone, caption, file_path, file_name, url }) {
async sendImage({
recipientPhone,
caption,
file_path,
file_name,
url,
media_id,
mime_type,
}) {
this._mustHaverecipientPhone(recipientPhone);
if (file_path && url) {
const hasFileAndUrl = file_path && url;
const hasMediaIdAndFile = media_id && file_path;
const hasMediaIdAndUrl = media_id && url;
const lacksAll = !file_path && !url && !media_id;

if (hasFileAndUrl) {
throw new Error(
'You can only send an image in your "file_path" or an image in a publicly available "url". Provide either "file_path" or "url".'
);
}

if (!file_path && !url) {
if (hasMediaIdAndFile) {
throw new Error(
'You can only send an image using a media_id or a file_path. Provide either "media_id" or "file_path".'
);
}

if (hasMediaIdAndUrl) {
throw new Error(
'You must send an image in your "file_path" or an image in a publicly available "url". Provide either "file_path" or "url".'
'You can only send an image using a media_id or a url. Provide either "media_id" or "url".'
);
}

if (lacksAll) {
throw new Error(
'You must have either an image in your "file_path" or an image in a publicly available "url", or a previously uploaded "media_id". Provide either "file_path" or "url" or "media_id".'
);
}

Expand All @@ -536,12 +546,15 @@ class WhatsappCloud {
},
};

if (file_path) {
let uploadedFile = await this._uploadMedia({
if (media_id) {
body['image']['id'] = media_id;
} else if (file_path) {
let uploadedFile = await this.preUploadMedia({
file_path,
file_name,
mime_type,
});
body['image']['id'] = uploadedFile.media_id;
body['image']['id'] = Number(uploadedFile.media_id);
} else {
body['image']['link'] = url;
}
Expand All @@ -557,7 +570,15 @@ class WhatsappCloud {
body,
};
}
async sendVideo({ recipientPhone, caption, file_path, file_name, url }) {

async sendVideo({
recipientPhone,
caption,
file_path,
file_name,
url,
mime_type,
}) {
this._mustHaverecipientPhone(recipientPhone);
if (file_path && url) {
throw new Error(
Expand All @@ -581,9 +602,10 @@ class WhatsappCloud {
},
};
if (file_path) {
let uploadedFile = await this._uploadMedia({
let uploadedFile = await this.preUploadMedia({
file_path,
file_name,
mime_type,
});
body['video']['id'] = uploadedFile.media_id;
} else {
Expand All @@ -602,7 +624,14 @@ class WhatsappCloud {
};
}

async sendAudio({ recipientPhone, caption, file_path, file_name, url }) {
async sendAudio({
recipientPhone,
caption,
file_path,
file_name,
url,
mime_type,
}) {
this._mustHaverecipientPhone(recipientPhone);
if (file_path && url) {
throw new Error(
Expand All @@ -624,9 +653,10 @@ class WhatsappCloud {
audio: {},
};
if (file_path) {
let uploadedFile = await this._uploadMedia({
let uploadedFile = await this.preUploadMedia({
file_path,
file_name,
mime_type,
});
body['audio']['id'] = uploadedFile.media_id;
} else {
Expand All @@ -645,7 +675,7 @@ class WhatsappCloud {
};
}

async sendDocument({ recipientPhone, caption, file_path, url }) {
async sendDocument({ recipientPhone, caption, file_path, url, mime_type }) {
this._mustHaverecipientPhone(recipientPhone);
if (file_path && url) {
throw new Error(
Expand Down Expand Up @@ -674,9 +704,10 @@ class WhatsappCloud {
};

if (file_path) {
let uploadedFile = await this._uploadMedia({
let uploadedFile = await this.preUploadMedia({
file_path,
file_name: caption,
mime_type,
});
body['document']['id'] = uploadedFile.media_id;
body['document']['filename'] = uploadedFile.file_name || '';
Expand Down Expand Up @@ -921,6 +952,87 @@ class WhatsappCloud {

async getUserStatusPicture({ recipientPhone }) {}

async preUploadMedia({ file_path, file_name, file_buffer, mime_type }) {
return new Promise((resolve, reject) => {
let fileStream;

if (!file_path && !file_buffer) {
return reject({
status: 'failed',
error: 'You must provide either a file_path or a file_buffer.',
});
}

if (file_path) {
if (!fileExists({ path: file_path })) {
return reject({
status: 'failed',
error: 'The file_path does not exist.',
});
} else {
fileStream = fs.createReadStream(file_path);
}
}

if (file_buffer) {
if (!Buffer.isBuffer(file_buffer)) {
return reject({
status: 'failed',
error: 'The file_buffer is not a buffer.',
});
} else {
// Convert buffer to a readable stream
fileStream = new Readable();
fileStream.push(file_buffer);
fileStream.push(null); // Signal the end of the stream
}
}

if (!mime_type) {
throw new Error('You must provide a "mime_type".');
}

const url = `https://graph.facebook.com/${this.graphAPIVersion}/${this.senderPhoneNumberId}/media`;

const options = {
method: 'POST',
url,
headers: {
Authorization: `Bearer ${this.accessToken}`,
},
formData: {
messaging_product: 'whatsapp',
type: mime_type,
file: {
value: fileStream,
options: {
filename: file_name,
contentType: null,
},
},
},
};

request(options, function (error, response) {
if (error) {
reject({
status: 'failed',
error,
});
} else {
const data = JSON.parse(response.body);
const media_id = data.id;

resolve({
status: 'success',
media_id,
file_name: file_name || null,
});
}
});
});
}

parseMessage(requestBody) {
return messageParser({ requestBody, currentWABA_ID: this.WABA_ID });
}
Expand Down
2 changes: 2 additions & 0 deletions msg_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ module.exports = ({ requestBody, currentWABA_ID }) => {
msgType = 'ad_message';
} else if (message.type === 'text') {
msgType = 'text_message';
} else if (message.type === 'audio') {
msgType = 'audio_message';
} else if (message.type === 'sticker') {
msgType = 'sticker_message';
} else if (message.type === 'image') {
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
{
"name": "whatsappcloudapi_wrapper",
"version": "1.0.14",
"version": "1.0.15",
"description": "Tired of doing things wrong? This package provides you with an easy way to get started and start building on the Whatsapp Cloud API. This is an unofficial wrapper for the Whatsapp Cloud API.",
"main": "index.js",
"author": "Daggie Blanqx",
"license": "MIT",
"scripts": {
"prettier": "prettier --ignore-path .prettierignore --config prettier.config.js --write .",
"quickpush": "cls && git status && git add * && git commit * -m \"Updated Code\" && git push",
"test": "echo \"Error: no test specified\" && exit 0",
"test": "PACKAGE_VERSION=$npm_package_version echo \"Error: no test specified\" && exit 0",
"npmpublish": "npm run prettier && np"
},
"dependencies": {
"request": "^2.88.2",
"signale": "^1.4.0",
"unirest": "^0.6.0"
},
Expand Down
Loading