Skip to content

Commit

Permalink
feat: first version, still on testing phase, may not work at all
Browse files Browse the repository at this point in the history
  • Loading branch information
j05u3 committed Aug 20, 2023
1 parent a7c426f commit 03f2b85
Show file tree
Hide file tree
Showing 10 changed files with 3,227 additions and 752 deletions.
3,074 changes: 2,325 additions & 749 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"url": "https://github.com/j05u3"
},
"engines": {
"node": ">=12.0"
"node": ">=18.0"
},
"keywords": [
"boilerplate",
Expand All @@ -45,11 +45,20 @@
"url": "https://github.com/j05u3/whatsapp-cloud-api-express/issues"
},
"homepage": "https://github.com/j05u3/whatsapp-cloud-api-express#readme",
"peerDependencies": {
"express": "^4.17.0"
},
"dependencies": {
"axios": "^0.27.2",
"validator": "^13.11.0"
},
"devDependencies": {
"@ryansonshine/commitizen": "^4.2.8",
"@ryansonshine/cz-conventional-changelog": "^3.3.4",
"@types/express": "^4.17.17",
"@types/jest": "^27.5.2",
"@types/node": "^12.20.11",
"@types/validator": "^13.11.1",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"conventional-changelog-conventionalcommits": "^5.0.0",
Expand Down Expand Up @@ -119,4 +128,4 @@
"@semantic-release/github"
]
}
}
}
175 changes: 175 additions & 0 deletions src/createBot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import isURL from 'validator/lib/isURL';
import { ICreateBot } from './createBot.types';
import {
MediaBase,
TextMessage,
MediaMessage,
LocationMessage,
TemplateMessage,
ContactMessage,
InteractiveMessage,
} from './messages.types';
import { sendRequestHelper } from './sendRequestHelper';

interface PayloadBase {
messaging_product: 'whatsapp';
recipient_type: 'individual';
}

const payloadBase: PayloadBase = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
};

export const createBot: ICreateBot = (
fromPhoneNumberId,
accessToken,
responseLogger
) => {
const sendRequest = sendRequestHelper(
fromPhoneNumberId,
accessToken,
responseLogger
);

const getMediaPayload = (urlOrObjectId: string, options?: MediaBase) => ({
...(isURL(urlOrObjectId) ? { link: urlOrObjectId } : { id: urlOrObjectId }),
caption: options?.caption,
filename: options?.filename,
});

return {
sendText: (to, text, options) =>
sendRequest<TextMessage>({
...payloadBase,
to,
type: 'text',
text: {
body: text,
preview_url: options?.preview_url,
},
}),
sendMessage(to, text, options) {
return this.sendText(to, text, options);
},
sendImage: (to, urlOrObjectId, options) =>
sendRequest<MediaMessage>({
...payloadBase,
to,
type: 'image',
image: getMediaPayload(urlOrObjectId, options),
}),
sendDocument: (to, urlOrObjectId, options) =>
sendRequest<MediaMessage>({
...payloadBase,
to,
type: 'document',
document: getMediaPayload(urlOrObjectId, options),
}),
sendAudio: (to, urlOrObjectId) =>
sendRequest<MediaMessage>({
...payloadBase,
to,
type: 'audio',
audio: getMediaPayload(urlOrObjectId),
}),
sendVideo: (to, urlOrObjectId, options) =>
sendRequest<MediaMessage>({
...payloadBase,
to,
type: 'video',
video: getMediaPayload(urlOrObjectId, options),
}),
sendSticker: (to, urlOrObjectId) =>
sendRequest<MediaMessage>({
...payloadBase,
to,
type: 'sticker',
sticker: getMediaPayload(urlOrObjectId),
}),
sendLocation: (to, latitude, longitude, options) =>
sendRequest<LocationMessage>({
...payloadBase,
to,
type: 'location',
location: {
latitude,
longitude,
name: options?.name,
address: options?.address,
},
}),
sendTemplate: (to, name, languageCode, components) =>
sendRequest<TemplateMessage>({
...payloadBase,
to,
type: 'template',
template: {
name,
language: {
code: languageCode,
},
components,
},
}),
sendContacts: (to, contacts) =>
sendRequest<ContactMessage>({
...payloadBase,
to,
type: 'contacts',
contacts,
}),
sendReplyButtons: (to, bodyText, buttons, options) =>
sendRequest<InteractiveMessage>({
...payloadBase,
to,
type: 'interactive',
interactive: {
body: {
text: bodyText,
},
...(options?.footerText
? {
footer: { text: options?.footerText },
}
: {}),
header: options?.header,
type: 'button',
action: {
buttons: Object.entries(buttons).map(([key, value]) => ({
type: 'reply',
reply: {
title: value,
id: key,
},
})),
},
},
}),
sendList: (to, buttonName, bodyText, sections, options) =>
sendRequest<InteractiveMessage>({
...payloadBase,
to,
type: 'interactive',
interactive: {
body: {
text: bodyText,
},
...(options?.footerText
? {
footer: { text: options?.footerText },
}
: {}),
header: options?.header,
type: 'list',
action: {
button: buttonName,
sections: Object.entries(sections).map(([key, value]) => ({
title: key,
rows: value,
})),
},
},
}),
};
};
133 changes: 133 additions & 0 deletions src/createBot.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {
Contact,
InteractiveHeader,
TemplateComponent,
} from './messages.types';
import { SendMessageResult } from './sendRequestHelper';
import { FreeFormObject } from './utils/misc';

