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

QR catching #223

Merged
merged 12 commits into from
Apr 4, 2020
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules
dist
session
session-*
assets
app.js
gohan.jpg
Expand Down
121 changes: 98 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@
![GitHub last commit](https://img.shields.io/github/last-commit/danielcardeenas/sulla)
[![GitHub license](https://img.shields.io/github/license/danielcardeenas/sulla)](https://github.com/danielcardeenas/sulla/blob/master/LICENSE)

> Sulla is a javascript library which provides a high-level API control to Whatsapp so it can be configured to automatize resposes or any data that goes through Whatsapp effortlessly.
> Sulla is a javascript library which provides a high-level API control to
> Whatsapp so it can be configured to automatize resposes or any data that goes
> trough Whatsapp effortlessly.
>
> It is built using [puppeteer](https://github.com/GoogleChrome/puppeteer) and based on [this python wrapper](https://github.com/mukulhase/WebWhatsapp-Wrapper)
> It is built using [puppeteer](https://github.com/GoogleChrome/puppeteer) and
> based on
> [this python wrapper](https://github.com/mukulhase/WebWhatsapp-Wrapper)
>
> By deafult sulla will try to use Google Chrome driver if installed, if not, it will use integrated Chromium instance
> By deafult sulla will try to use Google Chrome driver if installed, if not, it
> will use integrated Chromium instance

## Installation

Expand All @@ -30,10 +35,10 @@
// import { create, Whatsapp } from 'sulla';
const sulla = require('sulla');

sulla.create().then((client) => start(client));
sulla.create().then(client => start(client));

function start(client) {
client.onMessage((message) => {
client.onMessage(message => {
if (message.body === 'Hi') {
client.sendText(message.from, '👋 Hello from sulla!');
}
Expand All @@ -56,21 +61,56 @@ sulla.create('sales').then((salesBot) => {...});
// Init support whatsapp bot
sulla.create('support').then((supportBot) => {...});
```

<br>

## Exporting QR code

By default QR code will appear on the terminal. If you need to pass the QR
somewhere else heres how:

```javascript
const fs = require('fs');

// Second create() parameter is the QR callback
sulla.create('session-marketing', qrCode => {
exportQR(qrCode, 'marketing-qr.png');
});

// Writes QR in specified path
function exportQR(qrCode, path) {
qrCode = qrCode.replace('data:image/png;base64,', '');
const imageBuffer = Buffer.from(qrCode, 'base64');

// Creates 'marketing-qr.png' file
fs.writeFileSync(path, imageBuffer);
}
```

## Basic functions (usage)
Not every available function is listed, for further look, every function available can be found in [here](/src/api/layers).

Not every available function is listed, for further look, every function
available can be found in [here](/src/api/layers).

### Chatting

```javascript
// Send basic text
await client.sendText(chatId, '👋 Hello from sulla!');

// Send image
await client.sendImage(chatId, 'path/to/img.jpg', 'something.jpg', 'Caption text');
await client.sendImage(
chatId,
'path/to/img.jpg',
'image-name.jpg',
'Caption text'
);

// Send @tagged message
await client.sendMentioned(chatId, 'Hello @5218113130740 and @5218243160777!', ['5218113130740', '5218243160777']);
await client.sendMentioned(chatId, 'Hello @5218113130740 and @5218243160777!', [
'5218113130740',
'5218243160777'
]);

// Reply to a message
await client.reply(chatId, 'This is a reply!', message.id.toString());
Expand All @@ -79,7 +119,12 @@ await client.reply(chatId, 'This is a reply!', message.id.toString());
await client.sendFile(chatId, 'path/to/file.pdf', 'cv.pdf', 'Curriculum');

// Send gif
await client.sendVideoAsGif(chatId, 'path/to/video.mp4', 'video.gif', 'Gif image file');
await client.sendVideoAsGif(
chatId,
'path/to/video.mp4',
'video.gif',
'Gif image file'
);

// Send contact
// contactId: [email protected]
Expand All @@ -92,7 +137,13 @@ await client.forwardMessages(chatId, [message.id.toString()], true);
await client.sendImageAsSticker(chatId, 'path/to/image.jpg');

// Send location
await client.sendLocation(chatId, 25.6801987, -100.4060626, 'Some address, Washington DC', 'Subtitle');
await client.sendLocation(
chatId,
25.6801987,
-100.4060626,
'Some address, Washington DC',
'Subtitle'
);

// Send seen ✔️✔️
await client.sendSeen(chatId);
Expand All @@ -104,11 +155,11 @@ await client.startTyping(chatId);
await client.stopTyping(chatId);

// Set chat state (0: Typing, 1: Recording, 2: Paused)
await client.setChatState(chatId, 0 | 1 | 2)

await client.setChatState(chatId, 0 | 1 | 2);
```

### Group functions

```javascript
// groupId or chatId: leaveGroup [email protected]

Expand Down Expand Up @@ -141,10 +192,10 @@ await client.demoteParticipant(groupId, '[email protected]');

// Get group admins
await client.getGroupAdmins(groupId);

```

### Profile functions

```javascript
// Set client status
await client.setProfileStatus('On vacations! ✈️');
Expand All @@ -154,6 +205,7 @@ await client.setProfileName('Sulla bot');
```

### Device functions

```javascript
// Get device info
await client.getHostDevice();
Expand All @@ -172,6 +224,7 @@ await client.getWAVersion();
```

### Events

```javascript
// Listen to messages
client.onMessage(message => {
Expand Down Expand Up @@ -207,6 +260,7 @@ client.onAddedToGroup(chatEvent => {
```

### Other

```javascript
// Delete chat
await client.deleteChat(chatId);
Expand All @@ -215,42 +269,51 @@ await client.deleteChat(chatId);
await client.clearChat(chatId);

// Delete message (last parameter: delete only locally)
await client.deleteMessage(chatId, message.id.toString(), false)
await client.deleteMessage(chatId, message.id.toString(), false);
```

## Misc

There are some tricks for a better usage of sulla.

#### Keep session alive:

```javascript
// In case of being logged out of whatsapp web
// Force it to keep the current session
client.onStateChange((state) => {
client.onStateChange(state => {
if (state === 'UNLAUNCHED') {
client.useHere();
}
});
```

#### Send message to new contacts (non-added)

Also see [Whatsapp links](https://faq.whatsapp.com/en/26000030/)

```javascript
await client.sendMessageToId('[email protected]', 'Hello from sulla! 👋')
await client.sendMessageToId('[email protected]', 'Hello from sulla! 👋');
```

#### Multiple sessions
If you need to run multiple sessions at once just pass a session name to `create()` method.

If you need to run multiple sessions at once just pass a session name to
`create()` method.

```javascript
async () => {
const marketingClient = await sulla.create('marketing');
const salesClient = await sulla.create('sales');
const supportClient = await sulla.create('support');
}
};
```

#### Closing (saving) sessions
Close the session properly to ensure the session is saved for the next time you log in (So it wont ask for QR scan again).
So instead of CTRL+C,

Close the session properly to ensure the session is saved for the next time you
log in (So it wont ask for QR scan again). So instead of CTRL+C,

```javascript
// Catch ctrl+C
process.on('SIGINT', function() {
Expand All @@ -266,31 +329,43 @@ try {
```

## Development

Building sulla is really simple altough it contians 3 main projects inside
1. Wapi project

1. Wapi project

```bash
> npm run build:wapi
```

2. Middleeware

```bash
> npm run build:build:middleware
> npm run build:jsQR
```

3. Sulla

```bash
> npm run build:sulla
```

To build the entire project just run

```bash
> npm run build
```

## Maintainers
Maintainers are needed, I cannot keep with all the updates by myself. If you are interested please open a Pull Request.

Maintainers are needed, I cannot keep with all the updates by myself. If you are
interested please open a Pull Request.

## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Pull requests are welcome. For major changes, please open an issue first to
discuss what you would like to change.

## License

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sulla",
"version": "2.1.0",
"version": "2.1.1",
"description": "Javascript whatsapp framework",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
9 changes: 7 additions & 2 deletions src/api/layers/sender.layer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Page } from 'puppeteer';
import * as sharp from 'sharp';
import { base64MimeType, fileToBase64 } from '../helpers';
import { Message } from '../model';
import { Chat, Message } from '../model';
import { ChatState } from '../model/enum';
import { ListenerLayer } from './listener.layer';

declare module WAPI {
const sendSeen: (to: string) => void;
const getChat: (contactId: string) => Chat;
const startTyping: (to: string) => void;
const stopTyping: (to: string) => void;
const sendMessage: (to: string, content: string) => string;
Expand Down Expand Up @@ -76,7 +77,11 @@ export class SenderLayer extends ListenerLayer {
return this.page.evaluate(
({ to, content }) => {
WAPI.sendSeen(to);
return WAPI.sendMessage(to, content);
if (!WAPI.getChat(to)) {
return WAPI.sendMessageToID(to, content);
} else {
return WAPI.sendMessage(to, content);
}
},
{ to, content }
);
Expand Down
20 changes: 11 additions & 9 deletions src/controllers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const needsToScan = (waPage: puppeteer.Page) => {
return from(
waPage
.waitForSelector('body > div > div > .landing-wrapper', {
timeout: 0
timeout: 0,
})
.then(() => false)
);
Expand All @@ -35,28 +35,30 @@ export const isInsideChat = (waPage: puppeteer.Page) => {
!!document.getElementsByClassName('app')[0].attributes.tabindex
`,
{
timeout: 0
timeout: 0,
}
)
.then(() => true)
);
};

export async function retrieveQR(page: puppeteer.Page) {
const code = await decodeQR(page);
const { code, data } = await decodeQR(page);
qrcode.generate(code, {
small: true
small: true,
});

return true;
return { code, data };
}

async function decodeQR(page: puppeteer.Page): Promise<string> {
async function decodeQR(
page: puppeteer.Page
): Promise<{ code: string; data: string }> {
await page.waitForSelector('canvas', { timeout: 0 });
await page.addScriptTag({
path: require.resolve(path.join(__dirname, '../lib/jsQR', 'jsQR.js'))
path: require.resolve(path.join(__dirname, '../lib/jsQR', 'jsQR.js')),
});

return await page.evaluate(() => {
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
Expand All @@ -68,6 +70,6 @@ async function decodeQR(page: puppeteer.Page): Promise<string> {
canvas.height
);

return code.data;
return { code: code.data, data: canvas.toDataURL() };
});
}
5 changes: 1 addition & 4 deletions src/controllers/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ async function initBrowser(
// headless: true,
headless: options.headless,
devtools: options.devtools,
userDataDir: path.join(
process.cwd(),
`session${session && session.trim() !== '' ? '-' + session.trim() : ''}`
),
userDataDir: path.join(process.cwd(), session),
args: [...puppeteerConfig.chroniumArgs],
...extras,
});
Expand Down
Loading