├── .gitignore ├── src ├── utils │ ├── path.ts │ ├── arrays.ts │ ├── encryption.ts │ ├── whatsappTokens.ts │ ├── whatsappBinaryWriter.ts │ └── whatsappBinaryReader.ts └── main.ts ├── .vscode └── launch.json ├── .github └── stale.yml ├── package.json ├── LICENSE ├── README.md ├── tsconfig.json ├── spec └── def.proto └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /src/utils/path.ts: -------------------------------------------------------------------------------- 1 | import { exists as pathExists } from "fs"; 2 | 3 | export function doesFileExist(pathname: string): Promise { 4 | return new Promise(resolve => { 5 | pathExists(pathname, exists => { 6 | resolve(exists); 7 | }); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch via NPM", 11 | "runtimeExecutable": "npm", 12 | "runtimeArgs": [ 13 | "run-script", 14 | "debug" 15 | ], 16 | "port": 9229 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /src/utils/arrays.ts: -------------------------------------------------------------------------------- 1 | export function concatIntArray(...arrs: Uint8Array[]) { 2 | if (arrs.length > 0) { 3 | let finalArr = arrs[0]; 4 | 5 | for (let i = 1; i < arrs.length; i++) { 6 | const arr = arrs[i]; 7 | const tmp = new Uint8Array(finalArr.length + arr.length); 8 | 9 | tmp.set(finalArr, 0); 10 | tmp.set(arr, finalArr.length); 11 | 12 | finalArr = tmp; 13 | } 14 | 15 | return finalArr; 16 | } 17 | 18 | return new Uint8Array(); 19 | } 20 | 21 | export function arraysEqual(arr1: Uint8Array, arr2: Uint8Array) { 22 | if (arr1 === arr2) return true; 23 | if (arr1 === null || arr2 === null) return false; 24 | if (arr1.length !== arr2.length) return false; 25 | 26 | for (let i = 0; i < arr1.length; i++) { 27 | if (arr1[i] !== arr2[i]) return false; 28 | } 29 | return true; 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@noamalffasy/js-whatsapp", 3 | "version": "1.0.0-beta.3", 4 | "description": "A WhatsApp bot library", 5 | "author": "Noam Alffasy ", 6 | "license": "MIT", 7 | "main": "dist/main.js", 8 | "scripts": { 9 | "start": "node dist/main.js", 10 | "debug": "tsc && node --nolazy --inspect-brk=9229 dist/main.js", 11 | "prepare": "tsc" 12 | }, 13 | "devDependencies": { 14 | "@types/node": "^12.7.3", 15 | "@types/node-fetch": "^2.5.2", 16 | "@types/qrcode": "^1.3.4", 17 | "@types/sharp": "^0.22.3", 18 | "@types/ws": "^6.0.3", 19 | "typescript": "^3.6.2" 20 | }, 21 | "dependencies": { 22 | "curve25519-js": "^0.0.4", 23 | "form-data": "^2.5.1", 24 | "node-fetch": "^2.6.0", 25 | "protobufjs": "^6.8.9", 26 | "qrcode": "^1.4.4", 27 | "sharp": "^0.25.2", 28 | "ws": "^7.2.3" 29 | }, 30 | "files": [ 31 | "dist/**/*", 32 | "spec/*" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Noam Alffasy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WhatsApp Web API JS 2 | 3 | This library allows you to make a WhatsApp bot in JS or TS. The library implements the WhatsApp Web API and therefore doesn't use puppeteer, selenium, etc. 4 | 5 | The library is based on [Sigalor's WhatsApp Web Python library](https://github.com/sigalor/whatsapp-web-reveng). 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm install -S @noamalffasy/js-whatsapp 11 | ``` 12 | 13 | or 14 | 15 | ```bash 16 | yarn add @noamalffasy/js-whatsapp 17 | ``` 18 | 19 | ## Usage 20 | 21 | ### Setting up 22 | 23 | ```js 24 | import Whatsapp from "@noamalffasy/js-whatsapp"; 25 | 26 | const wap = new Whatsapp(); 27 | ``` 28 | 29 | ### Login 30 | 31 | By using the code shown above, a QR Code should be generated automatically and put in the current directory. 32 | 33 | Once scanned, your keys will be put in the same directory and saved for the next session. 34 | 35 | Auto login is supported, in order to login automatically you need to change: 36 | 37 | ```js 38 | const wap = new Whatsapp(); 39 | ``` 40 | 41 | to: 42 | 43 | ```js 44 | const wap = new Whatsapp(true); 45 | ``` 46 | 47 | ### Handle messages 48 | 49 | As of now, 2 events are supported: `ready` and `message`. 50 | 51 | ```js 52 | import Whatsapp from "@noamalffasy/js-whatsapp"; 53 | 54 | const wap = new Whatsapp(); 55 | 56 | wap.on("ready", () => { 57 | // Your code goes here 58 | }); 59 | 60 | wap.on("message", msg => { 61 | // Your code goes here 62 | }); 63 | ``` 64 | 65 | ### Sending text messages 66 | 67 | WhatsApp internally uses IDs called Jids: 68 | 69 | - **Chats:** [country code][phone number]@s.whatsapp.net 70 | - **Groups:** [country code][phone number of creator]-[timestamp of group creation]@g.us 71 | - **Broadcast Channels:** [timestamp of group creation]@broadcast 72 | 73 | If you'd like to see your contacts' and chats' Jids you can access the `contactList` or the `chatList` of the `wap` class. 74 | 75 | Once you've got your Jids you can send messages like so: 76 | 77 | ```js 78 | import Whatsapp from "@noamalffasy/js-whatsapp"; 79 | 80 | const wap = new Whatsapp(); 81 | 82 | wap.sendTextMessage("[text to send]", "[jid]"); 83 | ``` 84 | 85 | ### Sending quoted messages 86 | 87 | As of now, the library supports only text messages so the example is only for text. 88 | 89 | In order to quote a message you need to get its ID, the sender's Jid and the message's text. 90 | 91 | ```js 92 | import Whatsapp from "@noamalffasy/js-whatsapp"; 93 | 94 | const wap = new Whatsapp(); 95 | 96 | wap.sendQuotedTextMessage( 97 | "[text to send]", 98 | "[jid of group or contact to send the message to]", 99 | "[the jid of the message's sender]", 100 | "[the quoted message's content]", 101 | "[the quoted message's ID]" 102 | ); 103 | ``` 104 | 105 | ## Legal 106 | 107 | This code is in no way affiliated with, authorized, maintained, sponsored or endorsed by WhatsApp or any of its affiliates or subsidiaries. This is an independent and unofficial software. Use at your own risk. 108 | -------------------------------------------------------------------------------- /src/utils/encryption.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | 3 | import { concatIntArray } from "./arrays"; 4 | 5 | const AES_BLOCK_SIZE = 16; 6 | 7 | export function dataUrlToBuffer(dataString: string) { 8 | const matches = dataString.match( 9 | /^data:image\/([A-Za-z-+\/]+);base64,(.+)$/ 10 | )!; 11 | 12 | const type = matches[1]; 13 | const data = Buffer.from(matches[2], "base64"); 14 | 15 | return { type, data }; 16 | } 17 | 18 | export function randHex(n: number) { 19 | if (n <= 0) { 20 | return ""; 21 | } 22 | var rs = ""; 23 | try { 24 | rs = crypto 25 | .randomBytes(Math.ceil(n / 2)) 26 | .toString("hex") 27 | .slice(0, n); 28 | /* note: could do this non-blocking, but still might fail */ 29 | } catch (ex) { 30 | /* known exception cause: depletion of entropy info for randomBytes */ 31 | console.error("Exception generating random string: " + ex); 32 | /* weaker random fallback */ 33 | rs = ""; 34 | var r = n % 8, 35 | q = (n - r) / 8, 36 | i; 37 | for (i = 0; i < q; i++) { 38 | rs += Math.random() 39 | .toString(16) 40 | .slice(2); 41 | } 42 | if (r > 0) { 43 | rs += Math.random() 44 | .toString(16) 45 | .slice(2, i); 46 | } 47 | } 48 | return rs; 49 | } 50 | 51 | export function AESPad(src: Uint8Array) { 52 | const pad = AES_BLOCK_SIZE - (src.length % AES_BLOCK_SIZE); 53 | return concatIntArray(src, repeatedNumToBits(pad, pad)); 54 | } 55 | 56 | export function AESUnpad(src: Uint8Array) { 57 | return src 58 | .slice(0, src.length - src[src.length - 1]) 59 | .filter((_, i) => (i + 1) % 4 === 0); 60 | } 61 | 62 | export function AESEncrypt( 63 | key: Uint8Array, 64 | plaintext: Uint8Array, 65 | _iv: Uint8Array | null = null, 66 | includeIv = true 67 | ) { 68 | const iv = _iv ? _iv : Uint8Array.from(crypto.randomBytes(AES_BLOCK_SIZE)); 69 | const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); 70 | const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]); 71 | return includeIv 72 | ? concatIntArray(iv, Uint8Array.from(encrypted)) 73 | : Uint8Array.from(encrypted); 74 | } 75 | 76 | export function AESDecrypt(key: Uint8Array, cipherbits: Uint8Array) { 77 | const iv = cipherbits.slice(0, AES_BLOCK_SIZE); 78 | const prp = crypto.createDecipheriv("aes-256-cbc", key, iv); 79 | prp.setAutoPadding(false); 80 | const decrypted = Buffer.concat([ 81 | prp.update(cipherbits.slice(AES_BLOCK_SIZE)), 82 | prp.final() 83 | ]); 84 | 85 | return Uint8Array.from(decrypted); 86 | } 87 | 88 | export function numToBits(n: number): Uint8Array { 89 | return Uint8Array.from( 90 | Buffer.from((n < 16 ? "0" : "") + n.toString(16), "hex") 91 | ); 92 | } 93 | 94 | export function repeatedNumToBits(n: number, repeats: number): Uint8Array { 95 | let nBits = numToBits(n); 96 | let ret = new Uint8Array(); 97 | 98 | for (let i = 0; i < repeats; i++) { 99 | ret = concatIntArray(ret, nBits); 100 | } 101 | 102 | return ret; 103 | } 104 | 105 | export function HmacSha256( 106 | keyBits: Uint8Array, 107 | signBits: Uint8Array 108 | ): Uint8Array { 109 | return Uint8Array.from( 110 | crypto 111 | .createHmac("sha256", keyBits) 112 | .update(signBits) 113 | .digest() 114 | ); 115 | } 116 | 117 | export function Sha256(signBits: Uint8Array) { 118 | return Uint8Array.from( 119 | crypto 120 | .createHash("sha256") 121 | .update(signBits) 122 | .digest() 123 | ); 124 | } 125 | 126 | export function HKDF( 127 | _key: Uint8Array, 128 | length: number, 129 | appInfo = "" 130 | ): Uint8Array { 131 | const key = HmacSha256(repeatedNumToBits(0, 32), _key); 132 | let keyStream = new Uint8Array(); 133 | let keyBlock = new Uint8Array(); 134 | let blockIndex = 1; 135 | 136 | while (keyStream.length < length) { 137 | keyBlock = HmacSha256( 138 | key, 139 | concatIntArray( 140 | keyBlock, 141 | new TextEncoder().encode(appInfo), 142 | numToBits(blockIndex) 143 | ) 144 | ); 145 | blockIndex += 1; 146 | keyStream = concatIntArray(keyStream, keyBlock); 147 | } 148 | 149 | return keyStream.slice(0, length); 150 | } 151 | 152 | export function toArrayBuffer(buf: Buffer) { 153 | var ab = new ArrayBuffer(buf.length); 154 | var view = new Uint8Array(ab); 155 | for (var i = 0; i < buf.length; ++i) { 156 | view[i] = buf[i]; 157 | } 158 | return ab; 159 | } 160 | -------------------------------------------------------------------------------- /src/utils/whatsappTokens.ts: -------------------------------------------------------------------------------- 1 | import { resolve as resolvePath } from "path"; 2 | 3 | import protobuf from "protobufjs"; 4 | 5 | export const WATags = { 6 | LIST_EMPTY: 0, 7 | STREAM_END: 2, 8 | DICTIONARY_0: 236, 9 | DICTIONARY_1: 237, 10 | DICTIONARY_2: 238, 11 | DICTIONARY_3: 239, 12 | LIST_8: 248, 13 | LIST_16: 249, 14 | JID_PAIR: 250, 15 | HEX_8: 251, 16 | BINARY_8: 252, 17 | BINARY_20: 253, 18 | BINARY_32: 254, 19 | NIBBLE_8: 255, 20 | SINGLE_BYTE_MAX: 256, 21 | PACKED_MAX: 254 22 | }; 23 | export const WAMetrics = { 24 | DEBUG_LOG: 1, 25 | QUERY_RESUME: 2, 26 | QUERY_RECEIPT: 3, 27 | QUERY_MEDIA: 4, 28 | QUERY_CHAT: 5, 29 | QUERY_CONTACTS: 6, 30 | QUERY_MESSAGES: 7, 31 | PRESENCE: 8, 32 | PRESENCE_SUBSCRIBE: 9, 33 | GROUP: 10, 34 | READ: 11, 35 | CHAT: 12, 36 | RECEIVED: 13, 37 | PIC: 14, 38 | STATUS: 15, 39 | MESSAGE: 16, 40 | QUERY_ACTIONS: 17, 41 | BLOCK: 18, 42 | QUERY_GROUP: 19, 43 | QUERY_PREVIEW: 20, 44 | QUERY_EMOJI: 21, 45 | QUERY_MESSAGE_INFO: 22, 46 | SPAM: 23, 47 | QUERY_SEARCH: 24, 48 | QUERY_IDENTITY: 25, 49 | QUERY_URL: 26, 50 | PROFILE: 27, 51 | CONTACT: 28, 52 | QUERY_VCARD: 29, 53 | QUERY_STATUS: 30, 54 | QUERY_STATUS_UPDATE: 31, 55 | PRIVACY_STATUS: 32, 56 | QUERY_LIVE_LOCATIONS: 33, 57 | LIVE_LOCATION: 34, 58 | QUERY_VNAME: 35, 59 | QUERY_LABELS: 36, 60 | CALL: 37, 61 | QUERY_CALL: 38, 62 | QUERY_QUICK_REPLIES: 39, 63 | QUERY_CALL_OFFER: 40, 64 | QUERY_RESPONSE: 41, 65 | QUERY_STICKER_PACKS: 42, 66 | QUERY_STICKERS: 43, 67 | ADD_OR_REMOVE_LABELS: 44, 68 | QUERY_NEXT_LABEL_COLOR: 45, 69 | QUERY_LABEL_PALETTE: 46, 70 | CREATE_OR_DELETE_LABELS: 47, 71 | EDIT_LABELS: 48 72 | }; 73 | export const WAFlags = { 74 | IGNORE: 1 << 7, 75 | ACK_REQUEST: 1 << 6, 76 | AVAILABLE: 1 << 5, 77 | NOT_AVAILABLE: 1 << 4, 78 | EXPIRES: 1 << 3, 79 | SKIP_OFFLINE: 1 << 2 80 | }; 81 | export const WAMediaAppInfo = { 82 | image: "WhatsApp Image Keys", 83 | sticker: "WhatsApp Image Keys", 84 | video: "WhatsApp Video Keys", 85 | audio: "WhatsApp Audio Keys", 86 | document: "WhatsApp Document Keys" 87 | }; 88 | 89 | export const WASingleByteTokens = [ 90 | "", 91 | "", 92 | "", 93 | "200", 94 | "400", 95 | "404", 96 | "500", 97 | "501", 98 | "502", 99 | "action", 100 | "add", 101 | "after", 102 | "archive", 103 | "author", 104 | "available", 105 | "battery", 106 | "before", 107 | "body", 108 | "broadcast", 109 | "chat", 110 | "clear", 111 | "code", 112 | "composing", 113 | "contacts", 114 | "count", 115 | "create", 116 | "debug", 117 | "delete", 118 | "demote", 119 | "duplicate", 120 | "encoding", 121 | "error", 122 | "false", 123 | "filehash", 124 | "from", 125 | "g.us", 126 | "group", 127 | "groups_v2", 128 | "height", 129 | "id", 130 | "image", 131 | "in", 132 | "index", 133 | "invis", 134 | "item", 135 | "jid", 136 | "kind", 137 | "last", 138 | "leave", 139 | "live", 140 | "log", 141 | "media", 142 | "message", 143 | "mimetype", 144 | "missing", 145 | "modify", 146 | "name", 147 | "notification", 148 | "notify", 149 | "out", 150 | "owner", 151 | "participant", 152 | "paused", 153 | "picture", 154 | "played", 155 | "presence", 156 | "preview", 157 | "promote", 158 | "query", 159 | "raw", 160 | "read", 161 | "receipt", 162 | "received", 163 | "recipient", 164 | "recording", 165 | "relay", 166 | "remove", 167 | "response", 168 | "resume", 169 | "retry", 170 | "s.whatsapp.net", 171 | "seconds", 172 | "set", 173 | "size", 174 | "status", 175 | "subject", 176 | "subscribe", 177 | "t", 178 | "text", 179 | "to", 180 | "true", 181 | "type", 182 | "unarchive", 183 | "unavailable", 184 | "url", 185 | "user", 186 | "value", 187 | "web", 188 | "width", 189 | "mute", 190 | "read_only", 191 | "admin", 192 | "creator", 193 | "short", 194 | "update", 195 | "powersave", 196 | "checksum", 197 | "epoch", 198 | "block", 199 | "previous", 200 | "409", 201 | "replaced", 202 | "reason", 203 | "spam", 204 | "modify_tag", 205 | "message_info", 206 | "delivery", 207 | "emoji", 208 | "title", 209 | "description", 210 | "canonical-url", 211 | "matched-text", 212 | "star", 213 | "unstar", 214 | "media_key", 215 | "filename", 216 | "identity", 217 | "unread", 218 | "page", 219 | "page_count", 220 | "search", 221 | "media_message", 222 | "security", 223 | "call_log", 224 | "profile", 225 | "ciphertext", 226 | "invite", 227 | "gif", 228 | "vcard", 229 | "frequent", 230 | "privacy", 231 | "blacklist", 232 | "whitelist", 233 | "verify", 234 | "location", 235 | "document", 236 | "elapsed", 237 | "revoke_invite", 238 | "expiration", 239 | "unsubscribe", 240 | "disable", 241 | "vname", 242 | "old_jid", 243 | "new_jid", 244 | "announcement", 245 | "locked", 246 | "prop", 247 | "label", 248 | "color", 249 | "call", 250 | "offer", 251 | "call-id" 252 | ]; 253 | export const WADoubleByteTokens = []; 254 | 255 | const specPath = resolvePath(__dirname, "../../spec/def.proto"); 256 | 257 | export class WAWebMessageInfo { 258 | static async decode(data: Uint8Array) { 259 | return await protobuf.load(specPath).then(root => { 260 | const msgType = root.lookupType("proto.WebMessageInfo")!; 261 | const msg = msgType.decode(data); 262 | return msg.toJSON(); 263 | }); 264 | } 265 | 266 | static async encode(msg: { [k: string]: any }) { 267 | return await protobuf.load(specPath).then(root => { 268 | const msgType = root.lookupType("proto.WebMessageInfo")!; 269 | const data = msgType.fromObject(msg); 270 | return msgType.encode(data).finish(); 271 | }); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist", /* Redirect output structure to the directory. */ 16 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | }, 63 | "include": ["src"] 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/whatsappBinaryWriter.ts: -------------------------------------------------------------------------------- 1 | import { WATags, WASingleByteTokens, WAFlags } from "./whatsappTokens"; 2 | import { WANode, whatsappReadBinary } from "./whatsappBinaryReader"; 3 | 4 | class WABinaryWriter { 5 | data: number[] = []; 6 | 7 | getData() { 8 | return Uint8Array.from(this.data); 9 | } 10 | 11 | pushByte(value: number) { 12 | this.data.push(value); 13 | } 14 | 15 | pushBytes(bytes: number[]) { 16 | this.data = this.data.concat(bytes); 17 | } 18 | 19 | pushIntN(value: number, n: number, littleEndian = false) { 20 | for (let i = 0; i < n; i++) { 21 | const currShift = littleEndian ? i : n - i - 1; 22 | this.data.push((value >> (currShift * 8)) & 0xff); 23 | } 24 | } 25 | 26 | pushInt8(value: number) { 27 | this.pushIntN(value, 1); 28 | } 29 | 30 | pushInt16(value: number) { 31 | this.pushIntN(value, 2); 32 | } 33 | 34 | pushInt20(value: number) { 35 | this.pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff]); 36 | } 37 | 38 | pushInt32(value: number) { 39 | this.pushIntN(value, 4); 40 | } 41 | 42 | pushInt64(value: number) { 43 | this.pushIntN(value, 8); 44 | } 45 | 46 | pushString(str: string) { 47 | this.pushBytes(Array.from(new TextEncoder().encode(str))); 48 | } 49 | 50 | writeByteLength(length: number) { 51 | if (length >= Number.MAX_VALUE) { 52 | throw new Error(`string too large to encode (len = ${length})`); 53 | } 54 | 55 | if (length >= 1 << 20) { 56 | this.pushByte(WATags.BINARY_32); 57 | this.pushInt32(length); 58 | } else if (length >= 256) { 59 | this.pushByte(WATags.BINARY_20); 60 | this.pushInt20(length); 61 | } else { 62 | this.pushByte(WATags.BINARY_8); 63 | this.pushByte(length); 64 | } 65 | } 66 | 67 | writeNode(node: WAMessageNode | null) { 68 | if (!node) { 69 | return; 70 | } 71 | 72 | if (!node.description && !node.content) { 73 | throw new Error("Invalid node"); 74 | } 75 | 76 | const numAttributes = node.attributes 77 | ? Object.keys(node.attributes).filter(key => node.attributes![key]).length 78 | : 0; 79 | 80 | this.writeListStart(2 * numAttributes + 1 + (node.content ? 1 : 0)); 81 | this.writeString(node.description); 82 | this.writeAttributes(node.attributes); 83 | this.writeChildren(node.content!); 84 | } 85 | 86 | writeString(token: string, i = false) { 87 | if (!i && token === "c.us") { 88 | this.writeToken(WASingleByteTokens.indexOf("s.whatsapp.net")); 89 | return; 90 | } 91 | 92 | const tokenIndex = WASingleByteTokens.indexOf(token); 93 | 94 | if (tokenIndex === -1) { 95 | const jidSepIndex = token.indexOf("@"); 96 | 97 | if (jidSepIndex < 1) { 98 | this.writeStringRaw(token); 99 | } else { 100 | this.writeJid( 101 | token.slice(0, jidSepIndex), 102 | token.slice(jidSepIndex + 1) 103 | ); 104 | } 105 | } else if (tokenIndex < WATags.SINGLE_BYTE_MAX) { 106 | this.writeToken(tokenIndex); 107 | } else { 108 | const singleByteOverflow = tokenIndex - WATags.SINGLE_BYTE_MAX; 109 | const dictionaryIndex = singleByteOverflow >> 8; 110 | 111 | if (dictionaryIndex < 0 || dictionaryIndex > 3) { 112 | throw new Error( 113 | `Double byte dictionary token out of range ${token} ${tokenIndex}` 114 | ); 115 | } 116 | 117 | this.writeToken(WATags.DICTIONARY_0 + dictionaryIndex); 118 | this.writeToken(singleByteOverflow % 256); 119 | } 120 | } 121 | 122 | writeStringRaw(value: string) { 123 | this.writeByteLength(value.length); 124 | this.pushString(value); 125 | } 126 | 127 | writeJid(jidLeft: string, jidRight: string) { 128 | this.pushByte(WATags.JID_PAIR); 129 | 130 | if (jidLeft && jidLeft.length > 0) { 131 | this.writeString(jidLeft); 132 | } else { 133 | this.writeToken(WATags.LIST_EMPTY); 134 | } 135 | this.writeString(jidRight); 136 | } 137 | 138 | writeToken(token: number) { 139 | if (token < WASingleByteTokens.length) { 140 | this.pushByte(token); 141 | } else if (token <= 500) { 142 | throw new Error("Invalid token"); 143 | } 144 | } 145 | 146 | writeAttributes(attrs: WANode["attributes"]) { 147 | if (!attrs) { 148 | return; 149 | } 150 | 151 | for (const key in attrs) { 152 | if (attrs[key]) { 153 | this.writeString(key); 154 | this.writeString(attrs[key]!); 155 | } 156 | } 157 | } 158 | 159 | writeChildren(children: string | Uint8Array | WAMessageNode[] | undefined) { 160 | if (children) { 161 | if (typeof children === "string") { 162 | this.writeString(children as string, true); 163 | } else if (children instanceof Uint8Array) { 164 | this.writeByteLength(children.length); 165 | this.pushBytes(Array.from(children as Uint8Array)); 166 | } else if (Array.isArray(children)) { 167 | this.writeListStart((children as WAMessageNode[]).length); 168 | 169 | for (const child of children) { 170 | this.writeNode(child); 171 | } 172 | } else { 173 | throw new Error("Invalid children"); 174 | } 175 | } 176 | } 177 | 178 | writeListStart(listSize: number) { 179 | if (listSize === 0) { 180 | this.pushByte(WATags.LIST_EMPTY); 181 | } else if (listSize < 256) { 182 | this.pushBytes([WATags.LIST_8, listSize]); 183 | } else { 184 | this.pushBytes([WATags.LIST_16, listSize]); 185 | } 186 | } 187 | 188 | writePackedBytes(value: string) { 189 | try { 190 | this.writePackedBytesImpl(value, WATags.NIBBLE_8); 191 | } catch { 192 | this.writePackedBytesImpl(value, WATags.HEX_8); 193 | } 194 | } 195 | 196 | writePackedBytesImpl(value: string, dataType: number) { 197 | const numBytes = value.length; 198 | 199 | if (numBytes > WATags.PACKED_MAX) { 200 | throw new Error(`Too many bytes to nibble encode, length: ${numBytes}`); 201 | } 202 | 203 | this.pushByte(dataType); 204 | this.pushByte((numBytes % 2 > 0 ? 128 : 0) | Math.ceil(numBytes / 2)); 205 | 206 | for (let i = 0; i < Math.floor(numBytes / 2); i++) { 207 | this.pushByte( 208 | this.packBytePair(dataType, value[2 * i], value[2 * i + 1]) 209 | ); 210 | } 211 | 212 | if (numBytes % 2 !== 0) { 213 | this.pushByte(this.packBytePair(dataType, value[numBytes - 1], "\x00")); 214 | } 215 | } 216 | 217 | packBytePair(packType: number, part1: string, part2: string) { 218 | if (packType === WATags.NIBBLE_8) { 219 | return (this.packNibble(part1) << 4) | this.packNibble(part2); 220 | } else if (packType === WATags.HEX_8) { 221 | return (this.packHex(part1) << 4) | this.packHex(part2); 222 | } else { 223 | throw new Error(`Invalid byte pack type: ${packType}`); 224 | } 225 | } 226 | 227 | packNibble(value: string) { 228 | if (value >= "0" && value <= "9") { 229 | return parseInt(value); 230 | } else if (value === "-") { 231 | return 10; 232 | } else if (value === ".") { 233 | return 11; 234 | } else if (value === "\x00") { 235 | return 15; 236 | } 237 | throw new Error(`Invalid byte to pack as nibble ${value}`); 238 | } 239 | 240 | packHex(value: string) { 241 | if ( 242 | (value >= "0" && value <= "9") || 243 | (value >= "A" && value <= "F") || 244 | (value >= "a" && value <= "f") 245 | ) { 246 | return parseInt(value, 16); 247 | } else if (value === "\x00") { 248 | return 15; 249 | } 250 | throw new Error(`Invalid byte to pack as hex: ${value}`); 251 | } 252 | } 253 | 254 | export interface WAMessageNode { 255 | description: WANode["description"]; 256 | attributes?: WANode["attributes"]; 257 | content?: Uint8Array | WAMessageNode[]; 258 | } 259 | 260 | export async function whatsappWriteBinary(node: WAMessageNode) { 261 | const stream = new WABinaryWriter(); 262 | stream.writeNode(node); 263 | return stream.getData(); 264 | } 265 | -------------------------------------------------------------------------------- /src/utils/whatsappBinaryReader.ts: -------------------------------------------------------------------------------- 1 | import { 2 | WATags, 3 | WASingleByteTokens, 4 | WADoubleByteTokens, 5 | WAWebMessageInfo 6 | } from "./whatsappTokens"; 7 | import { concatIntArray } from "./arrays"; 8 | 9 | export interface WANode { 10 | description: string; 11 | attributes?: { [k: string]: string | null }; 12 | content?: WANode[] | Uint8Array; 13 | } 14 | 15 | export class WABinaryReader { 16 | data: Uint8Array; 17 | index: number; 18 | 19 | constructor(data: Uint8Array) { 20 | this.data = data; 21 | this.index = 0; 22 | } 23 | 24 | checkEOS(length: number) { 25 | if (this.index + length > this.data.length) { 26 | throw Error("End of stream reached"); 27 | } 28 | } 29 | 30 | readByte() { 31 | this.checkEOS(1); 32 | const ret = this.data[this.index]; 33 | this.index++; 34 | return ret; 35 | } 36 | 37 | readIntN(n: number, littleEndian: boolean = false) { 38 | this.checkEOS(n); 39 | let ret = 0; 40 | 41 | for (let i = 0; i < n; i++) { 42 | const currShift = littleEndian ? i : n - 1 - i; 43 | ret |= this.data[this.index + i] << (currShift * 8); 44 | } 45 | this.index += n; 46 | return ret; 47 | } 48 | 49 | readInt16(littleEndian: boolean = false) { 50 | return this.readIntN(2, littleEndian); 51 | } 52 | 53 | readInt20() { 54 | this.checkEOS(3); 55 | const ret = 56 | ((this.data[this.index] & 15) << 16) + 57 | (this.data[this.index + 1] << 8) + 58 | this.data[this.index + 2]; 59 | this.index += 3; 60 | return ret; 61 | } 62 | 63 | readInt32(littleEndian: boolean = false) { 64 | return this.readIntN(4, littleEndian); 65 | } 66 | 67 | readInt64(littleEndian: boolean = false) { 68 | return this.readIntN(8, littleEndian); 69 | } 70 | 71 | readPacked8(tag: number) { 72 | const startByte = this.readByte(); 73 | let ret = ""; 74 | 75 | for (let i = 0; i < (startByte & 127); i++) { 76 | const currByte = this.readByte(); 77 | ret += 78 | this.unpackByte(tag, (currByte & 0xf0) >> 4)! + 79 | this.unpackByte(tag, currByte & 0x0f)!; 80 | } 81 | 82 | if (startByte >> 7 !== 0) { 83 | ret = ret.substr(0, ret.length - 1); 84 | } 85 | 86 | return ret; 87 | } 88 | 89 | unpackByte(tag: number, value: number) { 90 | if (tag === WATags.NIBBLE_8) { 91 | return this.unpackNibble(value); 92 | } else if (tag === WATags.HEX_8) { 93 | return this.unpackHex(value); 94 | } 95 | throw new Error(`unpackByte with unknown tag ${tag}`); 96 | } 97 | 98 | unpackNibble(value: number) { 99 | if (value >= 0 && value <= 9) { 100 | return "" + value; 101 | } else if (value === 10) { 102 | return "-"; 103 | } else if (value === 11) { 104 | return "."; 105 | } else if (value === 15) { 106 | return "\0"; 107 | } 108 | throw new Error(`Invalid nibble to unpack: ${value}`); 109 | } 110 | 111 | unpackHex(value: number) { 112 | if (value < 0 || value > 15) { 113 | throw new Error(`Invalid hex to unpack: ${value}`); 114 | } 115 | if (value < 10) { 116 | return "" + value; 117 | } 118 | return String.fromCharCode("A".charCodeAt(0) + value - 10); 119 | } 120 | 121 | isListTag(tag: number) { 122 | return ( 123 | tag === WATags.LIST_EMPTY || 124 | tag === WATags.LIST_8 || 125 | tag === WATags.LIST_16 126 | ); 127 | } 128 | 129 | readListSize(tag: number) { 130 | if (tag === WATags.LIST_EMPTY) { 131 | return 0; 132 | } else if (tag === WATags.LIST_8) { 133 | return this.readByte(); 134 | } else if (tag === WATags.LIST_16) { 135 | return this.readInt16(); 136 | } 137 | throw new Error( 138 | `Invalid tag for list size: ${tag} at position ${this.index}` 139 | ); 140 | } 141 | 142 | readString(tag: number): Uint8Array { 143 | if (tag >= 3 && tag <= WASingleByteTokens.length) { 144 | const token = this.getToken(tag); 145 | 146 | if (token === "s.whatsapp.net") { 147 | return new TextEncoder().encode("c.us"); 148 | } 149 | return new TextEncoder().encode(token); 150 | } 151 | 152 | if ( 153 | tag === WATags.DICTIONARY_0 || 154 | tag === WATags.DICTIONARY_1 || 155 | tag === WATags.DICTIONARY_2 || 156 | tag === WATags.DICTIONARY_3 157 | ) { 158 | return this.getDoubleToken(tag - WATags.DICTIONARY_0, this.readByte()); 159 | } else if (tag === WATags.LIST_EMPTY) { 160 | return new TextEncoder().encode(""); 161 | } else if (tag === WATags.BINARY_8) { 162 | return this.readStringFromChars(this.readByte()); 163 | } else if (tag === WATags.BINARY_20) { 164 | return this.readStringFromChars(this.readInt20()); 165 | } else if (tag === WATags.BINARY_32) { 166 | return this.readStringFromChars(this.readInt32()); 167 | } else if (tag === WATags.JID_PAIR) { 168 | const i = this.readString(this.readByte()); 169 | const j = this.readString(this.readByte()); 170 | 171 | if (!i || !j) { 172 | throw new Error(`Invalid jid pair: ${i}, ${j}`); 173 | } 174 | return concatIntArray(i, new TextEncoder().encode("@"), j); 175 | } else if (tag === WATags.NIBBLE_8 || tag === WATags.HEX_8) { 176 | return new TextEncoder().encode(this.readPacked8(tag)); 177 | } else { 178 | throw new Error(`Invalid string with tag ${tag}`); 179 | } 180 | } 181 | 182 | readStringFromChars(length: number) { 183 | this.checkEOS(length); 184 | const ret = this.data.slice(this.index, this.index + length); 185 | this.index += length; 186 | return ret; 187 | } 188 | 189 | readAttributes(n: number) { 190 | let ret: { [k: string]: string } = {}; 191 | if (n === 0) { 192 | return; 193 | } 194 | for (let i = 0; i < n; i++) { 195 | const index = new TextDecoder().decode(this.readString(this.readByte())!); 196 | ret[index] = new TextDecoder().decode(this.readString(this.readByte())); 197 | } 198 | return ret; 199 | } 200 | 201 | readList(tag: number) { 202 | const listSize = this.readListSize(tag); 203 | let ret = []; 204 | 205 | for (let i = 0; i < listSize; i++) { 206 | ret.push(this.readNode()); 207 | } 208 | 209 | return ret; 210 | } 211 | 212 | readNode(): WANode { 213 | const listSize = this.readListSize(this.readByte()); 214 | const descrTag = this.readByte(); 215 | 216 | if (descrTag === WATags.STREAM_END) { 217 | throw new Error("Unexpected stream end"); 218 | } 219 | 220 | const description = new TextDecoder().decode(this.readString(descrTag)); 221 | 222 | if (listSize === 0 || description === "") { 223 | throw new Error("Invalid node"); 224 | } 225 | 226 | const attributes = this.readAttributes((listSize - 1) >> 1); 227 | 228 | if (listSize % 2 === 1) { 229 | return { description, attributes }; 230 | } 231 | 232 | const tag = this.readByte(); 233 | 234 | if (this.isListTag(tag)) { 235 | return { 236 | description, 237 | attributes, 238 | content: this.readList(tag) 239 | }; 240 | } else if (tag === WATags.BINARY_8) { 241 | return { 242 | description, 243 | attributes, 244 | content: this.readBytes(this.readByte()) 245 | }; 246 | } else if (tag === WATags.BINARY_20) { 247 | return { 248 | description, 249 | attributes, 250 | content: this.readBytes(this.readInt20()) 251 | }; 252 | } else if (tag === WATags.BINARY_32) { 253 | return { 254 | description, 255 | attributes, 256 | content: this.readBytes(this.readInt32()) 257 | }; 258 | } 259 | return { 260 | description, 261 | attributes, 262 | content: this.readString(tag)! 263 | }; 264 | } 265 | 266 | readBytes(n: number) { 267 | let ret = []; 268 | 269 | for (let i = 0; i < n; i++) { 270 | ret.push(this.readByte()); 271 | } 272 | 273 | return Uint8Array.from(ret); 274 | } 275 | 276 | getToken(index: number) { 277 | if (index < 3 || index >= WASingleByteTokens.length) { 278 | throw new Error(`Invalid token index: ${index}`); 279 | } 280 | return WASingleByteTokens[index]; 281 | } 282 | 283 | getDoubleToken(index1: number, index2: number) { 284 | const n = 256 * index1 + index2; 285 | 286 | if (n < 0 || n >= WADoubleByteTokens.length) { 287 | throw new Error(`Invalid token index: ${n}`); 288 | } 289 | return WADoubleByteTokens[n]; 290 | } 291 | } 292 | 293 | export async function whatsappReadMessageArray(msgs: WANode["content"]) { 294 | if (!Array.isArray(msgs)) { 295 | return msgs; 296 | } 297 | 298 | let ret = []; 299 | 300 | for (const msg of msgs!) { 301 | ret.push( 302 | msg.description === "message" 303 | ? await WAWebMessageInfo.decode(msg.content as Uint8Array) 304 | : msg 305 | ); 306 | } 307 | 308 | return ret; 309 | } 310 | 311 | export async function whatsappReadBinary( 312 | data: Uint8Array, 313 | withMessages: boolean = false 314 | ) { 315 | const node = new WABinaryReader(data).readNode(); 316 | 317 | if ( 318 | withMessages && 319 | node && 320 | node.attributes && 321 | node.content && 322 | node.description === "action" 323 | ) { 324 | node.content = await whatsappReadMessageArray(node.content).then( 325 | res => res as WANode["content"] 326 | ); 327 | } 328 | return node; 329 | } 330 | -------------------------------------------------------------------------------- /spec/def.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package proto; 3 | 4 | message HydratedQuickReplyButton { 5 | optional string displayText = 1; 6 | optional string id = 2; 7 | } 8 | 9 | message HydratedURLButton { 10 | optional string displayText = 1; 11 | optional string url = 2; 12 | } 13 | 14 | message HydratedCallButton { 15 | optional string displayText = 1; 16 | optional string phoneNumber = 2; 17 | } 18 | 19 | message HydratedTemplateButton { 20 | optional uint32 index = 4; 21 | oneof hydratedButton { 22 | HydratedQuickReplyButton quickReplyButton = 1; 23 | HydratedURLButton urlButton = 2; 24 | HydratedCallButton callButton = 3; 25 | } 26 | } 27 | 28 | message QuickReplyButton { 29 | optional HighlyStructuredMessage displayText = 1; 30 | optional string id = 2; 31 | } 32 | 33 | message URLButton { 34 | optional HighlyStructuredMessage displayText = 1; 35 | optional HighlyStructuredMessage url = 2; 36 | } 37 | 38 | message CallButton { 39 | optional HighlyStructuredMessage displayText = 1; 40 | optional HighlyStructuredMessage phoneNumber = 2; 41 | } 42 | 43 | message TemplateButton { 44 | optional uint32 index = 4; 45 | oneof button { 46 | QuickReplyButton quickReplyButton = 1; 47 | URLButton urlButton = 2; 48 | CallButton callButton = 3; 49 | } 50 | } 51 | 52 | message Location { 53 | optional double degreesLatitude = 1; 54 | optional double degreesLongitude = 2; 55 | optional string name = 3; 56 | } 57 | 58 | message Point { 59 | optional int32 xDeprecated = 1; 60 | optional int32 yDeprecated = 2; 61 | optional double x = 3; 62 | optional double y = 4; 63 | } 64 | 65 | message InteractiveAnnotation { 66 | repeated Point polygonVertices = 1; 67 | oneof action { 68 | Location location = 2; 69 | } 70 | } 71 | 72 | message AdReplyInfo { 73 | optional string advertiserName = 1; 74 | enum AD_REPLY_INFO_MEDIATYPE { 75 | NONE = 0; 76 | IMAGE = 1; 77 | VIDEO = 2; 78 | } 79 | optional AD_REPLY_INFO_MEDIATYPE mediaType = 2; 80 | optional bytes jpegThumbnail = 16; 81 | optional string caption = 17; 82 | } 83 | 84 | message ContextInfo { 85 | optional string stanzaId = 1; 86 | optional string participant = 2; 87 | optional Message quotedMessage = 3; 88 | optional string remoteJid = 4; 89 | repeated string mentionedJid = 15; 90 | optional string conversionSource = 18; 91 | optional bytes conversionData = 19; 92 | optional uint32 conversionDelaySeconds = 20; 93 | optional uint32 forwardingScore = 21; 94 | optional bool isForwarded = 22; 95 | optional AdReplyInfo quotedAd = 23; 96 | optional MessageKey placeholderKey = 24; 97 | optional uint32 expiration = 25; 98 | optional int64 ephemeralSettingTimestamp = 26; 99 | } 100 | 101 | message SenderKeyDistributionMessage { 102 | optional string groupId = 1; 103 | optional bytes axolotlSenderKeyDistributionMessage = 2; 104 | } 105 | 106 | message ImageMessage { 107 | optional string url = 1; 108 | optional string mimetype = 2; 109 | optional string caption = 3; 110 | optional bytes fileSha256 = 4; 111 | optional uint64 fileLength = 5; 112 | optional uint32 height = 6; 113 | optional uint32 width = 7; 114 | optional bytes mediaKey = 8; 115 | optional bytes fileEncSha256 = 9; 116 | repeated InteractiveAnnotation interactiveAnnotations = 10; 117 | optional string directPath = 11; 118 | optional int64 mediaKeyTimestamp = 12; 119 | optional bytes jpegThumbnail = 16; 120 | optional ContextInfo contextInfo = 17; 121 | optional bytes firstScanSidecar = 18; 122 | optional uint32 firstScanLength = 19; 123 | optional uint32 experimentGroupId = 20; 124 | optional bytes scansSidecar = 21; 125 | repeated uint32 scanLengths = 22; 126 | optional bytes midQualityFileSha256 = 23; 127 | optional bytes midQualityFileEncSha256 = 24; 128 | } 129 | 130 | message ContactMessage { 131 | optional string displayName = 1; 132 | optional string vcard = 16; 133 | optional ContextInfo contextInfo = 17; 134 | } 135 | 136 | message LocationMessage { 137 | optional double degreesLatitude = 1; 138 | optional double degreesLongitude = 2; 139 | optional string name = 3; 140 | optional string address = 4; 141 | optional string url = 5; 142 | optional bool isLive = 6; 143 | optional uint32 accuracyInMeters = 7; 144 | optional float speedInMps = 8; 145 | optional uint32 degreesClockwiseFromMagneticNorth = 9; 146 | optional string comment = 11; 147 | optional bytes jpegThumbnail = 16; 148 | optional ContextInfo contextInfo = 17; 149 | } 150 | 151 | message ExtendedTextMessage { 152 | optional string text = 1; 153 | optional string matchedText = 2; 154 | optional string canonicalUrl = 4; 155 | optional string description = 5; 156 | optional string title = 6; 157 | optional fixed32 textArgb = 7; 158 | optional fixed32 backgroundArgb = 8; 159 | enum EXTENDED_TEXT_MESSAGE_FONTTYPE { 160 | SANS_SERIF = 0; 161 | SERIF = 1; 162 | NORICAN_REGULAR = 2; 163 | BRYNDAN_WRITE = 3; 164 | BEBASNEUE_REGULAR = 4; 165 | OSWALD_HEAVY = 5; 166 | } 167 | optional EXTENDED_TEXT_MESSAGE_FONTTYPE font = 9; 168 | enum EXTENDED_TEXT_MESSAGE_PREVIEWTYPE { 169 | NONE = 0; 170 | VIDEO = 1; 171 | } 172 | optional EXTENDED_TEXT_MESSAGE_PREVIEWTYPE previewType = 10; 173 | optional bytes jpegThumbnail = 16; 174 | optional ContextInfo contextInfo = 17; 175 | optional bool doNotPlayInline = 18; 176 | } 177 | 178 | message DocumentMessage { 179 | optional string url = 1; 180 | optional string mimetype = 2; 181 | optional string title = 3; 182 | optional bytes fileSha256 = 4; 183 | optional uint64 fileLength = 5; 184 | optional uint32 pageCount = 6; 185 | optional bytes mediaKey = 7; 186 | optional string fileName = 8; 187 | optional bytes fileEncSha256 = 9; 188 | optional string directPath = 10; 189 | optional int64 mediaKeyTimestamp = 11; 190 | optional bytes jpegThumbnail = 16; 191 | optional ContextInfo contextInfo = 17; 192 | } 193 | 194 | message AudioMessage { 195 | optional string url = 1; 196 | optional string mimetype = 2; 197 | optional bytes fileSha256 = 3; 198 | optional uint64 fileLength = 4; 199 | optional uint32 seconds = 5; 200 | optional bool ptt = 6; 201 | optional bytes mediaKey = 7; 202 | optional bytes fileEncSha256 = 8; 203 | optional string directPath = 9; 204 | optional int64 mediaKeyTimestamp = 10; 205 | optional ContextInfo contextInfo = 17; 206 | optional bytes streamingSidecar = 18; 207 | } 208 | 209 | message VideoMessage { 210 | optional string url = 1; 211 | optional string mimetype = 2; 212 | optional bytes fileSha256 = 3; 213 | optional uint64 fileLength = 4; 214 | optional uint32 seconds = 5; 215 | optional bytes mediaKey = 6; 216 | optional string caption = 7; 217 | optional bool gifPlayback = 8; 218 | optional uint32 height = 9; 219 | optional uint32 width = 10; 220 | optional bytes fileEncSha256 = 11; 221 | repeated InteractiveAnnotation interactiveAnnotations = 12; 222 | optional string directPath = 13; 223 | optional int64 mediaKeyTimestamp = 14; 224 | optional bytes jpegThumbnail = 16; 225 | optional ContextInfo contextInfo = 17; 226 | optional bytes streamingSidecar = 18; 227 | enum VIDEO_MESSAGE_ATTRIBUTION { 228 | NONE = 0; 229 | GIPHY = 1; 230 | TENOR = 2; 231 | } 232 | optional VIDEO_MESSAGE_ATTRIBUTION gifAttribution = 19; 233 | } 234 | 235 | message Call { 236 | optional bytes callKey = 1; 237 | } 238 | 239 | message Chat { 240 | optional string displayName = 1; 241 | optional string id = 2; 242 | } 243 | 244 | message ProtocolMessage { 245 | optional MessageKey key = 1; 246 | enum PROTOCOL_MESSAGE_TYPE { 247 | REVOKE = 0; 248 | EPHEMERAL_SETTING = 3; 249 | EPHEMERAL_SYNC_RESPONSE = 4; 250 | HISTORY_SYNC_NOTIFICATION = 5; 251 | } 252 | optional PROTOCOL_MESSAGE_TYPE type = 2; 253 | optional uint32 ephemeralExpiration = 4; 254 | optional int64 ephemeralSettingTimestamp = 5; 255 | optional HistorySyncNotification historySyncNotification = 6; 256 | } 257 | 258 | message HistorySyncNotification { 259 | optional bytes fileSha256 = 1; 260 | optional uint64 fileLength = 2; 261 | optional bytes mediaKey = 3; 262 | optional bytes fileEncSha256 = 4; 263 | optional string directPath = 5; 264 | enum HISTORY_SYNC_NOTIFICATION_HISTORYSYNCTYPE { 265 | INITIAL_BOOTSTRAP = 0; 266 | INITIAL_STATUS_V3 = 1; 267 | FULL = 2; 268 | RECENT = 3; 269 | } 270 | optional HISTORY_SYNC_NOTIFICATION_HISTORYSYNCTYPE syncType = 6; 271 | optional uint32 chunkOrder = 7; 272 | } 273 | 274 | message ContactsArrayMessage { 275 | optional string displayName = 1; 276 | repeated ContactMessage contacts = 2; 277 | optional ContextInfo contextInfo = 17; 278 | } 279 | 280 | message HSMCurrency { 281 | optional string currencyCode = 1; 282 | optional int64 amount1000 = 2; 283 | } 284 | 285 | message HSMDateTimeComponent { 286 | enum HSM_DATE_TIME_COMPONENT_DAYOFWEEKTYPE { 287 | MONDAY = 1; 288 | TUESDAY = 2; 289 | WEDNESDAY = 3; 290 | THURSDAY = 4; 291 | FRIDAY = 5; 292 | SATURDAY = 6; 293 | SUNDAY = 7; 294 | } 295 | optional HSM_DATE_TIME_COMPONENT_DAYOFWEEKTYPE dayOfWeek = 1; 296 | optional uint32 year = 2; 297 | optional uint32 month = 3; 298 | optional uint32 dayOfMonth = 4; 299 | optional uint32 hour = 5; 300 | optional uint32 minute = 6; 301 | enum HSM_DATE_TIME_COMPONENT_CALENDARTYPE { 302 | GREGORIAN = 1; 303 | SOLAR_HIJRI = 2; 304 | } 305 | optional HSM_DATE_TIME_COMPONENT_CALENDARTYPE calendar = 7; 306 | } 307 | 308 | message HSMDateTimeUnixEpoch { 309 | optional int64 timestamp = 1; 310 | } 311 | 312 | message HSMDateTime { 313 | oneof datetimeOneof { 314 | HSMDateTimeComponent component = 1; 315 | HSMDateTimeUnixEpoch unixEpoch = 2; 316 | } 317 | } 318 | 319 | message HSMLocalizableParameter { 320 | optional string default = 1; 321 | oneof paramOneof { 322 | HSMCurrency currency = 2; 323 | HSMDateTime dateTime = 3; 324 | } 325 | } 326 | 327 | message HighlyStructuredMessage { 328 | optional string namespace = 1; 329 | optional string elementName = 2; 330 | repeated string params = 3; 331 | optional string fallbackLg = 4; 332 | optional string fallbackLc = 5; 333 | repeated HSMLocalizableParameter localizableParams = 6; 334 | optional string deterministicLg = 7; 335 | optional string deterministicLc = 8; 336 | optional TemplateMessage hydratedHsm = 9; 337 | } 338 | 339 | message SendPaymentMessage { 340 | optional Message noteMessage = 2; 341 | optional MessageKey requestMessageKey = 3; 342 | } 343 | 344 | message RequestPaymentMessage { 345 | optional Message noteMessage = 4; 346 | optional string currencyCodeIso4217 = 1; 347 | optional uint64 amount1000 = 2; 348 | optional string requestFrom = 3; 349 | optional int64 expiryTimestamp = 5; 350 | } 351 | 352 | message DeclinePaymentRequestMessage { 353 | optional MessageKey key = 1; 354 | } 355 | 356 | message CancelPaymentRequestMessage { 357 | optional MessageKey key = 1; 358 | } 359 | 360 | message LiveLocationMessage { 361 | optional double degreesLatitude = 1; 362 | optional double degreesLongitude = 2; 363 | optional uint32 accuracyInMeters = 3; 364 | optional float speedInMps = 4; 365 | optional uint32 degreesClockwiseFromMagneticNorth = 5; 366 | optional string caption = 6; 367 | optional int64 sequenceNumber = 7; 368 | optional uint32 timeOffset = 8; 369 | optional bytes jpegThumbnail = 16; 370 | optional ContextInfo contextInfo = 17; 371 | } 372 | 373 | message StickerMessage { 374 | optional string url = 1; 375 | optional bytes fileSha256 = 2; 376 | optional bytes fileEncSha256 = 3; 377 | optional bytes mediaKey = 4; 378 | optional string mimetype = 5; 379 | optional uint32 height = 6; 380 | optional uint32 width = 7; 381 | optional string directPath = 8; 382 | optional uint64 fileLength = 9; 383 | optional int64 mediaKeyTimestamp = 10; 384 | optional uint32 firstFrameLength = 11; 385 | optional bytes firstFrameSidecar = 12; 386 | optional bool isAnimated = 13; 387 | optional bytes pngThumbnail = 16; 388 | optional ContextInfo contextInfo = 17; 389 | } 390 | 391 | message FourRowTemplate { 392 | optional HighlyStructuredMessage content = 6; 393 | optional HighlyStructuredMessage footer = 7; 394 | repeated TemplateButton buttons = 8; 395 | oneof title { 396 | DocumentMessage documentMessage = 1; 397 | HighlyStructuredMessage highlyStructuredMessage = 2; 398 | ImageMessage imageMessage = 3; 399 | VideoMessage videoMessage = 4; 400 | LocationMessage locationMessage = 5; 401 | } 402 | } 403 | 404 | message HydratedFourRowTemplate { 405 | optional string hydratedContentText = 6; 406 | optional string hydratedFooterText = 7; 407 | repeated HydratedTemplateButton hydratedButtons = 8; 408 | optional string templateId = 9; 409 | oneof title { 410 | DocumentMessage documentMessage = 1; 411 | string hydratedTitleText = 2; 412 | ImageMessage imageMessage = 3; 413 | VideoMessage videoMessage = 4; 414 | LocationMessage locationMessage = 5; 415 | } 416 | } 417 | 418 | message TemplateMessage { 419 | optional ContextInfo contextInfo = 3; 420 | optional HydratedFourRowTemplate hydratedTemplate = 4; 421 | oneof format { 422 | FourRowTemplate fourRowTemplate = 1; 423 | HydratedFourRowTemplate hydratedFourRowTemplate = 2; 424 | } 425 | } 426 | 427 | message TemplateButtonReplyMessage { 428 | optional string selectedId = 1; 429 | optional string selectedDisplayText = 2; 430 | optional ContextInfo contextInfo = 3; 431 | optional uint32 selectedIndex = 4; 432 | } 433 | 434 | message CatalogSnapshot { 435 | optional ImageMessage catalogImage = 1; 436 | optional string title = 2; 437 | optional string description = 3; 438 | } 439 | 440 | message ProductSnapshot { 441 | optional ImageMessage productImage = 1; 442 | optional string productId = 2; 443 | optional string title = 3; 444 | optional string description = 4; 445 | optional string currencyCode = 5; 446 | optional int64 priceAmount1000 = 6; 447 | optional string retailerId = 7; 448 | optional string url = 8; 449 | optional uint32 productImageCount = 9; 450 | optional string firstImageId = 11; 451 | } 452 | 453 | message ProductMessage { 454 | optional ProductSnapshot product = 1; 455 | optional string businessOwnerJid = 2; 456 | optional CatalogSnapshot catalog = 4; 457 | optional ContextInfo contextInfo = 17; 458 | } 459 | 460 | message GroupInviteMessage { 461 | optional string groupJid = 1; 462 | optional string inviteCode = 2; 463 | optional int64 inviteExpiration = 3; 464 | optional string groupName = 4; 465 | optional bytes jpegThumbnail = 5; 466 | optional string caption = 6; 467 | optional ContextInfo contextInfo = 7; 468 | } 469 | 470 | message DeviceSentMessage { 471 | optional string destinationJid = 1; 472 | optional Message message = 2; 473 | } 474 | 475 | message DeviceSyncMessage { 476 | optional bytes serializedXmlBytes = 1; 477 | } 478 | 479 | message Message { 480 | optional string conversation = 1; 481 | optional SenderKeyDistributionMessage senderKeyDistributionMessage = 2; 482 | optional ImageMessage imageMessage = 3; 483 | optional ContactMessage contactMessage = 4; 484 | optional LocationMessage locationMessage = 5; 485 | optional ExtendedTextMessage extendedTextMessage = 6; 486 | optional DocumentMessage documentMessage = 7; 487 | optional AudioMessage audioMessage = 8; 488 | optional VideoMessage videoMessage = 9; 489 | optional Call call = 10; 490 | optional Chat chat = 11; 491 | optional ProtocolMessage protocolMessage = 12; 492 | optional ContactsArrayMessage contactsArrayMessage = 13; 493 | optional HighlyStructuredMessage highlyStructuredMessage = 14; 494 | optional SenderKeyDistributionMessage fastRatchetKeySenderKeyDistributionMessage = 15; 495 | optional SendPaymentMessage sendPaymentMessage = 16; 496 | optional LiveLocationMessage liveLocationMessage = 18; 497 | optional RequestPaymentMessage requestPaymentMessage = 22; 498 | optional DeclinePaymentRequestMessage declinePaymentRequestMessage = 23; 499 | optional CancelPaymentRequestMessage cancelPaymentRequestMessage = 24; 500 | optional TemplateMessage templateMessage = 25; 501 | optional StickerMessage stickerMessage = 26; 502 | optional GroupInviteMessage groupInviteMessage = 28; 503 | optional TemplateButtonReplyMessage templateButtonReplyMessage = 29; 504 | optional ProductMessage productMessage = 30; 505 | optional DeviceSentMessage deviceSentMessage = 31; 506 | optional DeviceSyncMessage deviceSyncMessage = 32; 507 | } 508 | 509 | message MessageKey { 510 | optional string remoteJid = 1; 511 | optional bool fromMe = 2; 512 | optional string id = 3; 513 | optional string participant = 4; 514 | } 515 | 516 | message WebFeatures { 517 | enum WEB_FEATURES_FLAG { 518 | NOT_STARTED = 0; 519 | FORCE_UPGRADE = 1; 520 | DEVELOPMENT = 2; 521 | PRODUCTION = 3; 522 | } 523 | optional WEB_FEATURES_FLAG labelsDisplay = 1; 524 | optional WEB_FEATURES_FLAG voipIndividualOutgoing = 2; 525 | optional WEB_FEATURES_FLAG groupsV3 = 3; 526 | optional WEB_FEATURES_FLAG groupsV3Create = 4; 527 | optional WEB_FEATURES_FLAG changeNumberV2 = 5; 528 | optional WEB_FEATURES_FLAG queryStatusV3Thumbnail = 6; 529 | optional WEB_FEATURES_FLAG liveLocations = 7; 530 | optional WEB_FEATURES_FLAG queryVname = 8; 531 | optional WEB_FEATURES_FLAG voipIndividualIncoming = 9; 532 | optional WEB_FEATURES_FLAG quickRepliesQuery = 10; 533 | optional WEB_FEATURES_FLAG payments = 11; 534 | optional WEB_FEATURES_FLAG stickerPackQuery = 12; 535 | optional WEB_FEATURES_FLAG liveLocationsFinal = 13; 536 | optional WEB_FEATURES_FLAG labelsEdit = 14; 537 | optional WEB_FEATURES_FLAG mediaUpload = 15; 538 | optional WEB_FEATURES_FLAG mediaUploadRichQuickReplies = 18; 539 | optional WEB_FEATURES_FLAG vnameV2 = 19; 540 | optional WEB_FEATURES_FLAG videoPlaybackUrl = 20; 541 | optional WEB_FEATURES_FLAG statusRanking = 21; 542 | optional WEB_FEATURES_FLAG voipIndividualVideo = 22; 543 | optional WEB_FEATURES_FLAG thirdPartyStickers = 23; 544 | optional WEB_FEATURES_FLAG frequentlyForwardedSetting = 24; 545 | optional WEB_FEATURES_FLAG groupsV4JoinPermission = 25; 546 | optional WEB_FEATURES_FLAG recentStickers = 26; 547 | optional WEB_FEATURES_FLAG catalog = 27; 548 | optional WEB_FEATURES_FLAG starredStickers = 28; 549 | optional WEB_FEATURES_FLAG voipGroupCall = 29; 550 | optional WEB_FEATURES_FLAG templateMessage = 30; 551 | optional WEB_FEATURES_FLAG templateMessageInteractivity = 31; 552 | optional WEB_FEATURES_FLAG ephemeralMessages = 32; 553 | optional WEB_FEATURES_FLAG e2ENotificationSync = 33; 554 | optional WEB_FEATURES_FLAG recentStickersV2 = 34; 555 | } 556 | 557 | message TabletNotificationsInfo { 558 | optional uint64 timestamp = 2; 559 | optional uint32 unreadChats = 3; 560 | optional uint32 notifyMessageCount = 4; 561 | repeated NotificationMessageInfo notifyMessage = 5; 562 | } 563 | 564 | message NotificationMessageInfo { 565 | optional MessageKey key = 1; 566 | optional Message message = 2; 567 | optional uint64 messageTimestamp = 3; 568 | optional string participant = 4; 569 | } 570 | 571 | message WebNotificationsInfo { 572 | optional uint64 timestamp = 2; 573 | optional uint32 unreadChats = 3; 574 | optional uint32 notifyMessageCount = 4; 575 | repeated WebMessageInfo notifyMessages = 5; 576 | } 577 | 578 | message PaymentInfo { 579 | enum PAYMENT_INFO_CURRENCY { 580 | UNKNOWN_CURRENCY = 0; 581 | INR = 1; 582 | } 583 | optional PAYMENT_INFO_CURRENCY currencyDeprecated = 1; 584 | optional uint64 amount1000 = 2; 585 | optional string receiverJid = 3; 586 | enum PAYMENT_INFO_STATUS { 587 | UNKNOWN_STATUS = 0; 588 | PROCESSING = 1; 589 | SENT = 2; 590 | NEED_TO_ACCEPT = 3; 591 | COMPLETE = 4; 592 | COULD_NOT_COMPLETE = 5; 593 | REFUNDED = 6; 594 | EXPIRED = 7; 595 | REJECTED = 8; 596 | CANCELLED = 9; 597 | WAITING_FOR_PAYER = 10; 598 | WAITING = 11; 599 | } 600 | optional PAYMENT_INFO_STATUS status = 4; 601 | optional uint64 transactionTimestamp = 5; 602 | optional MessageKey requestMessageKey = 6; 603 | optional uint64 expiryTimestamp = 7; 604 | optional bool futureproofed = 8; 605 | optional string currency = 9; 606 | enum PAYMENT_INFO_TXNSTATUS { 607 | UNKNOWN = 0; 608 | PENDING_SETUP = 1; 609 | PENDING_RECEIVER_SETUP = 2; 610 | INIT = 3; 611 | SUCCESS = 4; 612 | COMPLETED = 5; 613 | FAILED = 6; 614 | FAILED_RISK = 7; 615 | FAILED_PROCESSING = 8; 616 | FAILED_RECEIVER_PROCESSING = 9; 617 | FAILED_DA = 10; 618 | FAILED_DA_FINAL = 11; 619 | REFUNDED_TXN = 12; 620 | REFUND_FAILED = 13; 621 | REFUND_FAILED_PROCESSING = 14; 622 | REFUND_FAILED_DA = 15; 623 | EXPIRED_TXN = 16; 624 | AUTH_CANCELED = 17; 625 | AUTH_CANCEL_FAILED_PROCESSING = 18; 626 | AUTH_CANCEL_FAILED = 19; 627 | COLLECT_INIT = 20; 628 | COLLECT_SUCCESS = 21; 629 | COLLECT_FAILED = 22; 630 | COLLECT_FAILED_RISK = 23; 631 | COLLECT_REJECTED = 24; 632 | COLLECT_EXPIRED = 25; 633 | COLLECT_CANCELED = 26; 634 | COLLECT_CANCELLING = 27; 635 | } 636 | optional PAYMENT_INFO_TXNSTATUS txnStatus = 10; 637 | } 638 | 639 | message WebMessageInfo { 640 | required MessageKey key = 1; 641 | optional Message message = 2; 642 | optional uint64 messageTimestamp = 3; 643 | enum WEB_MESSAGE_INFO_STATUS { 644 | ERROR = 0; 645 | PENDING = 1; 646 | SERVER_ACK = 2; 647 | DELIVERY_ACK = 3; 648 | READ = 4; 649 | PLAYED = 5; 650 | } 651 | optional WEB_MESSAGE_INFO_STATUS status = 4; 652 | optional string participant = 5; 653 | optional bool ignore = 16; 654 | optional bool starred = 17; 655 | optional bool broadcast = 18; 656 | optional string pushName = 19; 657 | optional bytes mediaCiphertextSha256 = 20; 658 | optional bool multicast = 21; 659 | optional bool urlText = 22; 660 | optional bool urlNumber = 23; 661 | enum WEB_MESSAGE_INFO_STUBTYPE { 662 | UNKNOWN = 0; 663 | REVOKE = 1; 664 | CIPHERTEXT = 2; 665 | FUTUREPROOF = 3; 666 | NON_VERIFIED_TRANSITION = 4; 667 | UNVERIFIED_TRANSITION = 5; 668 | VERIFIED_TRANSITION = 6; 669 | VERIFIED_LOW_UNKNOWN = 7; 670 | VERIFIED_HIGH = 8; 671 | VERIFIED_INITIAL_UNKNOWN = 9; 672 | VERIFIED_INITIAL_LOW = 10; 673 | VERIFIED_INITIAL_HIGH = 11; 674 | VERIFIED_TRANSITION_ANY_TO_NONE = 12; 675 | VERIFIED_TRANSITION_ANY_TO_HIGH = 13; 676 | VERIFIED_TRANSITION_HIGH_TO_LOW = 14; 677 | VERIFIED_TRANSITION_HIGH_TO_UNKNOWN = 15; 678 | VERIFIED_TRANSITION_UNKNOWN_TO_LOW = 16; 679 | VERIFIED_TRANSITION_LOW_TO_UNKNOWN = 17; 680 | VERIFIED_TRANSITION_NONE_TO_LOW = 18; 681 | VERIFIED_TRANSITION_NONE_TO_UNKNOWN = 19; 682 | GROUP_CREATE = 20; 683 | GROUP_CHANGE_SUBJECT = 21; 684 | GROUP_CHANGE_ICON = 22; 685 | GROUP_CHANGE_INVITE_LINK = 23; 686 | GROUP_CHANGE_DESCRIPTION = 24; 687 | GROUP_CHANGE_RESTRICT = 25; 688 | GROUP_CHANGE_ANNOUNCE = 26; 689 | GROUP_PARTICIPANT_ADD = 27; 690 | GROUP_PARTICIPANT_REMOVE = 28; 691 | GROUP_PARTICIPANT_PROMOTE = 29; 692 | GROUP_PARTICIPANT_DEMOTE = 30; 693 | GROUP_PARTICIPANT_INVITE = 31; 694 | GROUP_PARTICIPANT_LEAVE = 32; 695 | GROUP_PARTICIPANT_CHANGE_NUMBER = 33; 696 | BROADCAST_CREATE = 34; 697 | BROADCAST_ADD = 35; 698 | BROADCAST_REMOVE = 36; 699 | GENERIC_NOTIFICATION = 37; 700 | E2E_IDENTITY_CHANGED = 38; 701 | E2E_ENCRYPTED = 39; 702 | CALL_MISSED_VOICE = 40; 703 | CALL_MISSED_VIDEO = 41; 704 | INDIVIDUAL_CHANGE_NUMBER = 42; 705 | GROUP_DELETE = 43; 706 | GROUP_ANNOUNCE_MODE_MESSAGE_BOUNCE = 44; 707 | CALL_MISSED_GROUP_VOICE = 45; 708 | CALL_MISSED_GROUP_VIDEO = 46; 709 | PAYMENT_CIPHERTEXT = 47; 710 | PAYMENT_FUTUREPROOF = 48; 711 | PAYMENT_TRANSACTION_STATUS_UPDATE_FAILED = 49; 712 | PAYMENT_TRANSACTION_STATUS_UPDATE_REFUNDED = 50; 713 | PAYMENT_TRANSACTION_STATUS_UPDATE_REFUND_FAILED = 51; 714 | PAYMENT_TRANSACTION_STATUS_RECEIVER_PENDING_SETUP = 52; 715 | PAYMENT_TRANSACTION_STATUS_RECEIVER_SUCCESS_AFTER_HICCUP = 53; 716 | PAYMENT_ACTION_ACCOUNT_SETUP_REMINDER = 54; 717 | PAYMENT_ACTION_SEND_PAYMENT_REMINDER = 55; 718 | PAYMENT_ACTION_SEND_PAYMENT_INVITATION = 56; 719 | PAYMENT_ACTION_REQUEST_DECLINED = 57; 720 | PAYMENT_ACTION_REQUEST_EXPIRED = 58; 721 | PAYMENT_ACTION_REQUEST_CANCELLED = 59; 722 | BIZ_VERIFIED_TRANSITION_TOP_TO_BOTTOM = 60; 723 | BIZ_VERIFIED_TRANSITION_BOTTOM_TO_TOP = 61; 724 | BIZ_INTRO_TOP = 62; 725 | BIZ_INTRO_BOTTOM = 63; 726 | BIZ_NAME_CHANGE = 64; 727 | BIZ_MOVE_TO_CONSUMER_APP = 65; 728 | BIZ_TWO_TIER_MIGRATION_TOP = 66; 729 | BIZ_TWO_TIER_MIGRATION_BOTTOM = 67; 730 | OVERSIZED = 68; 731 | GROUP_CHANGE_NO_FREQUENTLY_FORWARDED = 69; 732 | GROUP_V4_ADD_INVITE_SENT = 70; 733 | GROUP_PARTICIPANT_ADD_REQUEST_JOIN = 71; 734 | CHANGE_EPHEMERAL_SETTING = 72; 735 | } 736 | optional WEB_MESSAGE_INFO_STUBTYPE messageStubType = 24; 737 | optional bool clearMedia = 25; 738 | repeated string messageStubParameters = 26; 739 | optional uint32 duration = 27; 740 | repeated string labels = 28; 741 | optional PaymentInfo paymentInfo = 29; 742 | optional LiveLocationMessage finalLiveLocation = 30; 743 | optional PaymentInfo quotedPaymentInfo = 31; 744 | optional uint64 ephemeralStartTimestamp = 32; 745 | optional uint32 ephemeralDuration = 33; 746 | } 747 | 748 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": 6 | version "1.1.2" 7 | resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" 8 | integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= 9 | 10 | "@protobufjs/base64@^1.1.2": 11 | version "1.1.2" 12 | resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" 13 | integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== 14 | 15 | "@protobufjs/codegen@^2.0.4": 16 | version "2.0.4" 17 | resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" 18 | integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== 19 | 20 | "@protobufjs/eventemitter@^1.1.0": 21 | version "1.1.0" 22 | resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" 23 | integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= 24 | 25 | "@protobufjs/fetch@^1.1.0": 26 | version "1.1.0" 27 | resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" 28 | integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= 29 | dependencies: 30 | "@protobufjs/aspromise" "^1.1.1" 31 | "@protobufjs/inquire" "^1.1.0" 32 | 33 | "@protobufjs/float@^1.0.2": 34 | version "1.0.2" 35 | resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" 36 | integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= 37 | 38 | "@protobufjs/inquire@^1.1.0": 39 | version "1.1.0" 40 | resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" 41 | integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= 42 | 43 | "@protobufjs/path@^1.1.2": 44 | version "1.1.2" 45 | resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" 46 | integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= 47 | 48 | "@protobufjs/pool@^1.1.0": 49 | version "1.1.0" 50 | resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" 51 | integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= 52 | 53 | "@protobufjs/utf8@^1.1.0": 54 | version "1.1.0" 55 | resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" 56 | integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= 57 | 58 | "@types/long@^4.0.0": 59 | version "4.0.0" 60 | resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" 61 | integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q== 62 | 63 | "@types/node-fetch@^2.5.2": 64 | version "2.5.2" 65 | resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.2.tgz#76906dea5b3d6901e50e63e15249c9bcd6e9676e" 66 | integrity sha512-djYYKmdNRSBtL1x4CiE9UJb9yZhwtI1VC+UxZD0psNznrUj80ywsxKlEGAE+QL1qvLjPbfb24VosjkYM6W4RSQ== 67 | dependencies: 68 | "@types/node" "*" 69 | 70 | "@types/node@*", "@types/node@^12.7.3": 71 | version "12.7.3" 72 | resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.3.tgz#27b3f40addaf2f580459fdb405222685542f907a" 73 | integrity sha512-3SiLAIBkDWDg6vFo0+5YJyHPWU9uwu40Qe+v+0MH8wRKYBimHvvAOyk3EzMrD/TrIlLYfXrqDqrg913PynrMJQ== 74 | 75 | "@types/node@^10.1.0": 76 | version "10.14.17" 77 | resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce" 78 | integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ== 79 | 80 | "@types/qrcode@^1.3.4": 81 | version "1.3.4" 82 | resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.3.4.tgz#984d97bb72caa558d470158701081ccb712f616b" 83 | integrity sha512-aILE5yvKaqQXlY0YPMEYwK/KwdD43fwQTyagj0ffBBTQj8h//085Zp8LUrOnZ9FT69x64f5UgDo0EueY4BPAdg== 84 | dependencies: 85 | "@types/node" "*" 86 | 87 | "@types/sharp@^0.22.3": 88 | version "0.22.3" 89 | resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.22.3.tgz#db64657398be95c4f4d70d5a81a849bfada6422d" 90 | integrity sha512-4fP7DGvNMlk6qy3rtsC24rLttg7oa0yVugFXPRmD8kGxHLvoqiRTVUbcK3qB0eYK6pO9mBWkzAAL7lV1htig3A== 91 | dependencies: 92 | "@types/node" "*" 93 | 94 | "@types/ws@^6.0.3": 95 | version "6.0.3" 96 | resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.3.tgz#b772375ba59d79066561c8d87500144d674ba6b3" 97 | integrity sha512-yBTM0P05Tx9iXGq00BbJPo37ox68R5vaGTXivs6RGh/BQ6QP5zqZDGWdAO6JbRE/iR1l80xeGAwCQS2nMV9S/w== 98 | dependencies: 99 | "@types/node" "*" 100 | 101 | ansi-regex@^2.0.0: 102 | version "2.1.1" 103 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 104 | integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= 105 | 106 | ansi-regex@^3.0.0: 107 | version "3.0.0" 108 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 109 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 110 | 111 | ansi-regex@^4.1.0: 112 | version "4.1.0" 113 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" 114 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== 115 | 116 | ansi-styles@^3.2.0: 117 | version "3.2.1" 118 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 119 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 120 | dependencies: 121 | color-convert "^1.9.0" 122 | 123 | aproba@^1.0.3: 124 | version "1.2.0" 125 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" 126 | integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== 127 | 128 | are-we-there-yet@~1.1.2: 129 | version "1.1.5" 130 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" 131 | integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== 132 | dependencies: 133 | delegates "^1.0.0" 134 | readable-stream "^2.0.6" 135 | 136 | asynckit@^0.4.0: 137 | version "0.4.0" 138 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 139 | integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 140 | 141 | base64-js@^1.0.2: 142 | version "1.3.1" 143 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" 144 | integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== 145 | 146 | bl@^3.0.0: 147 | version "3.0.0" 148 | resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" 149 | integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== 150 | dependencies: 151 | readable-stream "^3.0.1" 152 | 153 | buffer-alloc-unsafe@^1.1.0: 154 | version "1.1.0" 155 | resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" 156 | integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== 157 | 158 | buffer-alloc@^1.2.0: 159 | version "1.2.0" 160 | resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" 161 | integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== 162 | dependencies: 163 | buffer-alloc-unsafe "^1.1.0" 164 | buffer-fill "^1.0.0" 165 | 166 | buffer-fill@^1.0.0: 167 | version "1.0.0" 168 | resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" 169 | integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= 170 | 171 | buffer-from@^1.1.1: 172 | version "1.1.1" 173 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 174 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 175 | 176 | buffer@^5.4.3: 177 | version "5.5.0" 178 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.5.0.tgz#9c3caa3d623c33dd1c7ef584b89b88bf9c9bc1ce" 179 | integrity sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww== 180 | dependencies: 181 | base64-js "^1.0.2" 182 | ieee754 "^1.1.4" 183 | 184 | camelcase@^5.0.0: 185 | version "5.3.1" 186 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 187 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 188 | 189 | chownr@^1.1.1: 190 | version "1.1.3" 191 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" 192 | integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== 193 | 194 | chownr@^1.1.3: 195 | version "1.1.4" 196 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" 197 | integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== 198 | 199 | cliui@^5.0.0: 200 | version "5.0.0" 201 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" 202 | integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== 203 | dependencies: 204 | string-width "^3.1.0" 205 | strip-ansi "^5.2.0" 206 | wrap-ansi "^5.1.0" 207 | 208 | code-point-at@^1.0.0: 209 | version "1.1.0" 210 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 211 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= 212 | 213 | color-convert@^1.9.0, color-convert@^1.9.1: 214 | version "1.9.3" 215 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 216 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 217 | dependencies: 218 | color-name "1.1.3" 219 | 220 | color-name@1.1.3: 221 | version "1.1.3" 222 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 223 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 224 | 225 | color-name@^1.0.0: 226 | version "1.1.4" 227 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 228 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 229 | 230 | color-string@^1.5.2: 231 | version "1.5.3" 232 | resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" 233 | integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== 234 | dependencies: 235 | color-name "^1.0.0" 236 | simple-swizzle "^0.2.2" 237 | 238 | color@^3.1.2: 239 | version "3.1.2" 240 | resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" 241 | integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== 242 | dependencies: 243 | color-convert "^1.9.1" 244 | color-string "^1.5.2" 245 | 246 | combined-stream@^1.0.6: 247 | version "1.0.8" 248 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 249 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 250 | dependencies: 251 | delayed-stream "~1.0.0" 252 | 253 | console-control-strings@^1.0.0, console-control-strings@~1.1.0: 254 | version "1.1.0" 255 | resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 256 | integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= 257 | 258 | core-util-is@~1.0.0: 259 | version "1.0.2" 260 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 261 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 262 | 263 | curve25519-js@^0.0.4: 264 | version "0.0.4" 265 | resolved "https://registry.yarnpkg.com/curve25519-js/-/curve25519-js-0.0.4.tgz#e6ad967e8cd284590d657bbfc90d8b50e49ba060" 266 | integrity sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w== 267 | 268 | decamelize@^1.2.0: 269 | version "1.2.0" 270 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 271 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 272 | 273 | decompress-response@^4.2.0: 274 | version "4.2.1" 275 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" 276 | integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== 277 | dependencies: 278 | mimic-response "^2.0.0" 279 | 280 | deep-extend@^0.6.0: 281 | version "0.6.0" 282 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 283 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 284 | 285 | delayed-stream@~1.0.0: 286 | version "1.0.0" 287 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 288 | integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= 289 | 290 | delegates@^1.0.0: 291 | version "1.0.0" 292 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" 293 | integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= 294 | 295 | detect-libc@^1.0.3: 296 | version "1.0.3" 297 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" 298 | integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= 299 | 300 | dijkstrajs@^1.0.1: 301 | version "1.0.1" 302 | resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.1.tgz#d3cd81221e3ea40742cfcde556d4e99e98ddc71b" 303 | integrity sha1-082BIh4+pAdCz83lVtTpnpjdxxs= 304 | 305 | emoji-regex@^7.0.1: 306 | version "7.0.3" 307 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 308 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== 309 | 310 | end-of-stream@^1.1.0, end-of-stream@^1.4.1: 311 | version "1.4.4" 312 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 313 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 314 | dependencies: 315 | once "^1.4.0" 316 | 317 | expand-template@^2.0.3: 318 | version "2.0.3" 319 | resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" 320 | integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== 321 | 322 | find-up@^3.0.0: 323 | version "3.0.0" 324 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" 325 | integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== 326 | dependencies: 327 | locate-path "^3.0.0" 328 | 329 | form-data@^2.5.1: 330 | version "2.5.1" 331 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" 332 | integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== 333 | dependencies: 334 | asynckit "^0.4.0" 335 | combined-stream "^1.0.6" 336 | mime-types "^2.1.12" 337 | 338 | fs-constants@^1.0.0: 339 | version "1.0.0" 340 | resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" 341 | integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== 342 | 343 | fs-minipass@^2.0.0: 344 | version "2.1.0" 345 | resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" 346 | integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== 347 | dependencies: 348 | minipass "^3.0.0" 349 | 350 | gauge@~2.7.3: 351 | version "2.7.4" 352 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" 353 | integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= 354 | dependencies: 355 | aproba "^1.0.3" 356 | console-control-strings "^1.0.0" 357 | has-unicode "^2.0.0" 358 | object-assign "^4.1.0" 359 | signal-exit "^3.0.0" 360 | string-width "^1.0.1" 361 | strip-ansi "^3.0.1" 362 | wide-align "^1.1.0" 363 | 364 | get-caller-file@^2.0.1: 365 | version "2.0.5" 366 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 367 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 368 | 369 | github-from-package@0.0.0: 370 | version "0.0.0" 371 | resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" 372 | integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= 373 | 374 | has-unicode@^2.0.0: 375 | version "2.0.1" 376 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" 377 | integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= 378 | 379 | ieee754@^1.1.4: 380 | version "1.1.13" 381 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" 382 | integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== 383 | 384 | inherits@^2.0.3, inherits@~2.0.3: 385 | version "2.0.4" 386 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 387 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 388 | 389 | ini@~1.3.0: 390 | version "1.3.5" 391 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" 392 | integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== 393 | 394 | is-arrayish@^0.3.1: 395 | version "0.3.2" 396 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" 397 | integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== 398 | 399 | is-fullwidth-code-point@^1.0.0: 400 | version "1.0.0" 401 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 402 | integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= 403 | dependencies: 404 | number-is-nan "^1.0.0" 405 | 406 | is-fullwidth-code-point@^2.0.0: 407 | version "2.0.0" 408 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 409 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 410 | 411 | isarray@^2.0.1: 412 | version "2.0.5" 413 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" 414 | integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== 415 | 416 | isarray@~1.0.0: 417 | version "1.0.0" 418 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 419 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 420 | 421 | locate-path@^3.0.0: 422 | version "3.0.0" 423 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 424 | integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== 425 | dependencies: 426 | p-locate "^3.0.0" 427 | path-exists "^3.0.0" 428 | 429 | long@^4.0.0: 430 | version "4.0.0" 431 | resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" 432 | integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== 433 | 434 | mime-db@1.40.0: 435 | version "1.40.0" 436 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" 437 | integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== 438 | 439 | mime-types@^2.1.12: 440 | version "2.1.24" 441 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" 442 | integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== 443 | dependencies: 444 | mime-db "1.40.0" 445 | 446 | mimic-response@^2.0.0: 447 | version "2.0.0" 448 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" 449 | integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== 450 | 451 | minimist@0.0.8: 452 | version "0.0.8" 453 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 454 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= 455 | 456 | minimist@^1.2.0: 457 | version "1.2.0" 458 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 459 | integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= 460 | 461 | minipass@^3.0.0: 462 | version "3.1.1" 463 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" 464 | integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w== 465 | dependencies: 466 | yallist "^4.0.0" 467 | 468 | minizlib@^2.1.0: 469 | version "2.1.0" 470 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3" 471 | integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA== 472 | dependencies: 473 | minipass "^3.0.0" 474 | yallist "^4.0.0" 475 | 476 | mkdirp@^0.5.1: 477 | version "0.5.1" 478 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 479 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 480 | dependencies: 481 | minimist "0.0.8" 482 | 483 | mkdirp@^1.0.3: 484 | version "1.0.3" 485 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" 486 | integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== 487 | 488 | napi-build-utils@^1.0.1: 489 | version "1.0.1" 490 | resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" 491 | integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== 492 | 493 | node-abi@^2.7.0: 494 | version "2.11.0" 495 | resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.11.0.tgz#b7dce18815057544a049be5ae75cd1fdc2e9ea59" 496 | integrity sha512-kuy/aEg75u40v378WRllQ4ZexaXJiCvB68D2scDXclp/I4cRq6togpbOoKhmN07tns9Zldu51NNERo0wehfX9g== 497 | dependencies: 498 | semver "^5.4.1" 499 | 500 | node-addon-api@^2.0.0: 501 | version "2.0.0" 502 | resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.0.tgz#f9afb8d777a91525244b01775ea0ddbe1125483b" 503 | integrity sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA== 504 | 505 | node-fetch@^2.6.0: 506 | version "2.6.0" 507 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" 508 | integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== 509 | 510 | noop-logger@^0.1.1: 511 | version "0.1.1" 512 | resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" 513 | integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= 514 | 515 | npmlog@^4.0.1, npmlog@^4.1.2: 516 | version "4.1.2" 517 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" 518 | integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== 519 | dependencies: 520 | are-we-there-yet "~1.1.2" 521 | console-control-strings "~1.1.0" 522 | gauge "~2.7.3" 523 | set-blocking "~2.0.0" 524 | 525 | number-is-nan@^1.0.0: 526 | version "1.0.1" 527 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 528 | integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= 529 | 530 | object-assign@^4.1.0: 531 | version "4.1.1" 532 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 533 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 534 | 535 | once@^1.3.1, once@^1.4.0: 536 | version "1.4.0" 537 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 538 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 539 | dependencies: 540 | wrappy "1" 541 | 542 | p-limit@^2.0.0: 543 | version "2.2.1" 544 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537" 545 | integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg== 546 | dependencies: 547 | p-try "^2.0.0" 548 | 549 | p-locate@^3.0.0: 550 | version "3.0.0" 551 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" 552 | integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== 553 | dependencies: 554 | p-limit "^2.0.0" 555 | 556 | p-try@^2.0.0: 557 | version "2.2.0" 558 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 559 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 560 | 561 | path-exists@^3.0.0: 562 | version "3.0.0" 563 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 564 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= 565 | 566 | pngjs@^3.3.0: 567 | version "3.4.0" 568 | resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" 569 | integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== 570 | 571 | prebuild-install@^5.3.3: 572 | version "5.3.3" 573 | resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e" 574 | integrity sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g== 575 | dependencies: 576 | detect-libc "^1.0.3" 577 | expand-template "^2.0.3" 578 | github-from-package "0.0.0" 579 | minimist "^1.2.0" 580 | mkdirp "^0.5.1" 581 | napi-build-utils "^1.0.1" 582 | node-abi "^2.7.0" 583 | noop-logger "^0.1.1" 584 | npmlog "^4.0.1" 585 | pump "^3.0.0" 586 | rc "^1.2.7" 587 | simple-get "^3.0.3" 588 | tar-fs "^2.0.0" 589 | tunnel-agent "^0.6.0" 590 | which-pm-runs "^1.0.0" 591 | 592 | process-nextick-args@~2.0.0: 593 | version "2.0.1" 594 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 595 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 596 | 597 | protobufjs@^6.8.9: 598 | version "6.8.9" 599 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.9.tgz#0b1adbcdaa983d369c3d9108a97c814edc030754" 600 | integrity sha512-j2JlRdUeL/f4Z6x4aU4gj9I2LECglC+5qR2TrWb193Tla1qfdaNQTZ8I27Pt7K0Ajmvjjpft7O3KWTGciz4gpw== 601 | dependencies: 602 | "@protobufjs/aspromise" "^1.1.2" 603 | "@protobufjs/base64" "^1.1.2" 604 | "@protobufjs/codegen" "^2.0.4" 605 | "@protobufjs/eventemitter" "^1.1.0" 606 | "@protobufjs/fetch" "^1.1.0" 607 | "@protobufjs/float" "^1.0.2" 608 | "@protobufjs/inquire" "^1.1.0" 609 | "@protobufjs/path" "^1.1.2" 610 | "@protobufjs/pool" "^1.1.0" 611 | "@protobufjs/utf8" "^1.1.0" 612 | "@types/long" "^4.0.0" 613 | "@types/node" "^10.1.0" 614 | long "^4.0.0" 615 | 616 | pump@^3.0.0: 617 | version "3.0.0" 618 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 619 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 620 | dependencies: 621 | end-of-stream "^1.1.0" 622 | once "^1.3.1" 623 | 624 | qrcode@^1.4.4: 625 | version "1.4.4" 626 | resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83" 627 | integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q== 628 | dependencies: 629 | buffer "^5.4.3" 630 | buffer-alloc "^1.2.0" 631 | buffer-from "^1.1.1" 632 | dijkstrajs "^1.0.1" 633 | isarray "^2.0.1" 634 | pngjs "^3.3.0" 635 | yargs "^13.2.4" 636 | 637 | rc@^1.2.7: 638 | version "1.2.8" 639 | resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" 640 | integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== 641 | dependencies: 642 | deep-extend "^0.6.0" 643 | ini "~1.3.0" 644 | minimist "^1.2.0" 645 | strip-json-comments "~2.0.1" 646 | 647 | readable-stream@^2.0.6: 648 | version "2.3.6" 649 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" 650 | integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== 651 | dependencies: 652 | core-util-is "~1.0.0" 653 | inherits "~2.0.3" 654 | isarray "~1.0.0" 655 | process-nextick-args "~2.0.0" 656 | safe-buffer "~5.1.1" 657 | string_decoder "~1.1.1" 658 | util-deprecate "~1.0.1" 659 | 660 | readable-stream@^3.0.1, readable-stream@^3.1.1: 661 | version "3.4.0" 662 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" 663 | integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== 664 | dependencies: 665 | inherits "^2.0.3" 666 | string_decoder "^1.1.1" 667 | util-deprecate "^1.0.1" 668 | 669 | require-directory@^2.1.1: 670 | version "2.1.1" 671 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 672 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 673 | 674 | require-main-filename@^2.0.0: 675 | version "2.0.0" 676 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 677 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 678 | 679 | safe-buffer@^5.0.1, safe-buffer@~5.2.0: 680 | version "5.2.0" 681 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" 682 | integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== 683 | 684 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 685 | version "5.1.2" 686 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 687 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 688 | 689 | semver@^5.4.1: 690 | version "5.7.1" 691 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 692 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 693 | 694 | semver@^7.1.3: 695 | version "7.1.3" 696 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6" 697 | integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA== 698 | 699 | set-blocking@^2.0.0, set-blocking@~2.0.0: 700 | version "2.0.0" 701 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 702 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 703 | 704 | sharp@^0.25.2: 705 | version "0.25.2" 706 | resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.25.2.tgz#f9003d73be50e9265e98f79f04fe53d8c66a3967" 707 | integrity sha512-l1GN0kFNtJr3U9i9pt7a+vo2Ij0xv4tTKDIPx8W6G9WELhPwrMyZZJKAAQNBSI785XB4uZfS5Wpz8C9jWV4AFQ== 708 | dependencies: 709 | color "^3.1.2" 710 | detect-libc "^1.0.3" 711 | node-addon-api "^2.0.0" 712 | npmlog "^4.1.2" 713 | prebuild-install "^5.3.3" 714 | semver "^7.1.3" 715 | simple-get "^3.1.0" 716 | tar "^6.0.1" 717 | tunnel-agent "^0.6.0" 718 | 719 | signal-exit@^3.0.0: 720 | version "3.0.2" 721 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 722 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= 723 | 724 | simple-concat@^1.0.0: 725 | version "1.0.0" 726 | resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" 727 | integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= 728 | 729 | simple-get@^3.0.3, simple-get@^3.1.0: 730 | version "3.1.0" 731 | resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" 732 | integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== 733 | dependencies: 734 | decompress-response "^4.2.0" 735 | once "^1.3.1" 736 | simple-concat "^1.0.0" 737 | 738 | simple-swizzle@^0.2.2: 739 | version "0.2.2" 740 | resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" 741 | integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= 742 | dependencies: 743 | is-arrayish "^0.3.1" 744 | 745 | string-width@^1.0.1: 746 | version "1.0.2" 747 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 748 | integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= 749 | dependencies: 750 | code-point-at "^1.0.0" 751 | is-fullwidth-code-point "^1.0.0" 752 | strip-ansi "^3.0.0" 753 | 754 | "string-width@^1.0.2 || 2": 755 | version "2.1.1" 756 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 757 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 758 | dependencies: 759 | is-fullwidth-code-point "^2.0.0" 760 | strip-ansi "^4.0.0" 761 | 762 | string-width@^3.0.0, string-width@^3.1.0: 763 | version "3.1.0" 764 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 765 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== 766 | dependencies: 767 | emoji-regex "^7.0.1" 768 | is-fullwidth-code-point "^2.0.0" 769 | strip-ansi "^5.1.0" 770 | 771 | string_decoder@^1.1.1: 772 | version "1.3.0" 773 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 774 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 775 | dependencies: 776 | safe-buffer "~5.2.0" 777 | 778 | string_decoder@~1.1.1: 779 | version "1.1.1" 780 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 781 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 782 | dependencies: 783 | safe-buffer "~5.1.0" 784 | 785 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: 786 | version "3.0.1" 787 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 788 | integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= 789 | dependencies: 790 | ansi-regex "^2.0.0" 791 | 792 | strip-ansi@^4.0.0: 793 | version "4.0.0" 794 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 795 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 796 | dependencies: 797 | ansi-regex "^3.0.0" 798 | 799 | strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: 800 | version "5.2.0" 801 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" 802 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 803 | dependencies: 804 | ansi-regex "^4.1.0" 805 | 806 | strip-json-comments@~2.0.1: 807 | version "2.0.1" 808 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 809 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 810 | 811 | tar-fs@^2.0.0: 812 | version "2.0.0" 813 | resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" 814 | integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== 815 | dependencies: 816 | chownr "^1.1.1" 817 | mkdirp "^0.5.1" 818 | pump "^3.0.0" 819 | tar-stream "^2.0.0" 820 | 821 | tar-stream@^2.0.0: 822 | version "2.1.0" 823 | resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" 824 | integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== 825 | dependencies: 826 | bl "^3.0.0" 827 | end-of-stream "^1.4.1" 828 | fs-constants "^1.0.0" 829 | inherits "^2.0.3" 830 | readable-stream "^3.1.1" 831 | 832 | tar@^6.0.1: 833 | version "6.0.1" 834 | resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.1.tgz#7b3bd6c313cb6e0153770108f8d70ac298607efa" 835 | integrity sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q== 836 | dependencies: 837 | chownr "^1.1.3" 838 | fs-minipass "^2.0.0" 839 | minipass "^3.0.0" 840 | minizlib "^2.1.0" 841 | mkdirp "^1.0.3" 842 | yallist "^4.0.0" 843 | 844 | tunnel-agent@^0.6.0: 845 | version "0.6.0" 846 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 847 | integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= 848 | dependencies: 849 | safe-buffer "^5.0.1" 850 | 851 | typescript@^3.6.2: 852 | version "3.6.2" 853 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.2.tgz#105b0f1934119dde543ac8eb71af3a91009efe54" 854 | integrity sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw== 855 | 856 | util-deprecate@^1.0.1, util-deprecate@~1.0.1: 857 | version "1.0.2" 858 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 859 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 860 | 861 | which-module@^2.0.0: 862 | version "2.0.0" 863 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 864 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 865 | 866 | which-pm-runs@^1.0.0: 867 | version "1.0.0" 868 | resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" 869 | integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= 870 | 871 | wide-align@^1.1.0: 872 | version "1.1.3" 873 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" 874 | integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== 875 | dependencies: 876 | string-width "^1.0.2 || 2" 877 | 878 | wrap-ansi@^5.1.0: 879 | version "5.1.0" 880 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" 881 | integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== 882 | dependencies: 883 | ansi-styles "^3.2.0" 884 | string-width "^3.0.0" 885 | strip-ansi "^5.0.0" 886 | 887 | wrappy@1: 888 | version "1.0.2" 889 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 890 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 891 | 892 | ws@^7.2.3: 893 | version "7.2.3" 894 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" 895 | integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== 896 | 897 | y18n@^4.0.0: 898 | version "4.0.0" 899 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" 900 | integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== 901 | 902 | yallist@^4.0.0: 903 | version "4.0.0" 904 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 905 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 906 | 907 | yargs-parser@^13.1.1: 908 | version "13.1.1" 909 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" 910 | integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== 911 | dependencies: 912 | camelcase "^5.0.0" 913 | decamelize "^1.2.0" 914 | 915 | yargs@^13.2.4: 916 | version "13.3.0" 917 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" 918 | integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== 919 | dependencies: 920 | cliui "^5.0.0" 921 | find-up "^3.0.0" 922 | get-caller-file "^2.0.1" 923 | require-directory "^2.1.1" 924 | require-main-filename "^2.0.0" 925 | set-blocking "^2.0.0" 926 | string-width "^3.0.0" 927 | which-module "^2.0.0" 928 | y18n "^4.0.0" 929 | yargs-parser "^13.1.1" 930 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { writeFile, readFile } from "fs"; 2 | import { resolve as resolvePath } from "path"; 3 | 4 | import sharp from "sharp"; 5 | import fetch from "node-fetch"; 6 | import WebSocket from "ws"; 7 | import crypto from "crypto"; 8 | import qrcode from "qrcode"; 9 | import { generateKeyPair, sharedKey } from "curve25519-js"; 10 | 11 | import { doesFileExist } from "./utils/path"; 12 | import { 13 | dataUrlToBuffer, 14 | HmacSha256, 15 | AESDecrypt, 16 | HKDF, 17 | randHex, 18 | AESEncrypt, 19 | Sha256 20 | } from "./utils/encryption"; 21 | import { whatsappReadBinary, WANode } from "./utils/whatsappBinaryReader"; 22 | import { 23 | whatsappWriteBinary, 24 | WAMessageNode 25 | } from "./utils/whatsappBinaryWriter"; 26 | import { concatIntArray, arraysEqual } from "./utils/arrays"; 27 | import { 28 | WAMediaAppInfo, 29 | WAWebMessageInfo, 30 | WAMetrics, 31 | WAFlags 32 | } from "./utils/whatsappTokens"; 33 | 34 | export interface WhatsAppLoginPayload { 35 | status: number; 36 | ref: string; 37 | ttl: 200000; 38 | update: boolean; 39 | curr: string; 40 | time: number; 41 | } 42 | 43 | export interface WhatsAppConnPayload { 44 | 0: "Conn"; 45 | 1: { 46 | battery: number; 47 | browserToken: string; 48 | clientToken: string; 49 | phone: { 50 | wa_version: string; 51 | mcc: string; 52 | mnc: string; 53 | os_version: string; 54 | device_manufacturer: string; 55 | device_model: string; 56 | os_build_number: string; 57 | }; 58 | platform: string; 59 | pushname: string; 60 | secret: string; 61 | serverToken: string; 62 | wid: string; 63 | }; 64 | } 65 | 66 | export interface WhatsAppStreamPayload { 67 | 0: "Stream"; 68 | 1: "update"; 69 | 2: boolean; 70 | 3: string; 71 | } 72 | 73 | export interface WhatsAppPropsPayload { 74 | 0: "Props"; 75 | 1: { 76 | imageMaxKBytes: 1024; 77 | maxParticipants: 257; 78 | videoMaxEdge: 960; 79 | }; 80 | } 81 | 82 | export interface WhatsAppUploadMediaURL { 83 | status: number; 84 | url: string; 85 | } 86 | 87 | export interface WhatsAppProfilePicPayload { 88 | eurl?: string; 89 | status?: number; 90 | tag: string; 91 | } 92 | 93 | export interface WhatsAppMediaConnPayload { 94 | status: number; 95 | media_conn: { 96 | auth: string; 97 | ttl: number; 98 | hosts: { 99 | hostname: string; 100 | ips: string[]; 101 | }[]; 102 | }; 103 | } 104 | 105 | export interface WhatsAppMediaUploadPayload { 106 | direct_path: string; 107 | url: string; 108 | } 109 | 110 | export interface WhatsAppGroupMetadataPayload { 111 | id: string; 112 | owner: string; 113 | subject: string; 114 | creation: number; 115 | participants: { 116 | id: string; 117 | isAdmin: boolean; 118 | isSuperAdmin: boolean; 119 | }[]; 120 | subjectTime: number; 121 | subjectOwner: string; 122 | } 123 | 124 | export interface WhatsAppAdminTestPayload { 125 | 0: "Pong"; 126 | 1: true; 127 | } 128 | 129 | export interface WAChat { 130 | id?: string | null; 131 | displayName?: string | null; 132 | count: string; 133 | jid: string; 134 | message?: string; 135 | modify_tag?: string; 136 | name: string; 137 | spam: string; 138 | read_only?: string; 139 | t: string; 140 | } 141 | 142 | export interface WAContact { 143 | jid: string; 144 | index?: string; 145 | name?: string; 146 | short?: string; 147 | verify?: string; 148 | vname?: string; 149 | notify?: string; 150 | } 151 | 152 | export interface WADecryptedMedia { 153 | type: "image" | "sticker" | "video" | "document" | "audio"; 154 | buffer: Buffer; 155 | gifPlayback: boolean; 156 | caption?: string; 157 | contextInfo?: WAContextInfo; 158 | } 159 | 160 | export interface WAMedia { 161 | fileEncSha256: Uint8Array; 162 | fileLength: number; 163 | fileSha256: Uint8Array; 164 | mediaKey: Uint8Array; 165 | mimetype: string; 166 | url: string; 167 | jpegThumbnail?: Buffer; 168 | pngThumbnail?: Buffer; 169 | gifPlayback?: boolean; 170 | seconds?: number; 171 | caption?: string; 172 | } 173 | 174 | export interface WAReceiveMedia { 175 | directPath: string; 176 | fileEncSha256: Uint8Array; 177 | fileLength: number; 178 | fileSha256: Uint8Array; 179 | mediaKey: string; 180 | mediaKeyTimestamp: number; 181 | mimetype: string; 182 | url: string; 183 | caption?: string; 184 | gifPlayback: boolean; 185 | contextInfo?: WAContextInfo; 186 | } 187 | 188 | export interface WAReceiveDocumentMessage extends WAReceiveMedia { 189 | fileName: string; 190 | } 191 | 192 | export interface WAContactMessage { 193 | displayName: string; 194 | vcard: string; 195 | contextInfo?: WAContextInfo; 196 | } 197 | 198 | export interface WAMessageKey { 199 | remoteJid?: string | null; 200 | fromMe?: boolean | null; 201 | id?: string | null; 202 | participant?: string | null; 203 | name?: string; 204 | } 205 | 206 | export interface WAExtendedTextMessage { 207 | /** ExtendedTextMessage text */ 208 | text?: string | null; 209 | 210 | /** ExtendedTextMessage matchedText */ 211 | matchedText?: string | null; 212 | 213 | /** ExtendedTextMessage canonicalUrl */ 214 | canonicalUrl?: string | null; 215 | 216 | /** ExtendedTextMessage description */ 217 | description?: string | null; 218 | 219 | /** ExtendedTextMessage title */ 220 | title?: string | null; 221 | 222 | /** ExtendedTextMessage textArgb */ 223 | textArgb?: number | null; 224 | 225 | /** ExtendedTextMessage backgroundArgb */ 226 | backgroundArgb?: number | null; 227 | 228 | /** ExtendedTextMessage font */ 229 | font?: 230 | | "SANS_SERIF" 231 | | "SERIF" 232 | | "NORICAN_REGULAR" 233 | | "BRYDAN_WRITE" 234 | | "BEBASNEUE_REGULAR" 235 | | "OSWALD_HEAVY" 236 | | null; 237 | 238 | /** ExtendedTextMessage previewType */ 239 | previewType?: "NONE" | "VIDEO" | null; 240 | 241 | /** ExtendedTextMessage jpegThumbnail */ 242 | jpegThumbnail?: Uint8Array | null; 243 | 244 | /** ExtendedTextMessage contextInfo */ 245 | contextInfo?: WAContextInfo | null; 246 | 247 | /** ExtendedTextMessage doNotPlayInline */ 248 | doNotPlayInline?: boolean | null; 249 | } 250 | 251 | export interface WAContextInfo { 252 | /** ContextInfo stanzaId */ 253 | stanzaId?: string | null; 254 | 255 | /** ContextInfo participant */ 256 | participant?: string | null; 257 | 258 | /** ContextInfo quotedMessage */ 259 | quotedMessage?: WAMessage | null; 260 | 261 | /** ContextInfo remoteJid */ 262 | remoteJid?: string | null; 263 | 264 | /** ContextInfo mentionedJid */ 265 | mentionedJid?: string[] | null; 266 | 267 | /** ContextInfo conversionSource */ 268 | conversionSource?: string | null; 269 | 270 | /** ContextInfo conversionData */ 271 | conversionData?: Uint8Array | null; 272 | 273 | /** ContextInfo conversionDelaySeconds */ 274 | conversionDelaySeconds?: number | null; 275 | 276 | /** ContextInfo forwardingScore */ 277 | forwardingScore?: number | null; 278 | 279 | /** ContextInfo isForwarded */ 280 | isForwarded?: boolean | null; 281 | 282 | /** ContextInfo placeholderKey */ 283 | placeholderKey?: WAMessageKey | null; 284 | 285 | /** ContextInfo expiration */ 286 | expiration?: number | null; 287 | 288 | /** ContextInfo ephemeralSettingTimestamp */ 289 | ephemeralSettingTimestamp?: number | null; 290 | } 291 | 292 | export interface WAProtocolMessage { 293 | key?: WAMessageKey | null; 294 | type?: 295 | | "REVOKE" 296 | | "EPHEMERAL_SETTING" 297 | | "EPHEMERAL_SYNC_RESPONSE" 298 | | "HISTORY_SYNC_NOTIFICATION" 299 | | null; 300 | ephemeralExpiration?: number | null; 301 | ephemeralSettingTimestamp?: number | null; 302 | historySyncNotification?: WAHistorySyncNotification | null; 303 | } 304 | 305 | export interface WAHistorySyncNotification { 306 | fileSha256?: Uint8Array | null; 307 | fileLength?: number | null; 308 | mediaKey?: Uint8Array | null; 309 | fileEncSha256?: Uint8Array | null; 310 | directPath?: string | null; 311 | syncType?: 312 | | "INITIAL_BOOTSTRAP" 313 | | "INITIAL_STATUS_V3" 314 | | "FULL" 315 | | "RECENT" 316 | | null; 317 | chunkOrder: number; 318 | } 319 | 320 | export interface WALocationMessage { 321 | degreesLatitude: number; 322 | degreesLongitude: number; 323 | name?: string; 324 | address?: string; 325 | url?: string; 326 | isLive?: boolean; 327 | accuracyInMeter?: number; 328 | speedInMps?: number; 329 | degreesClockwiseFromMagneticNorth?: number; 330 | comment?: string; 331 | jpegThumbnail?: string; 332 | contextInfo?: WAContextInfo; 333 | } 334 | 335 | export interface WAMessage { 336 | conversation?: string | null; 337 | extendedTextMessage?: WAExtendedTextMessage | null; 338 | decryptedMediaMessage?: WADecryptedMedia; 339 | documentMessage?: WAReceiveDocumentMessage | null; 340 | imageMessage?: WAReceiveMedia | null; 341 | audioMessage?: WAReceiveMedia | null; 342 | videoMessage?: WAReceiveMedia | null; 343 | stickerMessage?: WAReceiveMedia | null; 344 | contactMessage?: WAContactMessage | null; 345 | protocolMessage?: WAProtocolMessage | null; 346 | locationMessage?: WALocationMessage | null; 347 | groupInviteMessage?: WAGroupInviteMessage | null; 348 | } 349 | 350 | export interface WAGroupInviteMessage { 351 | groupJid: string; 352 | inviteCode: string; 353 | inviteExpiration: number; 354 | groupName: string; 355 | jpegThumbnail: Uint8Array; 356 | caption: string; 357 | contextInfo: WAContextInfo; 358 | } 359 | 360 | export interface WAWebMessage { 361 | key: WAMessageKey; 362 | message: WAMessage; 363 | messageTimestamp?: number | Long | null; 364 | status?: 365 | | "ERROR" 366 | | "PENDING" 367 | | "SERVER_ACK" 368 | | "DELIVERY_ACK" 369 | | "READ" 370 | | "PLAYED" 371 | | null; 372 | participant?: string | null; 373 | author?: string; 374 | } 375 | 376 | export interface WAStubMessage { 377 | key: WAMessageKey; 378 | messageTimestamp: number | Long; 379 | participant?: string | null; 380 | messageStubType: 381 | | "UNKNOWN" 382 | | "REVOKE" 383 | | "CIPHERTEXT" 384 | | "FUTUREPROOF" 385 | | "NON_VERIFIED_TRANSITION" 386 | | "UNVERIFIED_TRANSITION" 387 | | "VERIFIED_TRANSITION" 388 | | "VERIFIED_LOW_UNKNOWN" 389 | | "VERIFIED_HIGH" 390 | | "VERIFIED_INITIAL_UNKNOWN" 391 | | "VERIFIED_INITIAL_LOW" 392 | | "VERIFIED_INITIAL_HIGH" 393 | | "VERIFIED_TRANSITION_ANY_TO_NONE" 394 | | "VERIFIED_TRANSITION_ANY_TO_HIGH" 395 | | "VERIFIED_TRANSITION_HIGH_TO_LOW" 396 | | "VERIFIED_TRANSITION_HIGH_TO_UNKNOWN" 397 | | "VERIFIED_TRANSITION_UNKNOWN_TO_LOW" 398 | | "VERIFIED_TRANSITION_LOW_TO_UNKNOWN" 399 | | "VERIFIED_TRANSITION_NONE_TO_LOW" 400 | | "VERIFIED_TRANSITION_NONE_TO_UNKNOWN" 401 | | "GROUP_CREATE" 402 | | "GROUP_CHANGE_SUBJECT" 403 | | "GROUP_CHANGE_ICON" 404 | | "GROUP_CHANGE_INVITE_LINK" 405 | | "GROUP_CHANGE_DESCRIPTION" 406 | | "GROUP_CHANGE_RESTRICT" 407 | | "GROUP_CHANGE_ANNOUNCE" 408 | | "GROUP_PARTICIPANT_ADD" 409 | | "GROUP_PARTICIPANT_REMOVE" 410 | | "GROUP_PARTICIPANT_PROMOTE" 411 | | "GROUP_PARTICIPANT_DEMOTE" 412 | | "GROUP_PARTICIPANT_INVITE" 413 | | "GROUP_PARTICIPANT_LEAVE" 414 | | "GROUP_PARTICIPANT_CHANGE_NUMBER" 415 | | "BROADCAST_CREATE" 416 | | "BROADCAST_ADD" 417 | | "BROADCAST_REMOVE" 418 | | "GENERIC_NOTIFICATION" 419 | | "E2E_IDENTITY_CHANGED" 420 | | "E2E_ENCRYPTED" 421 | | "CALL_MISSED_VOICE" 422 | | "CALL_MISSED_VIDEO" 423 | | "INDIVIDUAL_CHANGE_NUMBER" 424 | | "GROUP_DELETE" 425 | | "GROUP_ANNOUNCE_MODE_MESSAGE_BOUNCE" 426 | | "CALL_MISSED_GROUP_VOICE" 427 | | "CALL_MISSED_GROUP_VIDEO" 428 | | "PAYMENT_CIPHERTEXT" 429 | | "PAYMENT_FUTUREPROOF" 430 | | "PAYMENT_TRANSACTION_STATUS_UPDATE_FAILED" 431 | | "PAYMENT_TRANSACTION_STATUS_UPDATE_REFUNDED" 432 | | "PAYMENT_TRANSACTION_STATUS_UPDATE_REFUND_FAILED" 433 | | "PAYMENT_TRANSACTION_STATUS_RECEIVER_PENDING_SETUP" 434 | | "PAYMENT_TRANSACTION_STATUS_RECEIVER_SUCCESS_AFTER_HICCUP" 435 | | "PAYMENT_ACTION_ACCOUNT_SETUP_REMINDER" 436 | | "PAYMENT_ACTION_SEND_PAYMENT_REMINDER" 437 | | "PAYMENT_ACTION_SEND_PAYMENT_INVITATION" 438 | | "PAYMENT_ACTION_REQUEST_DECLINED" 439 | | "PAYMENT_ACTION_REQUEST_EXPIRED" 440 | | "PAYMENT_ACTION_REQUEST_CANCELLED" 441 | | "BIZ_VERIFIED_TRANSITION_TOP_TO_BOTTOM" 442 | | "BIZ_VERIFIED_TRANSITION_BOTTOM_TO_TOP" 443 | | "BIZ_INTRO_TOP" 444 | | "BIZ_INTRO_BOTTOM" 445 | | "BIZ_NAME_CHANGE" 446 | | "BIZ_MOVE_TO_CONSUMER_APP" 447 | | "BIZ_TWO_TIER_MIGRATION_TOP" 448 | | "BIZ_TWO_TIER_MIGRATION_BOTTOM" 449 | | "OVERSIZED" 450 | | "GROUP_CHANGE_NO_FREQUENTLY_FORWARDED" 451 | | "GROUP_V4_ADD_INVITE_SENT" 452 | | "GROUP_PARTICIPANT_ADD_REQUEST_JOIN" 453 | | "CHANGE_EPHEMERAL_SETTING" 454 | | null; 455 | messageStubParameters: string[] | null; 456 | } 457 | 458 | export default class WhatsApp { 459 | private apiSocket: WebSocket; 460 | 461 | private keysPath?: string; 462 | private qrPath: string; 463 | private clientId?: string; 464 | private loginMsgId: string; 465 | 466 | public myWid?: string; 467 | 468 | private messageSentCount = 0; 469 | 470 | private keyPair: { 471 | public: Uint8Array; 472 | private: Uint8Array; 473 | } | null; 474 | 475 | private clientToken: string | null = null; 476 | private serverToken: string | null = null; 477 | private encKey: Uint8Array = new Uint8Array(); 478 | private macKey: Uint8Array = new Uint8Array(); 479 | 480 | chatList: WAChat[] = []; 481 | contactList: WAContact[] = []; 482 | 483 | isLoggedIn: boolean = false; 484 | 485 | private messageListeners: (( 486 | msg: WAWebMessage, 487 | description: string 488 | ) => void)[] = []; 489 | private messageStubListeners: ((msg: WAStubMessage) => void)[] = []; 490 | private noNetworkListeners: (() => void)[] = []; 491 | private loggedOutListeners: (() => void)[] = []; 492 | private eventListeners: { 493 | [key: string]: (e: WebSocket.MessageEvent) => void; 494 | } = {}; 495 | private readyListeners: (() => void)[] = []; 496 | private qrCodeListeners: (() => void)[] = []; 497 | 498 | constructor( 499 | qrPath = "./qrcode.png", 500 | restoreSession = false, 501 | keysPath = "./keys.json" 502 | ) { 503 | const loginMsgId = "" + Date.now(); 504 | 505 | this.apiSocket = new WebSocket("wss://web.whatsapp.com/ws", { 506 | headers: { Origin: "https://web.whatsapp.com" } 507 | }); 508 | 509 | this.keyPair = null; 510 | 511 | this.qrPath = resolvePath(".", qrPath); 512 | 513 | if (restoreSession) { 514 | this.keysPath = resolvePath(".", keysPath); 515 | } 516 | 517 | this.apiSocket.onopen = this.init(loginMsgId, restoreSession); 518 | this.loginMsgId = loginMsgId; 519 | 520 | if (restoreSession) { 521 | doesFileExist(this.keysPath!).then(doesExist => { 522 | if (!doesExist) { 523 | this.apiSocket.onmessage = this.onMessage(loginMsgId); 524 | } 525 | }); 526 | } else { 527 | this.apiSocket.onmessage = this.onMessage(loginMsgId); 528 | } 529 | } 530 | 531 | public on( 532 | event: 533 | | "message" 534 | | "ready" 535 | | "stubMessage" 536 | | "qrCode" 537 | | "noNetwork" 538 | | "loggedOut", 539 | cb: 540 | | (() => void) 541 | | ((msg: WAWebMessage, description: string) => void) 542 | | ((msg: WAStubMessage) => void) 543 | ) { 544 | switch (event) { 545 | case "message": 546 | this.messageListeners.push( 547 | cb as (msg: WAWebMessage, description: string) => void 548 | ); 549 | break; 550 | case "stubMessage": 551 | this.messageStubListeners.push(cb as (msg: WAStubMessage) => void); 552 | break; 553 | case "ready": 554 | this.readyListeners.push(cb as () => void); 555 | break; 556 | case "qrCode": 557 | this.qrCodeListeners.push(cb as () => void); 558 | case "noNetwork": 559 | this.noNetworkListeners.push(cb as () => void); 560 | case "loggedOut": 561 | this.loggedOutListeners.push(cb as () => void); 562 | default: 563 | break; 564 | } 565 | } 566 | 567 | private addEventListener( 568 | cb: (e: WebSocket.MessageEvent) => void, 569 | id: string 570 | ) { 571 | this.eventListeners[id] = cb; 572 | } 573 | 574 | private saveKeys() { 575 | writeFile( 576 | this.keysPath!, 577 | JSON.stringify({ 578 | clientId: this.clientId, 579 | clientToken: this.clientToken, 580 | serverToken: this.serverToken, 581 | macKey: Array.from(this.macKey), 582 | encKey: Array.from(this.encKey) 583 | }), 584 | err => console.error(err) 585 | ); 586 | } 587 | 588 | private async getKeys() { 589 | return new Promise((resolve, reject) => { 590 | readFile(this.keysPath!, "utf-8", (err, data) => { 591 | if (err) reject(err); 592 | 593 | const { 594 | clientId, 595 | clientToken, 596 | serverToken, 597 | macKey, 598 | encKey 599 | } = JSON.parse(data); 600 | 601 | this.encKey = Uint8Array.from(encKey); 602 | this.macKey = Uint8Array.from(macKey); 603 | this.clientId = clientId; 604 | this.clientToken = clientToken; 605 | this.serverToken = serverToken; 606 | 607 | resolve(); 608 | }); 609 | }); 610 | } 611 | 612 | private async restoreSession(loginMsgId: string) { 613 | this.apiSocket.send( 614 | `${loginMsgId},["admin","login","${this.clientToken}","${this.serverToken}","${this.clientId}","takeover"]` 615 | ); 616 | 617 | this.apiSocket.onmessage = e => { 618 | if (typeof e.data === "string") { 619 | const receivedMessageId = e.data.substring(0, e.data.indexOf(",")); 620 | 621 | if (receivedMessageId === loginMsgId && e.data !== `${loginMsgId},`) { 622 | const data = JSON.parse( 623 | e.data.substring(e.data.indexOf(",") + 1) 624 | ) as { status: number }; 625 | 626 | if (data.status === 200) { 627 | this.apiSocket.onmessage = this.onMessage(loginMsgId); 628 | } else { 629 | this.loggedOutListeners.forEach(func => func()); 630 | } 631 | } 632 | } 633 | }; 634 | } 635 | 636 | private keepAlive() { 637 | if (this.apiSocket) { 638 | this.apiSocket.send("?,,"); 639 | setTimeout(this.keepAlive.bind(this), 20 * 1000); 640 | } 641 | } 642 | 643 | public disconnect() { 644 | this.apiSocket.send(`goodbye,,["admin","Conn","disconnect"]`); 645 | } 646 | 647 | private async sendSocketAsync(messageTag: string, data: any): Promise { 648 | return new Promise((resolve, reject) => { 649 | this.apiSocket.send(data); 650 | 651 | this.addEventListener(async e => { 652 | if (typeof e.data === "string") { 653 | const receivedMessageId = e.data.substring(0, e.data.indexOf(",")); 654 | 655 | if (receivedMessageId === messageTag && e.data !== `${messageTag},`) { 656 | const data = JSON.parse(e.data.substring(e.data.indexOf(",") + 1)); 657 | delete this.eventListeners[messageTag]; 658 | 659 | resolve(data); 660 | } 661 | } 662 | }, messageTag); 663 | }); 664 | } 665 | 666 | private onMessage(loginMsgId: string) { 667 | return async (e: WebSocket.MessageEvent) => { 668 | Object.values(this.eventListeners).forEach(func => func(e)); 669 | 670 | if (typeof e.data === "string") { 671 | try { 672 | const messageTag = e.data.substring(0, e.data.indexOf(",")); 673 | const data = JSON.parse(e.data.substring(e.data.indexOf(",") + 1)) as 674 | | WhatsAppLoginPayload 675 | | WhatsAppConnPayload 676 | | WhatsAppStreamPayload 677 | | WhatsAppPropsPayload 678 | | WhatsAppUploadMediaURL; 679 | 680 | // Initial response and setting up the QR code 681 | if (messageTag === loginMsgId && !this.clientToken) { 682 | await this.setupQrCode(data as WhatsAppLoginPayload); 683 | // Encryption and device data 684 | } else if ( 685 | Array.isArray(data) && 686 | data.length >= 2 && 687 | data[0] === "Conn" && 688 | data[1].secret 689 | ) { 690 | this.isLoggedIn = true; 691 | this.myWid = (data as WhatsAppConnPayload)[1].wid; 692 | this.setupEncryptionKeys(data as WhatsAppConnPayload); 693 | setTimeout(this.keepAlive.bind(this), 20 * 1000); 694 | 695 | if (this.keysPath) { 696 | this.saveKeys(); 697 | } 698 | } else if ( 699 | Array.isArray(data) && 700 | data.length >= 2 && 701 | data[0] === "Conn" && 702 | data[1].clientToken 703 | ) { 704 | const { 705 | clientToken, 706 | serverToken 707 | } = (data as WhatsAppConnPayload)[1]; 708 | this.isLoggedIn = true; 709 | this.clientToken = clientToken; 710 | this.serverToken = serverToken; 711 | this.myWid = (data as WhatsAppConnPayload)[1].wid; 712 | 713 | setTimeout(this.keepAlive.bind(this), 20 * 1000); 714 | 715 | if (this.keysPath) { 716 | this.saveKeys(); 717 | } 718 | } else if ( 719 | Array.isArray(data) && 720 | data.length >= 2 && 721 | data[0] === "Cmd" && 722 | data[1].type === "challenge" 723 | ) { 724 | const str = data[1].challenge; 725 | const decoded = Buffer.from(str, "base64"); 726 | const signed = HmacSha256(this.macKey, Uint8Array.from(decoded)); 727 | const encoded = Buffer.from(signed).toString("base64"); 728 | 729 | this.apiSocket.send( 730 | `${messageTag}, ["admin", "challenge", "${encoded}", "${this.serverToken}", "${this.clientId}"]` 731 | ); 732 | } else if ( 733 | (data as WhatsAppLoginPayload).status && 734 | !(data as WhatsAppLoginPayload).ref && 735 | messageTag === loginMsgId 736 | ) { 737 | } 738 | } catch {} 739 | } else if (Buffer.isBuffer(e.data)) { 740 | const result = new Uint8Array(e.data); 741 | await this.decryptMessage(result); 742 | } 743 | }; 744 | } 745 | 746 | private async decryptMessage(result: Uint8Array) { 747 | const delimPos = result.indexOf(44); //look for index of comma because there is a message tag before it 748 | const messageContent = result.slice(delimPos + 1); 749 | const hmacValidation = HmacSha256(this.macKey, messageContent.slice(32)); 750 | 751 | if (!arraysEqual(hmacValidation, messageContent.slice(0, 32))) { 752 | throw new Error(`hmac mismatch 753 | ${Buffer.from(hmacValidation).toString("hex")}, 754 | ${Buffer.from(messageContent.slice(0, 32)).toString("hex")}`); 755 | } 756 | 757 | const data = AESDecrypt(this.encKey, messageContent.slice(32)); 758 | const allMsgs = await whatsappReadBinary(data, true); 759 | 760 | if (allMsgs.description === "action") { 761 | this.readyListeners.forEach(func => func()); 762 | this.readyListeners = []; 763 | } 764 | 765 | (allMsgs.content as WANode[]).forEach(async node => { 766 | if ( 767 | node.description === "user" && 768 | ((node.attributes as unknown) as WAContact).jid.endsWith("c.us") 769 | ) { 770 | this.contactList.push((node.attributes as unknown) as WAContact); 771 | } else if ( 772 | node.description === "chat" && 773 | ((node.attributes as unknown) as WAChat).jid.endsWith("g.us") 774 | ) { 775 | this.chatList.push((node.attributes as unknown) as WAChat); 776 | } else if (((node as unknown) as WAWebMessage).message) { 777 | const msg = (node as unknown) as WAWebMessage; 778 | const remoteJid = msg.key!.remoteJid!.replace( 779 | "@s.whatsapp.net", 780 | "@c.us" 781 | ); 782 | 783 | if (msg.participant) { 784 | const userJid = msg.participant.replace("@s.whatsapp.net", "@c.us"); 785 | const contact = this.contactList.find( 786 | contact => contact.jid.replace("\0", "") === userJid 787 | ); 788 | 789 | if (contact) { 790 | msg.author = contact.name 791 | ? contact.name 792 | : contact.vname || contact.notify; 793 | } 794 | } else { 795 | const userJid = msg.key.remoteJid!.replace( 796 | "@s.whatsapp.net", 797 | "@c.us" 798 | ); 799 | const contact = this.contactList.find( 800 | contact => contact.jid.replace("\0", "") === userJid 801 | ); 802 | 803 | if (contact) { 804 | msg.author = contact.name 805 | ? contact.name 806 | : contact.vname || contact.notify; 807 | } 808 | } 809 | 810 | const chat = this.chatList.find( 811 | chat => chat.jid.replace("\0", "") === remoteJid 812 | ); 813 | 814 | if (chat) { 815 | msg.key.name = chat.name; 816 | } 817 | 818 | if (msg.message.stickerMessage) { 819 | msg.message.decryptedMediaMessage = await this.decryptMedia( 820 | msg.message.stickerMessage as WAReceiveMedia, 821 | "sticker" 822 | ).catch(err => { 823 | throw err; 824 | }); 825 | } else if (msg.message.imageMessage) { 826 | msg.message.decryptedMediaMessage = await this.decryptMedia( 827 | msg.message.imageMessage as WAReceiveMedia, 828 | "image" 829 | ).catch(err => { 830 | throw err; 831 | }); 832 | } else if (msg.message.videoMessage) { 833 | msg.message.decryptedMediaMessage = await this.decryptMedia( 834 | msg.message.videoMessage as WAReceiveMedia, 835 | "video" 836 | ).catch(err => { 837 | throw err; 838 | }); 839 | } else if (msg.message.audioMessage) { 840 | msg.message.decryptedMediaMessage = await this.decryptMedia( 841 | msg.message.audioMessage as WAReceiveMedia, 842 | "audio" 843 | ).catch(err => { 844 | throw err; 845 | }); 846 | } else if (msg.message.documentMessage) { 847 | msg.message.decryptedMediaMessage = await this.decryptMedia( 848 | msg.message.documentMessage as WAReceiveMedia, 849 | "document" 850 | ).catch(err => { 851 | throw err; 852 | }); 853 | } 854 | 855 | this.messageListeners.forEach(func => func(msg, allMsgs.description)); 856 | } else if (((node as unknown) as WAStubMessage).messageStubType) { 857 | const msg = (node as unknown) as WAStubMessage; 858 | 859 | if ( 860 | msg.messageStubType === "GROUP_PARTICIPANT_ADD" && 861 | msg.messageStubParameters!.includes( 862 | this.myWid!.replace("c.us", "s.whatsapp.net") 863 | ) 864 | ) { 865 | const chat = this.chatList.find( 866 | chat => chat.jid === msg.key.remoteJid! 867 | ); 868 | 869 | if (chat) { 870 | const i = this.chatList.indexOf(chat); 871 | 872 | chat.read_only = "false"; 873 | this.chatList[i] = chat; 874 | } else { 875 | const chat = await this.getGroupMetadata(msg.key.remoteJid!); 876 | 877 | this.chatList.push({ 878 | name: chat.subject, 879 | jid: msg.key.remoteJid!, 880 | spam: "false", 881 | count: "0", 882 | t: "" + chat.creation 883 | }); 884 | } 885 | } else if ( 886 | msg.messageStubType === "GROUP_PARTICIPANT_REMOVE" && 887 | msg.messageStubParameters!.includes( 888 | this.myWid!.replace("c.us", "s.whatsapp.net") 889 | ) 890 | ) { 891 | const chat = this.chatList.find( 892 | chat => chat.jid === msg.key.remoteJid! 893 | ); 894 | 895 | if (chat) { 896 | this.chatList.splice(this.chatList.indexOf(chat), 1); 897 | } 898 | } 899 | this.messageStubListeners.forEach(func => func(msg)); 900 | } 901 | }); 902 | } 903 | 904 | private async sendAdminTest() { 905 | const id = randHex(10).toUpperCase(); 906 | const timeout = setTimeout(() => { 907 | this.noNetworkListeners.forEach(func => func()); 908 | }, 10 * 1000); 909 | 910 | return await this.sendSocketAsync(id, ["admin", "test"]).then( 911 | (data: WhatsAppAdminTestPayload) => { 912 | if (data[0] === "Pong" && data[1]) { 913 | clearTimeout(timeout); 914 | return true; 915 | } 916 | return false; 917 | } 918 | ); 919 | } 920 | 921 | private async sendProto( 922 | msgData: WAMessageNode, 923 | id: string, 924 | metric: keyof typeof WAMetrics = "MESSAGE" 925 | ) { 926 | const encoder = new TextEncoder(); 927 | const cipher = AESEncrypt(this.encKey, await whatsappWriteBinary(msgData)); 928 | const encryptedMsg = concatIntArray( 929 | HmacSha256(this.macKey, cipher), 930 | cipher 931 | ); 932 | const payload = concatIntArray( 933 | encoder.encode(id), 934 | encoder.encode(","), 935 | Uint8Array.from([WAMetrics[metric]]), 936 | Uint8Array.from([WAFlags.IGNORE]), 937 | encryptedMsg 938 | ); 939 | 940 | this.messageSentCount++; 941 | 942 | const timeout = setTimeout(async () => { 943 | await this.sendAdminTest().then(async isLoggedIn => { 944 | this.isLoggedIn = isLoggedIn; 945 | }); 946 | }, 2 * 1000); 947 | 948 | return await this.sendSocketAsync(id, payload) 949 | .then(data => { 950 | clearTimeout(timeout); 951 | return data; 952 | }) 953 | } 954 | 955 | private async sendMessage( 956 | content: WAMessage, 957 | remoteJid: string, 958 | msgId?: string 959 | ) { 960 | const id = msgId ? msgId : "3EB0" + randHex(8).toUpperCase(); 961 | const msgParams = { 962 | key: { 963 | id, 964 | remoteJid, 965 | fromMe: true 966 | }, 967 | messageTimestamp: Math.round(Date.now() / 1000), 968 | status: 1, 969 | message: content 970 | }; 971 | const msgData: WAMessageNode = { 972 | description: "action", 973 | attributes: { 974 | type: "relay", 975 | epoch: "" + this.messageSentCount 976 | }, 977 | content: [ 978 | { 979 | description: "message", 980 | content: await WAWebMessageInfo.encode(msgParams) 981 | } 982 | ] 983 | }; 984 | 985 | await this.sendProto(msgData, id); 986 | 987 | return { id, content }; 988 | } 989 | 990 | private async getGroupMetadata( 991 | remoteJid: string 992 | ): Promise { 993 | const id = randHex(10).toUpperCase(); 994 | 995 | return await this.sendSocketAsync( 996 | id, 997 | `${id},,["query","GroupMetadata","${remoteJid}"]` 998 | ); 999 | } 1000 | 1001 | public async getGroupParticipants(remoteJid: string) { 1002 | return await this.getGroupMetadata(remoteJid).then( 1003 | (data: WhatsAppGroupMetadataPayload) => { 1004 | return data.participants; 1005 | } 1006 | ); 1007 | } 1008 | 1009 | public async getGroupSubject(remoteJid: string) { 1010 | return await this.getGroupMetadata(remoteJid).then( 1011 | (data: WhatsAppGroupMetadataPayload) => { 1012 | return data.subject; 1013 | } 1014 | ); 1015 | } 1016 | 1017 | public async setGroupPhoto(image: Buffer, remoteJid: string) { 1018 | const id = `${Math.round(Date.now() / 1000)}.--${this.messageSentCount}`; 1019 | const content: WAMessageNode = { 1020 | description: "picture", 1021 | attributes: { 1022 | id, 1023 | jid: remoteJid, 1024 | type: "set" 1025 | }, 1026 | content: [ 1027 | { 1028 | description: "image", 1029 | content: Uint8Array.from(image) 1030 | }, 1031 | { 1032 | description: "preview", 1033 | content: Uint8Array.from(image) 1034 | } 1035 | ] 1036 | }; 1037 | const msgData: WAMessageNode = { 1038 | description: "action", 1039 | attributes: { 1040 | type: "set", 1041 | epoch: "" + this.messageSentCount 1042 | }, 1043 | content: [content] 1044 | }; 1045 | 1046 | await this.sendProto(msgData, id, "PIC"); 1047 | 1048 | return { id, content }; 1049 | } 1050 | 1051 | public async sendTextMessage( 1052 | text: string, 1053 | remoteJid: string, 1054 | mentionedJid?: WAContextInfo["mentionedJid"] 1055 | ) { 1056 | if (!mentionedJid) { 1057 | return await this.sendMessage( 1058 | { 1059 | conversation: text 1060 | }, 1061 | remoteJid 1062 | ); 1063 | } else { 1064 | return await this.sendMessage( 1065 | { 1066 | extendedTextMessage: { 1067 | contextInfo: { 1068 | mentionedJid 1069 | }, 1070 | text 1071 | } 1072 | }, 1073 | remoteJid 1074 | ); 1075 | } 1076 | } 1077 | 1078 | private async sendQuotedMessage( 1079 | content: WAMessage, 1080 | remoteJid: string, 1081 | quotedAuthorJid: string, 1082 | quotedMsg: WAMessage, 1083 | quotedMsgId: string, 1084 | mentionedJid?: WAContextInfo["mentionedJid"] 1085 | ) { 1086 | const contextInfo = { 1087 | mentionedJid: mentionedJid 1088 | ? quotedMsg.extendedTextMessage 1089 | ? quotedMsg.extendedTextMessage.contextInfo 1090 | ? quotedMsg.extendedTextMessage.contextInfo.mentionedJid 1091 | ? quotedMsg.extendedTextMessage.contextInfo.mentionedJid.concat( 1092 | mentionedJid 1093 | ) 1094 | : mentionedJid 1095 | : mentionedJid 1096 | : mentionedJid 1097 | : [], 1098 | stanzaId: quotedMsgId, 1099 | participant: quotedAuthorJid, 1100 | quotedMessage: quotedMsg.extendedTextMessage 1101 | ? { 1102 | conversation: quotedMsg.extendedTextMessage.text 1103 | } 1104 | : quotedMsg 1105 | }; 1106 | 1107 | if (content.conversation) { 1108 | return await this.sendMessage( 1109 | { 1110 | extendedTextMessage: { 1111 | contextInfo, 1112 | text: content.conversation 1113 | } 1114 | }, 1115 | remoteJid 1116 | ); 1117 | } else { 1118 | const quotingContent = Object.assign({}, content); 1119 | const innerContent = Object.keys(quotingContent) 1120 | .map(_key => { 1121 | const key: keyof typeof quotingContent = _key as any; 1122 | return key !== "decryptedMediaMessage" && key !== "conversation" 1123 | ? { key, content: quotingContent[key] } 1124 | : undefined; 1125 | }) 1126 | .filter(obj => obj !== undefined)[0]!; 1127 | 1128 | return await this.sendMessage( 1129 | { 1130 | [innerContent.key]: { 1131 | contextInfo, 1132 | ...innerContent.content 1133 | } 1134 | }, 1135 | remoteJid 1136 | ); 1137 | } 1138 | } 1139 | 1140 | public async sendQuotedTextMessage( 1141 | text: string, 1142 | remoteJid: string, 1143 | quotedAuthorJid: string, 1144 | quotedMsg: WAMessage, 1145 | quotedMsgId: string, 1146 | mentionedJid?: WAContextInfo["mentionedJid"] 1147 | ) { 1148 | return await this.sendQuotedMessage( 1149 | { conversation: text }, 1150 | remoteJid, 1151 | quotedAuthorJid, 1152 | quotedMsg, 1153 | quotedMsgId, 1154 | mentionedJid 1155 | ); 1156 | } 1157 | 1158 | public async sendQuotedMediaMessage( 1159 | file: Buffer, 1160 | mimetype: string, 1161 | msgType: "image" | "sticker" | "video" | "audio" | "document", 1162 | remoteJid: string, 1163 | quotedAuthorJid: string, 1164 | quotedMsg: WAMessage, 1165 | quotedMsgId: string, 1166 | caption: string | undefined = undefined, 1167 | duration: number | undefined = undefined, 1168 | isGif: boolean = false, 1169 | mentionedJid?: WAContextInfo["mentionedJid"] 1170 | ): Promise<{ id: string; content: WAMessage }> { 1171 | const media = await this.encryptMedia( 1172 | file, 1173 | mimetype, 1174 | msgType, 1175 | caption, 1176 | duration, 1177 | isGif 1178 | ); 1179 | 1180 | return await this.sendQuotedMessage( 1181 | media, 1182 | remoteJid, 1183 | quotedAuthorJid, 1184 | quotedMsg, 1185 | quotedMsgId, 1186 | mentionedJid 1187 | ); 1188 | } 1189 | 1190 | public async sendQuotedContactVCard( 1191 | vcard: string, 1192 | remoteJid: string, 1193 | quotedAuthorJid: string, 1194 | quotedMsg: WAMessage, 1195 | quotedMsgId: string, 1196 | mentionedJid?: WAContextInfo["mentionedJid"] 1197 | ) { 1198 | const fullName = vcard.slice( 1199 | vcard.indexOf("FN:") + 3, 1200 | vcard.indexOf("\n", vcard.indexOf("FN:")) 1201 | ); 1202 | 1203 | return await this.sendQuotedMessage( 1204 | { 1205 | contactMessage: { 1206 | vcard, 1207 | displayName: fullName 1208 | } 1209 | }, 1210 | remoteJid, 1211 | quotedAuthorJid, 1212 | quotedMsg, 1213 | quotedMsgId, 1214 | mentionedJid 1215 | ); 1216 | } 1217 | 1218 | public async sendQuotedContact( 1219 | phoneNumber: string, 1220 | firstName: string, 1221 | lastName: string = "", 1222 | remoteJid: string, 1223 | quotedAuthorJid: string, 1224 | quotedMsg: WAMessage, 1225 | quotedMsgId: string, 1226 | mentionedJid?: WAContextInfo["mentionedJid"] 1227 | ) { 1228 | const fullName = 1229 | lastName.length > 0 ? `${firstName} ${lastName}` : firstName; 1230 | const vcard = `BEGIN:VCARD\nVERSION:3.0\nN:${lastName};${firstName};;\nFN:${fullName}\nTEL;TYPE=VOICE:${phoneNumber}\nEND:VCARD`; 1231 | 1232 | this.sendQuotedContactVCard( 1233 | vcard, 1234 | remoteJid, 1235 | quotedAuthorJid, 1236 | quotedMsg, 1237 | quotedMsgId, 1238 | mentionedJid 1239 | ); 1240 | } 1241 | 1242 | public async sendQuotedLocation( 1243 | latitude: number, 1244 | longitude: number, 1245 | remoteJid: string, 1246 | quotedAuthorJid: string, 1247 | quotedMsg: WAMessage, 1248 | quotedMsgId: string, 1249 | mentionedJid?: WAContextInfo["mentionedJid"] 1250 | ) { 1251 | return await this.sendQuotedMessage( 1252 | { 1253 | locationMessage: { 1254 | degreesLatitude: latitude, 1255 | degreesLongitude: longitude 1256 | } 1257 | }, 1258 | remoteJid, 1259 | quotedAuthorJid, 1260 | quotedMsg, 1261 | quotedMsgId, 1262 | mentionedJid 1263 | ); 1264 | } 1265 | 1266 | private async uploadMedia(uploadUrl: string, body: Uint8Array) { 1267 | return await fetch(uploadUrl, { 1268 | body, 1269 | method: "POST", 1270 | headers: { 1271 | Origin: "https://web.whatsapp.com", 1272 | Referer: "https://web.whatsapp.com/" 1273 | } 1274 | }) 1275 | .then(res => res.json()) 1276 | .then(async (res: WhatsAppMediaUploadPayload) => { 1277 | return res; 1278 | }); 1279 | } 1280 | 1281 | private async queryMediaConn(): Promise<{ 1282 | hostname: any; 1283 | auth: string; 1284 | ttl: number; 1285 | }> { 1286 | return new Promise(async resolve => { 1287 | const messageTag = randHex(12).toUpperCase(); 1288 | await this.sendSocketAsync( 1289 | messageTag, 1290 | `${messageTag},["query", "mediaConn"]` 1291 | ).then(async (data: WhatsAppMediaConnPayload) => { 1292 | resolve({ 1293 | hostname: data.media_conn.hosts[0].hostname, 1294 | auth: data.media_conn.auth, 1295 | ttl: data.media_conn.ttl 1296 | }); 1297 | }); 1298 | }); 1299 | } 1300 | 1301 | private async encryptMedia( 1302 | file: Buffer, 1303 | mimetype: string, 1304 | msgType: "image" | "sticker" | "video" | "audio" | "document", 1305 | caption: string | undefined = undefined, 1306 | duration: number | undefined = undefined, 1307 | isGif: boolean = false 1308 | ): Promise { 1309 | return new Promise(async resolve => { 1310 | const messageTag = randHex(12).toUpperCase(); 1311 | const mediaKey = Uint8Array.from(crypto.randomBytes(32)); 1312 | const mediaKeyExpanded = HKDF(mediaKey, 112, WAMediaAppInfo[msgType]); 1313 | const iv = mediaKeyExpanded.slice(0, 16); 1314 | const cipherKey = mediaKeyExpanded.slice(16, 48); 1315 | const macKey = mediaKeyExpanded.slice(48, 80); 1316 | const enc = AESEncrypt(cipherKey, Uint8Array.from(file), iv, false); 1317 | const mac = HmacSha256(macKey, concatIntArray(iv, enc)).slice(0, 10); 1318 | const fileSha256 = Sha256(file); 1319 | const fileEncSha256 = Sha256(concatIntArray(enc, mac)); 1320 | const type = msgType === "sticker" ? "image" : msgType; 1321 | const { hostname, auth } = await this.queryMediaConn(); 1322 | const token = Buffer.from(fileEncSha256).toString("base64"); 1323 | const path = `mms/${type}`; 1324 | 1325 | const mediaObj: WAMedia = { 1326 | mimetype, 1327 | mediaKey, 1328 | caption, 1329 | url: "", 1330 | fileSha256, 1331 | fileEncSha256, 1332 | fileLength: file.byteLength 1333 | }; 1334 | 1335 | if (msgType === "sticker") { 1336 | mediaObj.pngThumbnail = await sharp(file) 1337 | .resize(100) 1338 | .png() 1339 | .toBuffer(); 1340 | } else if (msgType === "image") { 1341 | mediaObj.jpegThumbnail = await sharp(file) 1342 | .resize(100) 1343 | .jpeg() 1344 | .toBuffer(); 1345 | } else if (msgType === "audio") { 1346 | if (duration) { 1347 | mediaObj.seconds = duration; 1348 | } else { 1349 | throw new Error("Audio messages require duration"); 1350 | } 1351 | } else if (msgType === "video") { 1352 | mediaObj.gifPlayback = isGif; 1353 | } 1354 | 1355 | const media = await this.uploadMedia( 1356 | `https://${hostname}/${path}/${token}?auth=${auth}&token=${token}`, 1357 | concatIntArray(enc, mac) 1358 | ); 1359 | 1360 | mediaObj.url = media.url; 1361 | 1362 | resolve({ [msgType + "Message"]: mediaObj }); 1363 | }); 1364 | } 1365 | 1366 | public async sendMediaMessage( 1367 | file: Buffer, 1368 | mimetype: string, 1369 | msgType: "image" | "sticker" | "video" | "audio" | "document", 1370 | remoteJid: string, 1371 | caption: string | undefined = undefined, 1372 | duration: number | undefined = undefined, 1373 | isGif: boolean = false, 1374 | mentionedJid?: WAContextInfo["mentionedJid"] 1375 | ): Promise<{ id: string; content: WAMessage }> { 1376 | const nextId = randHex(12).toUpperCase(); 1377 | const mediaProto = await this.encryptMedia( 1378 | file, 1379 | mimetype, 1380 | msgType, 1381 | caption, 1382 | duration, 1383 | isGif 1384 | ); 1385 | const media = await this.sendMediaProto( 1386 | (mediaProto[ 1387 | (msgType + "Message") as 1388 | | "imageMessage" 1389 | | "stickerMessage" 1390 | | "videoMessage" 1391 | | "audioMessage" 1392 | | "documentMessage" 1393 | ]! as unknown) as WAMedia, 1394 | msgType, 1395 | remoteJid, 1396 | nextId, 1397 | mentionedJid 1398 | ); 1399 | 1400 | return { id: nextId, content: media.content }; 1401 | } 1402 | 1403 | public async sendVCardContact(remoteJid: string, vcard: string) { 1404 | const fullName = vcard.slice( 1405 | vcard.indexOf("FN:") + 3, 1406 | vcard.indexOf("\n", vcard.indexOf("FN:")) 1407 | ); 1408 | 1409 | return await this.sendMessage( 1410 | { 1411 | contactMessage: { 1412 | vcard, 1413 | displayName: fullName 1414 | } 1415 | }, 1416 | remoteJid 1417 | ); 1418 | } 1419 | 1420 | public async sendContact( 1421 | remoteJid: string, 1422 | phoneNumber: string, 1423 | firstName: string, 1424 | lastName: string = "" 1425 | ) { 1426 | const fullName = 1427 | lastName.length > 0 ? `${firstName} ${lastName}` : firstName; 1428 | const vcard = `BEGIN:VCARD\nVERSION:3.0\nN:${lastName};${firstName};;\nFN:${fullName}\nTEL;TYPE=VOICE:${phoneNumber}\nEND:VCARD`; 1429 | 1430 | return await this.sendVCardContact(remoteJid, vcard); 1431 | } 1432 | 1433 | public async sendLocation( 1434 | remoteJid: string, 1435 | latitude: number, 1436 | longitude: number 1437 | ) { 1438 | return await this.sendMessage( 1439 | { 1440 | locationMessage: { 1441 | degreesLatitude: latitude, 1442 | degreesLongitude: longitude 1443 | } 1444 | }, 1445 | remoteJid 1446 | ); 1447 | } 1448 | 1449 | private async sendMediaProto( 1450 | mediaFile: WAMedia, 1451 | msgType: string, 1452 | remoteJid: string, 1453 | msgId: string, 1454 | mentionedJid?: WAContextInfo["mentionedJid"] 1455 | ) { 1456 | if (!mentionedJid) { 1457 | return await this.sendMessage( 1458 | { 1459 | [msgType + "Message"]: mediaFile 1460 | }, 1461 | remoteJid, 1462 | msgId 1463 | ); 1464 | } else { 1465 | return await this.sendMessage( 1466 | { 1467 | [msgType + "Message"]: { 1468 | ...mediaFile, 1469 | contextInfo: { 1470 | mentionedJid 1471 | } 1472 | } 1473 | }, 1474 | remoteJid, 1475 | msgId 1476 | ); 1477 | } 1478 | } 1479 | 1480 | private async decryptMedia( 1481 | mediaObj: WAReceiveMedia, 1482 | type: "image" | "sticker" | "video" | "audio" | "document" 1483 | ): Promise { 1484 | const mediaKey = Uint8Array.from(Buffer.from(mediaObj.mediaKey, "base64")); 1485 | const mediaKeyExpanded = HKDF(mediaKey, 112, WAMediaAppInfo[type]); 1486 | const iv = mediaKeyExpanded.slice(0, 16); 1487 | const cipherKey = mediaKeyExpanded.slice(16, 48); 1488 | const macKey = mediaKeyExpanded.slice(48, 80); 1489 | 1490 | const rawFile = await fetch(mediaObj.url) 1491 | .then(res => res.arrayBuffer()) 1492 | .then(arrayBuffer => Buffer.from(arrayBuffer)) 1493 | .then(buffer => Uint8Array.from(buffer)); 1494 | const file = rawFile.slice(0, rawFile.length - 10); 1495 | const mac = rawFile.slice(rawFile.length - 10); 1496 | 1497 | const hmacValidation = HmacSha256(macKey, concatIntArray(iv, file)); 1498 | 1499 | if (!arraysEqual(hmacValidation.slice(0, 10), mac)) { 1500 | throw new Error("Invalid media data"); 1501 | } 1502 | 1503 | return { 1504 | type, 1505 | caption: mediaObj.caption, 1506 | contextInfo: mediaObj.contextInfo, 1507 | gifPlayback: mediaObj.gifPlayback, 1508 | buffer: Buffer.from(AESDecrypt(cipherKey, concatIntArray(iv, file))) 1509 | }; 1510 | } 1511 | 1512 | public async deleteMessage(remoteJid: string, msgId: string) { 1513 | return await this.sendMessage( 1514 | { 1515 | protocolMessage: { 1516 | key: { 1517 | remoteJid, 1518 | fromMe: true, 1519 | id: msgId 1520 | }, 1521 | type: "REVOKE" 1522 | } 1523 | }, 1524 | remoteJid 1525 | ); 1526 | } 1527 | 1528 | public async getProfilePicThumb( 1529 | jid: string 1530 | ): Promise<{ id: string; content?: Buffer; status?: number }> { 1531 | return new Promise(async resolve => { 1532 | const msgId = randHex(12).toUpperCase(); 1533 | 1534 | await this.sendSocketAsync( 1535 | msgId, 1536 | `${msgId},["query", "ProfilePicThumb", "${jid}"]` 1537 | ).then(async (data: WhatsAppProfilePicPayload) => { 1538 | if (data.eurl) { 1539 | resolve({ 1540 | id: msgId, 1541 | content: await fetch(data.eurl).then(res => res.buffer()) 1542 | }); 1543 | } else if (data.status) { 1544 | resolve({ id: msgId, status: data.status }); 1545 | } 1546 | }); 1547 | }); 1548 | } 1549 | 1550 | private setupEncryptionKeys(data: WhatsAppConnPayload) { 1551 | const decodedSecret = Uint8Array.from( 1552 | Buffer.from(data[1].secret, "base64") 1553 | ); 1554 | const publicKey = decodedSecret.slice(0, 32); 1555 | const sharedSecret = sharedKey(this.keyPair!.private, publicKey); 1556 | const sharedSecretExpanded = HKDF(sharedSecret, 80); 1557 | const hmacValidation = HmacSha256( 1558 | sharedSecretExpanded.slice(32, 64), 1559 | concatIntArray(decodedSecret.slice(0, 32), decodedSecret.slice(64)) 1560 | ); 1561 | 1562 | if (!arraysEqual(hmacValidation, decodedSecret.slice(32, 64))) 1563 | throw "hmac mismatch"; 1564 | 1565 | const keysEncrypted = concatIntArray( 1566 | sharedSecretExpanded.slice(64), 1567 | decodedSecret.slice(64) 1568 | ); 1569 | const keysDecrypted = AESDecrypt( 1570 | sharedSecretExpanded.slice(0, 32), 1571 | keysEncrypted 1572 | ); 1573 | 1574 | this.encKey = keysDecrypted.slice(0, 32); 1575 | this.macKey = keysDecrypted.slice(32, 64); 1576 | 1577 | this.clientToken = data[1].clientToken; 1578 | this.serverToken = data[1].serverToken; 1579 | } 1580 | 1581 | private async setupQrCode(data: WhatsAppLoginPayload) { 1582 | this.keyPair = generateKeyPair(Uint8Array.from(crypto.randomBytes(32))); 1583 | const publicKeyBase64 = Buffer.from(this.keyPair.public).toString("base64"); 1584 | const qrCode = dataUrlToBuffer( 1585 | await qrcode.toDataURL(`${data.ref},${publicKeyBase64},${this.clientId}`) 1586 | ); 1587 | 1588 | writeFile(this.qrPath, qrCode.data, err => { 1589 | if (err) console.error(err); 1590 | this.qrCodeListeners.forEach(func => func()); 1591 | }); 1592 | } 1593 | 1594 | private init(loginMsgId: string, restoreSession: boolean) { 1595 | return async (e: WebSocket.OpenEvent) => { 1596 | if ( 1597 | !restoreSession || 1598 | (restoreSession && !(await doesFileExist(this.keysPath!))) 1599 | ) { 1600 | this.clientId = crypto.randomBytes(16).toString("base64"); 1601 | } else { 1602 | await this.getKeys(); 1603 | } 1604 | 1605 | e.target.send( 1606 | `${loginMsgId},["admin","init",[0,4,2080],["WhatsApp forwarder","WhatsAppForwarder","0.1.0"],"${this.clientId}",true]` 1607 | ); 1608 | 1609 | if (restoreSession && (await doesFileExist(this.keysPath!))) { 1610 | this.restoreSession(loginMsgId); 1611 | } 1612 | }; 1613 | } 1614 | } 1615 | --------------------------------------------------------------------------------