diff --git a/.DS_Store b/.DS_Store index 96e93ef..ebf005c 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index bf1eb46..9765491 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules auth_info.json test_pvt.js +media_decode_tests.js diff --git a/README.md b/README.md index f378a78..8cb5948 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,23 @@ # Baileys - Reverse Engineered WhatsApp Web API in Node.js. Baileys does not require Selenium or any other browser to be interface with WhatsApp Web, it does so directly using WebSockets. + + Reverse Engineered WhatsApp Web API in pure Node.js. Baileys does not require Selenium or any other browser to be interface with WhatsApp Web, it does so directly using a WebSocket. - Thank you to [Sigalor](https://github.com/sigalor/whatsapp-web-reveng) for writing the guide reverse engineering WhatsApp Web and to the go reimplementation written by [Rhymen](https://github.com/Rhymen/go-whatsapp/tree/484cfe758705761d76724e01839d6fc473dc10c4) + Thank you to [Sigalor](https://github.com/sigalor/whatsapp-web-reveng) for writing the guide to reverse engineering WhatsApp Web and thanks to [Rhymen](https://github.com/Rhymen/go-whatsapp/tree/484cfe758705761d76724e01839d6fc473dc10c4) for the __go__ reimplementation. Baileys is super easy to use: -1. Install from npm using +* Install from npm using ``` npm install github:adiwajshing/Baileys ``` -2. Then import in your code using +* Then import in your code using ``` javascript const WhatsAppWeb = require('Baileys') ``` -3. Create an instance of Baileys & connect using +* Create an instance of Baileys & connect using ``` javascript let client = new WhatsAppWeb() client.connect() ``` If the connection is successful, you will see a QR code printed on your terminal screen, scan it with WhatsApp on your phone and you'll be logged in! -4. Implement the following event handlers in your code: +* Implement the following event handlers in your code: ``` javascript client.handlers.onConnected = () => { /* 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 0000000..b340688 Binary files /dev/null and b/example/.DS_Store differ 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 0000000..d48b0fe Binary files /dev/null and b/example/ma_gif.mp4 differ 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" } }