export interface Message {
from: string;
name: string | undefined;
id: string;
timestamp: string;
type: string;
data: FreeFormObject; // TODO: properly define interfaces for each type
to_phone_number_id: string;
}

export interface Status extends StatusReceived {
to_phone_number_id: string;
}

export interface StatusReceived {
timestamp: string; // in seconds
status: string; // 'failed', 'delivered', 'read'
recipient_id: string; // phone number
id: string; // id of the sent message which this status is for
errors: StatusError[] | undefined; // array of errors
// TODO: add the rest of the fields
}

export interface StatusError {
code: number;
title: string | undefined;
error_data:
| {
details: string | undefined;
}
| undefined;
}

export interface Bot {
sendText: (
to: string,
text: string,
options?: {
preview_url?: boolean;
}
) => Promise<SendMessageResult>;
sendMessage: (
to: string,
text: string,
options?: {
preview_url?: boolean;
}
) => Promise<SendMessageResult>;
sendImage: (
to: string,
urlOrObjectId: string,
options?: {
caption?: string;
}
) => Promise<SendMessageResult>;
sendDocument: (
to: string,
urlOrObjectId: string,
options?: {
caption?: string;
filename?: string;
}
) => Promise<SendMessageResult>;
sendAudio: (to: string, urlOrObjectId: string) => Promise<SendMessageResult>;
sendVideo: (
to: string,
urlOrObjectId: string,
options?: {
caption?: string;
}
) => Promise<SendMessageResult>;
sendSticker: (
to: string,
urlOrObjectId: string
) => Promise<SendMessageResult>;
sendLocation: (
to: string,
latitude: number,
longitude: number,
options?: {
name?: string;
address?: string;
}
) => Promise<SendMessageResult>;
sendTemplate: (
to: string,
name: string,
languageCode: string,
components?: TemplateComponent[]
) => Promise<SendMessageResult>;
sendContacts: (to: string, contacts: Contact[]) => Promise<SendMessageResult>;
sendReplyButtons: (
to: string,
bodyText: string,
buttons: {
[id: string]: string | number;
},
options?: {
footerText?: string;
header?: InteractiveHeader;
}
) => Promise<SendMessageResult>;
sendList: (
to: string,
buttonName: string,
bodyText: string,
sections: {
[sectionTitle: string]: {
id: string | number;
title: string | number;
description?: string;
}[];
},
options?: {
footerText?: string;
header?: InteractiveHeader;
}
) => Promise<SendMessageResult>;
}

export type ICreateBot = (
fromPhoneNumberId: string,
accessToken: string,
responseLogger?: (obj: any) => Promise<void>
) => Bot;
Loading

0 comments on commit 03f2b85

Please sign in to comment.