From 9ea4b0b38149c58ef20bc50f119aaa1fd557d873 Mon Sep 17 00:00:00 2001 From: Adhiraj Date: Thu, 16 Apr 2020 16:20:48 +0530 Subject: [PATCH] added media send/recieve, extended text messages send/recieve --- .DS_Store | Bin 8196 -> 10244 bytes .gitignore | 1 + README.md | 98 ++++-- WhatsAppWeb.Recv.js | 108 +++++-- WhatsAppWeb.Send.js | 133 +++++++- WhatsAppWeb.Session.js | 3 +- WhatsAppWeb.Utils.js | 89 +++++- WhatsAppWeb.js | 12 +- example/.DS_Store | Bin 0 -> 6148 bytes test.js => example/example.js | 37 ++- example/ma_gif.mp4 | Bin 0 -> 40240 bytes package-lock.json | 564 ++++++++++++++++++++++++++++++++++ package.json | 3 + 13 files changed, 987 insertions(+), 61 deletions(-) create mode 100644 example/.DS_Store rename test.js => example/example.js (50%) create mode 100644 example/ma_gif.mp4 diff --git a/.DS_Store b/.DS_Store index 96e93ef97ed296b60e601bd5adb6b8385e3a97ca..ebf005c9b28b286b3216b98a80b709634f944703 100644 GIT binary patch literal 10244 zcmeHM&2Jk;6o2Ccy3U8=G+%91B$t4w)PU^NX%bLW$4vzRRS9uu6Vl?Yy=!Obde_=r z=c6H2E|oZOhH&P9#2>&32_z11;RxcwKfnPAi7WhOcAa?r(F6%Wg=Vas_jY!EZ)Sh< z-ptOJh*-9!rHB$lq~PK*-iNGB;?Mcjk|IILB}fK5(KRaI(nzQ2mfEsFG9Vd{3`hnf z1CoKKfdQ=9JSn}tl(}R;G9Vc^Vt~&N30z!e0(sy|F?AplTL8#z6pI8M`v8GS1TquI z178X#Z2G$g7C>3B#b7=h&l9{lWG0XYzVzY5d^oY7WftsEm}niigs4s|<4c)K1|$Qm z3|vA{jFQ;5Pm&tizrR8is#1Ey9u>I&U$Ef7cF7QQQG*MCxBk8r% zBh7LuSF|ft@n(MZgmx)zZX5b`K0bWIwmj9abVq;a`LA4V1|8#LhGALXAyYhtC17c|vq9C_`fTaTt+5F_7G>v*&~*J?AuO?I|& zj%C_b89n815ks655Oq_CwIm|01a0<|<3ECt=YC_r2lB#?{J7C^=i!Nq-1TEH>>I+9 z8so1IvBx8hBP&9yagQQ5jHA!d<5S@pyz9IF+Plu}?aj=H1w45x(f@l|`#$qs1*m^iq{qb8qX83)zBSTUQ-NwaR*S z_nxU1baTnd+MZ`ucuj5%FS6|D+>&T1S5=FKRUW_oo8X$pGs|`5vaiHx+*b$>AO9qD zCC*kY+tQ=a@yuJnQ-!Y3JiSRdEI8Zr5gpPO^d)^uKhv-D2kT~|?0I&DrP&<2&fZ{e zvlX_=*4evk!^cPJQ^<4m)_ln{$d04j6ug6&cvUP`50IA;E-xc!Y;oUV;S&MWIE7z4 z*#ZJMg+)0nmg6Lj;!~I)u7(3MqBbqYr?Wc=OH=syOFh@FonW`GslOfGlYdx*mZ~VN z&`pfj0^Ne-9~4@+C5F$55Tf^J3d^I-E!>H=!l($JMjQEz(H;1MDki>p;HtRSF~PRM zias_@WSAKF>r{XU(Q9IRNFWZPNci*=)w(T;uLR$TlHc$(GD7Wajat>KQg z-T_Yre75Z(AwIPlxrJGoBm0_YF?^1%F|KIHV|Nqn1aoHoufMnnwV}Nxp z5|<4%l-fVegFR#zmZ!h delta 452 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8E20o2aKKDgu%R@);Q788R927}6N>8T2+5 zPGg_gz`L29gN1`pWO9pu9vg!-P#wqS9RjS3lbuDL@|zge>L^qj8kn2tD3}@>O*R*e zWZXP?qNtLgsHm8@sDzM|bi9CYQetv;dQpC9UW#*mPJWS7PHAeq0Do~tez|9IeqKOP zYFTD#`Q+Q8yXqq{fdavqRjJYGMVTqV1&PU-dFjgbAArsS0#0QI4G2qsK^ejT`bB^- zBriWN6(|`15mR>XhDvfUXfrr61T(}i6f!h0OlMfku$AE;!#Re>3||@kGx9QuF)A@? zGwLv!GMY0wFgh}-J2QGPdNKw { /* when you're successfully authenticated with the WhatsApp Web servers */ } ``` @@ -29,21 +30,77 @@ Baileys is super easy to use: ``` javascript client.handlers.onDisconnect = () => { /* called when internet gets disconnected */ } ``` -5. Send a text message using +* Get the type of message using + ``` javascript + client.handlers.onUnreadMessage = (m) => { + const messageType = client.getMessageType(m.message) // get what type of message it is -- text, image, video + } + ``` +* Decode a media message using + ``` javascript + client.handlers.onUnreadMessage = (m) => { + const messageType = client.getMessageType(m.message) // get what type of message it is -- text, image, video + + // if the message is not a text message + if (messageType !== WhatsAppWeb.MessageType.text && messageType !== WhatsAppWeb.MessageType.extendedText) { + client.decodeMediaMessage(m.message, "filename") // extension applied automatically + .then (meta => console.log(m.key.remoteJid + " sent media, saved at: " + meta.fileName)) + .catch (err => console.log("error in decoding message: " + err)) + } + } + ``` +* Send a text message using ``` javascript - client.sendTextMessage(id, txtMessage) + client.sendTextMessage(id, txtMessage) + ``` + Or if you want to quote another message: + ``` javascript + client.sendTextMessage(id, txtMessage, quotedMessage) ``` The id is the phone number of the person the message is being sent to, it must be in the format '[country code][phone number]@s.whatsapp.net', for example '+19999999999@s.whatsapp.net' -6. Send a read reciept using +* Send a media (image, video, sticker, pdf) message using + ``` javascript + client.sendMediaMessage(id, mediaBuffer, mediaType, info) + ``` + - The thumbnail can be generated automatically for images & stickers. + - ```mediaBuffer``` is just a Buffer containing the contents of the media you want to send + - ```mediaType``` represents the type of message you are sending. This can be one of the following: + ``` javascript + [ + WhatsAppWeb.MessageType.image, // an image message + WhatsAppWeb.MessageType.video, // a video message + WhatsAppWeb.MessageType.audio, // an audio message + WhatsAppWeb.MessageType.sticker // a sticker message + ] + ``` + - ```info``` is a JSON object, providing some information about the media. It can have the following __optional__ values: + ``` javascript + info = { + caption: "hello there!", // the caption to send with the media (cannot be sent with stickers though) + thumbnail: null, /* has to be a base 64 encoded JPEG if you want to send a custom thumb, + or set to null if you don't want to send a thumbnail. + Do not enter this field if you want to automatically generate a thumb + */ + mimetype: "application/pdf", /* specify the type of media (optional for all media types except documents), + for pdf files => set to "application/pdf", + for txt files => set to "application/txt" + etc. + */ + gif: true // only applicable to video messages, if the video should be treated as a GIF + } + ``` + - Tested formats: png, jpeg, webp (sticker), mp4, ogg + - To automatically generate thumbnails for videos, you need to have ``` ffmpeg ``` installed on your system +* Send a read reciept using ``` javascript client.sendReadReceipt(id, messageID) ``` The id is in the same format as mentioned earlier. The message ID is the unique identifier of the message that you are marking as read -7. Tell someone what your status is right now by using +* Update your status by using ``` javascript client.updatePresence(id, presence) ``` - Presence can be one of the following: + This lets the person with ``` id ``` know your status. where ``` presence ``` can be one of the following: ``` javascript static Presence = { available: "available", // "online" @@ -53,20 +110,22 @@ Baileys is super easy to use: paused: "paused" // I have no clue } ``` -8. Once you want to close your session, you can get your authentication credentials using: + +* Once you want to close your session, you can get your authentication credentials using: ``` javascript const authJSON = client.base64EncodedAuthInfo() ``` and then save this JSON to a file -9. If you want to restore your session (i.e. log back in without having to scan the QR code), simply retreive your previously saved credentials and use +* If you want to restore your session (i.e. log back in without having to scan the QR code), simply retreive your previously saved credentials and use ``` javascript const authJSON = JSON.parse( fs.readFileSync("auth_info.json") ) - client.login( authJSON ) + client.login(authJSON) ``` This will use the credentials to connect & log back in. No need to call ``` connect() ``` after calling this function -10. If you want to query whether a number is registered on WhatsApp, use: +* If you want to query whether a number is registered on WhatsApp, use: ``` javascript - client.isOnWhatsApp ("[countrycode][some10digitnumber]@s.whatsapp.net", (exists, id) => { + client.isOnWhatsApp ("[countrycode][some10digitnumber]@s.whatsapp.net") + .then ((exists, id) => { if (exists) { console.log(id + " is on WhatsApp") } else { @@ -74,10 +133,13 @@ Baileys is super easy to use: } }) ``` - Of course, replace ``` [countrycode][some10digitnumber] ``` with an actual country code & number + Of course, replace ``` [countrycode][some10digitnumber] ``` with an actual country code & number. ``` isOnWhatsApp ``` returns a promise. -Do check out test.js to see example usage of all these functions. +Do check out & run [example.js](example/example.js) to see example usage of all these functions. +To run the example script, download or clone the repo and then type the following in terminal: +1. ``` cd path/to/Baileys/example ``` +2. ``` node example.js ``` # Note I am in no way affiliated with WhatsApp. This was written for educational purposes. Use at your own discretion. \ No newline at end of file diff --git a/WhatsAppWeb.Recv.js b/WhatsAppWeb.Recv.js index 6558fe1..7f9eed5 100644 --- a/WhatsAppWeb.Recv.js +++ b/WhatsAppWeb.Recv.js @@ -1,4 +1,7 @@ const Utils = require("./WhatsAppWeb.Utils") +const HKDF = require("futoin-hkdf") +const fs = require("fs") +const fetch = require("node-fetch") /* Contains the code for recieving messages and forwarding what to do with them to the correct functions */ @@ -23,11 +26,15 @@ module.exports = function(WhatsAppWeb) { // got an empty message, usually get one after sending a message or something, just return then return } + // get the message tag. + // If a query was done, the server will respond with the same message tag we sent the query with + const messageTag = message.slice(0, commaIndex) + //console.log(messageTag) let json if (data[0] === "[" || data[0] === "{") { // if the first character is a "[", then the data must just be plain JSON array or object - json = JSON.parse( data ) // simply parse the JSON - console.log("JSON: " + data) + json = JSON.parse( data ) // parse the JSON + //console.log("JSON: " + data) } else if (this.status === Status.connected) { /* If the data recieved was not a JSON, then it must be an encrypted message. @@ -51,8 +58,7 @@ module.exports = function(WhatsAppWeb) { // if we recieved a message that was encrypted but we weren't conencted, then there must be an error return this.gotError([3, "recieved encrypted message when not connected: " + this.status, message]) } - - //console.log(json) + // the first item in the recieved JSON, if it exists, it tells us what the message is about switch (json[0]) { case "Conn": @@ -108,9 +114,10 @@ module.exports = function(WhatsAppWeb) { if (!this.chats[ id ]) { // if we haven't added this ID before, add them now this.chats[ id ] = {user: { jid: id, count: 0 }, messages: []} } - this.chats[id].messages.push(message[2]) // append this message to the array + } + const id = json[0][2].key.remoteJid // get the ID whose chats we just processed this.clearUnreadMessages(id) // forward to the handler any any unread messages @@ -139,18 +146,17 @@ module.exports = function(WhatsAppWeb) { /* if the recieved JSON wasn't an array, then we must have recieved a status for a request we made - this would include creating new sessions, logging in & queries */ - // if we're connected and we had a pending query if (this.status === Status.connected) { - if (json.status && this.queryCallbacks.length > 0) { - for (var i in this.queryCallbacks) { - if (this.queryCallbacks[i].queryJSON[1] === "exist") { - this.queryCallbacks[i].callback(json.status == 200, this.queryCallbacks[i].queryJSON[2]) - this.queryCallbacks.splice(i, 1) - break - } + // if this message is responding to a query + if (this.queryCallbacks[messageTag]) { + const q = this.queryCallbacks[messageTag] + if (q.queryJSON[1] === "exist") { + q.callback(json.status == 200, q.queryJSON[2]) + } else if (q.queryJSON[1] === "mediaConn") { + q.callback(json.media_conn) } + delete this.queryCallbacks[messageTag] } } else { // if we're trying to establish a new connection or are trying to log in @@ -166,12 +172,6 @@ module.exports = function(WhatsAppWeb) { } else { this.generateKeysForAuth(json.ref) } - } else if (this.queryCallbacks.length > 0) { - for (var i in this.queryCallbacks) { - if (this.queryCallbacks[i].queryJSON[1] == "query") { - this.queryCallbacks[i].callback( ) - } - } } break @@ -185,7 +185,7 @@ module.exports = function(WhatsAppWeb) { console.log("reuse previous ref") return this.gotError([ json.status, "request for new key denied", message ]) default: - break + return this.gotError([ json.status, "unknown error", message ]) } } } @@ -221,6 +221,70 @@ module.exports = function(WhatsAppWeb) { this.handlers.onUnreadMessage ( message ) } } - } + } + + // get what type of message it is + WhatsAppWeb.prototype.getMessageType = function (message) { + return Object.keys(message)[0] + /*for (var key in WhatsAppWeb.MessageType) { + if (WhatsAppWeb.MessageType[key] === relvantKey) { + return key + } + }*/ + } + + // decode a media message (video, image, document, audio) & save it to the given file; returns a promise with metadata + WhatsAppWeb.prototype.decodeMediaMessage = function (message, fileName) { + + const getExtension = function (mimetype) { + const str = mimetype.split(";")[0].split("/") + return str[1] + } + /* + can infer media type from the key in the message + it is usually written as [mediaType]Message. Eg. imageMessage, audioMessage etc. + */ + let type = this.getMessageType(message) + if (!type) { + return Promise.reject("unknown message type") + } + if (type === WhatsAppWeb.MessageType.extendedText || type === WhatsAppWeb.MessageType.text) { + return Promise.reject("cannot decode text message") + } + + message = message[type] + // get the keys to decrypt the message + const mediaKeys = Utils.getMediaKeys(Buffer.from(message.mediaKey, 'base64'), type) + const iv = mediaKeys.iv + const cipherKey = mediaKeys.cipherKey + const macKey = mediaKeys.macKey + + // download the message + let p = fetch(message.url).then (res => res.buffer()) + p = p.then(buffer => { + // first part is actual file + let file = buffer.slice(0, buffer.length-10) + // last 10 bytes is HMAC sign of file + let mac = buffer.slice(buffer.length-10, buffer.length) + + // sign IV+file & check for match with mac + let testBuff = Buffer.concat([iv, file]) + let sign = Utils.hmacSign(testBuff, macKey).slice(0, 10) + + // our sign should equal the mac + if (sign.equals(mac)) { + let decrypted = Utils.aesDecryptWithIV(file, cipherKey, iv) // decrypt media + + const trueFileName = fileName + "." + getExtension(message.mimetype) + fs.writeFileSync(trueFileName, decrypted) + + message.fileName = trueFileName + return message + } else { + throw "HMAC sign does not match" + } + }) + return p + } } \ No newline at end of file diff --git a/WhatsAppWeb.Send.js b/WhatsAppWeb.Send.js index 53739b7..bddba87 100644 --- a/WhatsAppWeb.Send.js +++ b/WhatsAppWeb.Send.js @@ -1,4 +1,5 @@ const Utils = require("./WhatsAppWeb.Utils") +const fetch = require('node-fetch') /* Contains the code for sending stuff to WhatsApp @@ -19,15 +20,13 @@ module.exports = function(WhatsAppWeb) { } } // check if given number is registered on WhatsApp - WhatsAppWeb.prototype.isOnWhatsApp = function (jid, callback) { + WhatsAppWeb.prototype.isOnWhatsApp = function (jid) { const json = [ "query", "exist", jid ] - this.sendJSON(json) // send - - this.queryCallbacks.push({queryJSON: json, callback: callback}) + return this.query(json) } // tell someone about your presence -- online, typing, offline etc. WhatsAppWeb.prototype.updatePresence = function (jid, type) { @@ -38,11 +37,111 @@ module.exports = function(WhatsAppWeb) { ] this.sendBinary(json, [10, 128]) } - // send a text message to someone, optionally you can provide the time at which you want the message to be sent - WhatsAppWeb.prototype.sendTextMessage = function (id, txt, timestamp=null) { - const message = {conversation: txt} + // send a text message to someone, optionally you can provide a quoted message & the timestamp for the message + WhatsAppWeb.prototype.sendTextMessage = function (id, txt, quoted=null, timestamp=null) { + let message + if (quoted) { + message = { + extendedTextMessage: { + text: txt, + contextInfo: { + participant: quoted.key.remoteJid, + stanzaId: quoted.key.id, + quotedMessage: quoted.message + } + } + } + } else { + message = {conversation: txt} + } + return this.sendMessage(id, message, timestamp) } + // send a media message to someone, optionally you can provide a caption, thumbnail, mimetype & the timestamp for the message + WhatsAppWeb.prototype.sendMediaMessage = function (id, buffer, mediaType, info=null, timestamp=null) { + // path to upload the media + const mediaPathMap = { + imageMessage: "/mms/image", + videoMessage: "/mms/video", + documentMessage: "/mms/document", + audioMessage: "/mms/audio", + stickerMessage: "/mms/image" + } + // gives WhatsApp info to process the media + const defaultMimetypeMap = { + imageMessage: "image/jpeg", + videoMessage: "video/mp4", + documentMessage: "appliction/pdf", + audioMessage: "audio/ogg; codecs=opus", + stickerMessage: "image/webp" + } + if (!info) { + info = {} + } + if (mediaType === WhatsAppWeb.MessageType.text || mediaType === WhatsAppWeb.MessageType.extendedText) { + return Promise.reject("use sendTextMessage() to send text messages") + } + if (mediaType === WhatsAppWeb.MessageType.document && !info.mimetype) { + return Promise.reject("mimetype required to send a document") + } + if (mediaType === WhatsAppWeb.MessageType.sticker && info.caption) { + return Promise.reject("cannot send a caption with a sticker") + } + if (!info.mimetype) { + info.mimetype = defaultMimetypeMap[mediaType] + } + + // generate a media key + const mediaKey = Utils.randomBytes(32) + const mediaKeys = Utils.getMediaKeys(mediaKey, mediaType) + const enc = Utils.aesEncrypWithIV(buffer, mediaKeys.cipherKey, mediaKeys.iv) + const mac = Utils.hmacSign(Buffer.concat([mediaKeys.iv, enc]), mediaKeys.macKey).slice(0, 10) + const body = Buffer.concat([enc, mac]) // body is enc + mac + const fileSha256 = Utils.sha256(buffer) + // url safe Base64 encode the SHA256 hash of the body + const fileEncSha256B64 = Utils.sha256(body).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '') + + const promise = + Utils.generateThumbnail(buffer, mediaType, info) + .then (() => this.query(["query", "mediaConn"])) // send a query JSON to obtain the url & auth token to upload our media + .then ((json) => { + const auth = json.auth // the auth token + let hostname = "https://" + json.hosts[0].hostname // first hostname available + hostname += mediaPathMap[mediaType] + "/" + fileEncSha256B64 // append path + hostname += "?auth=" + auth // add auth token + hostname += "&token=" + fileEncSha256B64 // file hash + + return fetch(hostname, {method: 'POST', body: body, headers: {Origin: "https://web.whatsapp.com"}}) + }) + .then (res => res.json()) + .then (json => { + if (json.url) { + return json.url + } else { + throw "UPLOAD FAILED GOT: " + JSON.stringify(json) + } + }) + .then (url => { + let message = {} + message[mediaType] = { + caption: info.caption, + url: url, + mediaKey: mediaKey.toString('base64'), + mimetype: info.mimetype, + fileEncSha256: fileEncSha256B64, + fileSha256: fileSha256.toString('base64'), + fileLength: buffer.length, + jpegThumbnail: info.thumbnail + } + if (mediaType === WhatsAppWeb.MessageType.video && info.gif) { + message[mediaType].gifPlayback = info.gif + } + //console.log(message) + return this.sendMessage(id, message, timestamp) + }) + + return promise + } // generic send message construct WhatsAppWeb.prototype.sendMessage = function (id, message, timestamp=null) { if (!timestamp) { // if no timestamp was provided, @@ -66,7 +165,8 @@ module.exports = function(WhatsAppWeb) { {epoch: this.msgCount.toString(), type: "relay" }, [ ['message', null, messageJSON] ] ] - return this.sendBinary(json, [16, 128]) + this.sendBinary(json, [16, 128]) + return messageJSON } // send a binary message, the tags parameter tell WhatsApp what the message is all about WhatsAppWeb.prototype.sendBinary = function (json, tags) { @@ -74,19 +174,30 @@ module.exports = function(WhatsAppWeb) { var buff = Utils.aesEncrypt(binary, this.authInfo.encKey) // encrypt it using AES and our encKey const sign = Utils.hmacSign(buff, this.authInfo.macKey) // sign the message using HMAC and our macKey - + const tag = Utils.generateMessageTag() buff = Buffer.concat([ - Buffer.from( Utils.generateMessageTag() + "," ), // generate & prefix the message tag + Buffer.from(tag + ","), // generate & prefix the message tag Buffer.from(tags), // prefix some bytes that tell whatsapp what the message is about sign, // the HMAC sign of the message buff // the actual encrypted buffer ]) this.send(buff) // send it off + return tag + } + // send query message to WhatsApp servers; returns a promise + WhatsAppWeb.prototype.query = function (json) { + const promise = new Promise((resolve, reject) => { + const tag = this.sendJSON(json) // send + this.queryCallbacks[tag] = {queryJSON: json, callback: resolve, errCallback: reject} + }) + return promise } // send a JSON message to WhatsApp servers WhatsAppWeb.prototype.sendJSON = function (json) { const str = JSON.stringify(json) - this.send( Utils.generateMessageTag() + "," + str ) + const tag = Utils.generateMessageTag() + this.send(tag + "," + str) + return tag } WhatsAppWeb.prototype.send = function (m) { this.msgCount += 1 // increment message count, it makes the 'epoch' field when sending binary messages diff --git a/WhatsAppWeb.Session.js b/WhatsAppWeb.Session.js index ab73d54..50aa7b8 100644 --- a/WhatsAppWeb.Session.js +++ b/WhatsAppWeb.Session.js @@ -1,6 +1,5 @@ const WebSocket = require('ws') const Curve = require ('curve25519-js') -const HKDF = require('futoin-hkdf') const Utils = require('./WhatsAppWeb.Utils') const QR = require('qrcode-terminal') @@ -68,7 +67,7 @@ module.exports = function (WhatsAppWeb) { // generate shared key from our private key & the secret shared by the server const sharedKey = Curve.sharedKey( this.curveKeys.private, secret.slice(0, 32) ) // expand the key to 80 bytes using HKDF - const expandedKey = HKDF(sharedKey, 80, [ Buffer.alloc(32), '', 'SHA-256' ]) + const expandedKey = Utils.hkdf(sharedKey, 80) // perform HMAC validation. const hmacValidationKey = expandedKey.slice(32, 64) diff --git a/WhatsAppWeb.Utils.js b/WhatsAppWeb.Utils.js index 5e583d9..b52d9b5 100644 --- a/WhatsAppWeb.Utils.js +++ b/WhatsAppWeb.Utils.js @@ -1,4 +1,8 @@ const Crypto = require("crypto") +const HKDF = require("futoin-hkdf") +const sharp = require("sharp") +const VideoThumb = require("video-thumb") +const fs = require("fs") /* Basic cryptographic utilities to interact with WhatsApp servers @@ -6,19 +10,98 @@ const Crypto = require("crypto") module.exports = { // decrypt AES 256 CBC; where the IV is prefixed to the buffer aesDecrypt: function (buffer, key) { - const aes = Crypto.createDecipheriv('aes-256-cbc', key, buffer.slice(0,16) ) // first 16 bytes of buffer is IV - return Buffer.concat( [ aes.update(buffer.slice(16, buffer.length)), aes.final() ] ) + return this.aesDecryptWithIV(buffer.slice(16, buffer.length), key, buffer.slice(0,16)) }, - // encrypt AES 256 CBC; where the IV is prefixed to the buffer + // decrypt AES 256 CBC + aesDecryptWithIV: function (buffer, key, IV) { + const aes = Crypto.createDecipheriv('aes-256-cbc', key, IV ) + return Buffer.concat( [ aes.update(buffer), aes.final() ] ) + }, + // encrypt AES 256 CBC; where a random IV is prefixed to the buffer aesEncrypt: function (buffer, key) { const IV = this.randomBytes(16) const aes = Crypto.createCipheriv('aes-256-cbc', key, IV) return Buffer.concat( [ IV, aes.update(buffer), aes.final() ] ) // prefix IV to the buffer }, + // encrypt AES 256 CBC with a given IV + aesEncrypWithIV: function (buffer, key, IV) { + const aes = Crypto.createCipheriv('aes-256-cbc', key, IV) + return Buffer.concat( [ aes.update(buffer), aes.final() ] ) // prefix IV to the buffer + }, // sign HMAC using SHA 256 hmacSign: function (buffer, key) { return Crypto.createHmac('sha256', key).update(buffer).digest() }, + sha256: function (buffer) { + return Crypto.createHash('sha256').update(buffer).digest() + }, + // HKDF key expansion + hkdf: function (buffer, expandedLength, info) { + return HKDF(buffer, expandedLength, {salt: Buffer.alloc(32), info: info, hash: 'SHA-256'}) + }, + // generates all the keys required to encrypt/decrypt & sign a media message + getMediaKeys: function (buffer, mediaType) { + // info to put into the HKDF key expansion + const appInfo = { + 'imageMessage': 'WhatsApp Image Keys', + 'videoMessage': 'WhatsApp Video Keys', + 'audioMessage': 'WhatsApp Audio Keys', + 'documentMessage': 'WhatsApp Document Keys', + 'stickerMessage': 'WhatsApp Image Keys' + } + // expand using HKDF to 112 bytes, also pass in the relevant app info + const expandedMediaKey = this.hkdf(buffer, 112, appInfo[mediaType]) + return { + iv: expandedMediaKey.slice(0, 16), + cipherKey: expandedMediaKey.slice(16, 48), + macKey: expandedMediaKey.slice(48, 80) + } + }, + // generates a thumbnail for a given media, if required + generateThumbnail: function (buffer, mediaType, info) { + let promise + if (info.thumbnail === null || info.thumbnail) { // don't do anything if the thumbnail is already provided, or is null + if (mediaType === 'audioMessage') { + promise = Promise.reject("audio messages cannot have thumbnails") + } else { + promise = Promise.resolve() + } + } else { + if (mediaType === 'imageMessage' || mediaType === 'stickerMessage') { + promise = sharp(buffer) // generate a 48x48 thumb + .resize(48, 48) + .jpeg() + .toBuffer() + .then (buffer => info.thumbnail = buffer.toString('base64')) + } else if (mediaType === 'videoMessage') { + const filename = "./" + this.randomBytes(5).toString("hex") + ".mp4" + fs.writeFileSync(filename, buffer) + + promise = new Promise ( (resolve, reject) => { + VideoThumb.extract (filename, filename + ".png", "00:00:00", "48x48", (err) => { + if (err) { + console.log("could not generate video thumb: " + err) + resolve() + } else { + const buff = fs.readFileSync(filename + ".png") + return sharp(buff) + .jpeg() + .toBuffer() + .then (buffer => info.thumbnail = buffer.toString('base64')) + .then (() => { + fs.unlinkSync(filename) + fs.unlinkSync(filename + ".png") + resolve() + }) + } + }) + }) + } else { + promise = Promise.resolve() + } + } + return promise + }, // generate a buffer with random bytes of the specified length randomBytes: function (length) { return Crypto.randomBytes(length) }, diff --git a/WhatsAppWeb.js b/WhatsAppWeb.js index 1f46c98..224015c 100644 --- a/WhatsAppWeb.js +++ b/WhatsAppWeb.js @@ -22,6 +22,16 @@ class WhatsAppWeb { paused: "paused" // I have no clue } + // set of message types that are supported by the library + static MessageType = { + text: "conversation", + image: "imageMessage", + video: "videoMessage", + sticker: "stickerMessage", + document: "documentMessage", + extendedText: "extendedTextMessage" + } + constructor() { this.conn = null // the websocket connection @@ -34,7 +44,7 @@ class WhatsAppWeb { this.autoReconnect = true // reconnect automatically after an unexpected disconnect this.lastSeen = null // updated by sending a keep alive request to the server, and the server responds with our updated last seen - this.queryCallbacks = [] + this.queryCallbacks = {} this.encoder = new BinaryCoding.Encoder() this.decoder = new BinaryCoding.Decoder() diff --git a/example/.DS_Store b/example/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b340688583367ddb830870602e4f83f42beddbcb GIT binary patch literal 6148 zcmeHKyG{c!5FA4!qR^zIbV(HniH_zJg@T$70Ky{zB??lYzm9L=D`EBl%H^P?K(o^B z*z2A1-4wSrfHHl(I|0T3hIB)`S?HUsoA+!lGls>V@rVaZu);G=d(}TDj5{LZ23J^M zi+{0P-_DooIoIvw&gjPk3j1xRzvs3-PQ|%N5DIf);fE17dQh*AuXPXTV z0~Mu!6p#Yn3fTXl(hZx$Hqd_^40e3C^?{)o$9|U})|@wqZ6H@@C8)%pmb_w=pfg{y zu1RbIgO1`m^EtV5$s0=X-I=dmj?x4wN&zX*RbW4+W9$Ft^k44(U6N)}KnnaT1&gwO-E9IRtQGt@voGuG*M&O=279bmpB-)Iq>>kx7BSP~a0s C-zM<@ literal 0 HcmV?d00001 diff --git a/test.js b/example/example.js similarity index 50% rename from test.js rename to example/example.js index b8ab28f..e931fbe 100644 --- a/test.js +++ b/example/example.js @@ -1,4 +1,4 @@ -const WhatsAppWeb = require("./WhatsAppWeb") +const WhatsAppWeb = require("../WhatsAppWeb") const fs = require("fs") let client = new WhatsAppWeb() // instantiate @@ -21,18 +21,47 @@ client.handlers.onConnected = () => { } // called when you have a pending unread message or recieve a new message client.handlers.onUnreadMessage = (m) => { - console.log("recieved message: " + JSON.stringify(m)) // log and see what the message looks like + // console.log("recieved message: " + JSON.stringify(m)) // uncomment to see what the raw message looks like + + const messageType = client.getMessageType(m.message) // get what type of message it is -- text, image, video + console.log("got message of type: " + messageType) + + if (messageType === WhatsAppWeb.MessageType.text) { // if it is plain text + const text = m.message.conversation + console.log (m.key.remoteJid + " sent: " + text) + } else if (messageType === WhatsAppWeb.MessageType.extendedText) { // if it is a quoted thing + const text = m.message.extendedTextMessage.text // the actual text + const quotedMessage = m.message.extendedTextMessage.contextInfo.quotedMessage // message that was replied to + console.log (m.key.remoteJid + " sent: " + text + " and quoted a " + client.getMessageType(quotedMessage)) + } else { // if it is a media (audio, image, video) message + // decode, decrypt & save the media. + // The extension to the is applied automatically based on the media type + client.decodeMediaMessage(m.message, "media_in_" + m.key.id) + .then (meta => console.log(m.key.remoteJid + " sent media, saved at: " + meta.fileName)) + .catch (err => console.log("error in decoding message: " + err)) + } + console.log("responding...") /* send a message after at least a 1 second timeout after recieving a message, otherwise WhatsApp will reject the message otherwise */ setTimeout(() => client.sendReadReceipt(m.key.remoteJid, m.key.id), 2*1000) // send a read reciept for the message in 2 seconds setTimeout(() => client.updatePresence(m.key.remoteJid, WhatsAppWeb.Presence.composing), 2.5*1000) // let them know you're typing in 2.5 seconds - setTimeout(() => client.sendTextMessage(m.key.remoteJid, "hello!"), 4*1000) // send the actual message after 4 seconds + setTimeout(() => { + if (Math.random() > 0.5) { // choose at random + client.sendTextMessage(m.key.remoteJid, "hello!", m) // send a "hello!" & quote the message recieved + } else { + const buffer = fs.readFileSync("./ma_gif.mp4") // load the gif + const info = { + gif: true, // the video is a gif + caption: "hello!" // the caption + } + client.sendMediaMessage (m.key.remoteJid, buffer, WhatsAppWeb.MessageType.video, info) // send this gif! + } + }, 4*1000) // send after 4 seconds } // called if an error occurs client.handlers.onError = (err) => console.log(err) client.handlers.onDisconnect = () => { /* internet got disconnected, save chats here or whatever; will reconnect automatically */ } - const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout diff --git a/example/ma_gif.mp4 b/example/ma_gif.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d48b0fe37ae8a11aeadb6bf5ab022937872852f3 GIT binary patch literal 40240 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 VrHn6&h}*?44$p8Y7ytX%{x3VZ-=Y8j literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 2914a0b..95ac67f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,21 +68,365 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.17.tgz", "integrity": "sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==" }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "bl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, "curve25519-js": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz", "integrity": "sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==" }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, "futoin-hkdf": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.3.1.tgz", "integrity": "sha512-k1DvCXIFAIx3hK8CSwApotX3JUDwA2Wb55zxyIgqwQpCBF2ZHgVqfHpyjG8mRpmsjRH7SWS1N/vj8EdSF9zBhw==" }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", + "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "mkdirp-classic": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz", + "integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==" + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node-abi": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.15.0.tgz", + "integrity": "sha512-FeLpTS0F39U7hHZU1srAK4Vx+5AHNVOTP+hxBNQknR/54laTHSFIJkDWDqiquY1LeLUgTfPN7sLPhMubx0PLAg==", + "requires": { + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "node-addon-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", + "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "prebuild-install": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", + "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "protobufjs": { "version": "6.8.9", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.9.tgz", @@ -103,15 +447,235 @@ "long": "^4.0.0" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "qrcode-terminal": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==" }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "sharp": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.25.2.tgz", + "integrity": "sha512-l1GN0kFNtJr3U9i9pt7a+vo2Ij0xv4tTKDIPx8W6G9WELhPwrMyZZJKAAQNBSI785XB4uZfS5Wpz8C9jWV4AFQ==", + "requires": { + "color": "^3.1.2", + "detect-libc": "^1.0.3", + "node-addon-api": "^2.0.0", + "npmlog": "^4.1.2", + "prebuild-install": "^5.3.3", + "semver": "^7.1.3", + "simple-get": "^3.1.0", + "tar": "^6.0.1", + "tunnel-agent": "^0.6.0" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "tar": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz", + "integrity": "sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==", + "requires": { + "chownr": "^1.1.3", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, + "tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "tar-stream": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", + "requires": { + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "video-thumb": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/video-thumb/-/video-thumb-0.0.3.tgz", + "integrity": "sha1-GMbS8wRIO1tVWzdZzUOI9uigjQg=" + }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, "ws": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index 60bfd1d..b5c2a1b 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,11 @@ "dependencies": { "curve25519-js": "0.0.4", "futoin-hkdf": "^1.3.1", + "node-fetch": "^2.6.0", "protobufjs": "^6.8.9", "qrcode-terminal": "^0.12.0", + "sharp": "^0.25.2", + "video-thumb": "0.0.3", "ws": "^7.2.3" } }