Initial simple client implementation

This commit is contained in:
Pedro Lopez
2019-02-17 00:03:07 -04:00
parent 372187d275
commit bcbd16f196
8 changed files with 538 additions and 0 deletions

54
src/client/Client.js Normal file
View File

@@ -0,0 +1,54 @@
'use strict';
const EventEmitter = require('events');
const puppeteer = require('puppeteer');
const Util = require('../util/Util');
const { WhatsWebURL, UserAgent, DefaultOptions, Events } = require('../util/Constants');
const { ExposeStore } = require('../util/Injected');
/**
* Starting point for interacting with the WhatsApp Web API
* @extends {EventEmitter}
*/
class Client extends EventEmitter {
constructor(options = {}) {
super();
this.options = Util.mergeDefault(DefaultOptions, options);
this.pupBrowser = null;
this.pupPage = null;
}
async initialize() {
const browser = await puppeteer.launch(this.options.puppeteer);
const page = await browser.newPage();
page.setUserAgent(UserAgent);
await page.goto(WhatsWebURL);
await page.evaluate(ExposeStore);
await page.waitForSelector('._1jjYO'); // Wait for QR Code
const qr = await page.$eval('._2EZ_m', node => node.getAttribute('data-ref'));
this.emit(Events.QR_RECEIVED, qr);
// Wait for Auth
await page.waitForSelector('._2Uo0Z', {timeout: 0});
this.emit(Events.AUTHENTICATED);
// Check Store Injection
await page.waitForFunction('window.Store != undefined');
this.pupBrowser = browser;
this.pupPage = page;
this.emit(Events.READY);
}
async destroy() {
await this.pupBrowser.close();
}
}
module.exports = Client;

9
src/index.js Normal file
View File

@@ -0,0 +1,9 @@
'use strict';
module.exports = {
Client: require('./client/Client'),
version: require('../package.json').version
// Models
}

24
src/util/Constants.js Normal file
View File

@@ -0,0 +1,24 @@
'use strict';
exports.WhatsWebURL = 'https://web.whatsapp.com/'
exports.UserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36';
exports.DefaultOptions = {
puppeteer: {
headless: true
}
}
exports.Status = {
INITIALIZING: 0,
AUTHENTICATING: 1,
READY: 3
}
exports.Events = {
AUTHENTICATED: 'authenticated',
READY: 'ready',
MESSAGE_CREATE: 'message',
QR_RECEIVED: 'qr'
}

50
src/util/Injected.js Normal file
View File

@@ -0,0 +1,50 @@
'use strict';
/**
* Exposes the internal Store to the WhatsApp Web client
*/
exports.ExposeStore = () => {
setTimeout(function () {
function getAllModules() {
return new Promise((resolve) => {
const id = _.uniqueId("fakeModule_");
window["webpackJsonp"](
[],
{
[id]: function (module, exports, __webpack_require__) {
resolve(__webpack_require__.c);
}
},
[id]
);
});
}
var modules = getAllModules()._value;
for (var key in modules) {
if (modules[key].exports) {
if (modules[key].exports.default) {
if (modules[key].exports.default.Wap) {
store_id = modules[key].id.replace(/"/g, '"');
}
}
}
}
}, 2000);
function _requireById(id) {
return webpackJsonp([], null, [id]);
}
var store_id = 0;
function init() {
window.Store = _requireById(store_id).default;
}
setTimeout(function () {
init();
}, 5000);
}

35
src/util/Util.js Normal file
View File

@@ -0,0 +1,35 @@
'use strict';
const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
/**
* Utility methods
*/
class Util {
constructor() {
throw new Error(`The ${this.constructor.name} class may not be instantiated.`);
}
/**
* Sets default properties on an object that aren't already specified.
* @param {Object} def Default properties
* @param {Object} given Object to assign defaults to
* @returns {Object}
* @private
*/
static mergeDefault(def, given) {
if (!given) return def;
for (const key in def) {
if (!has(given, key) || given[key] === undefined) {
given[key] = def[key];
} else if (given[key] === Object(given[key])) {
given[key] = Util.mergeDefault(def[key], given[key]);
}
}
return given;
}
}
module.exports = Util;