Skip to content

Commit

Permalink
Merge pull request #41 from gitbot-x/main
Browse files Browse the repository at this point in the history
Fixes #12 and #6
  • Loading branch information
gitbot-x authored Apr 19, 2024
2 parents 2db9983 + cf8d861 commit 041bec7
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 69 deletions.
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
"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

0 comments on commit 041bec7

Please sign in to comment.