mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
Initial commit
This commit is contained in:
476
binary_coding/binary_encoder.js
Normal file
476
binary_coding/binary_encoder.js
Normal file
@@ -0,0 +1,476 @@
|
||||
const ProtoBuf = require("protobufjs")
|
||||
|
||||
const WATags = {
|
||||
LIST_EMPTY: 0,
|
||||
STREAM_END: 2,
|
||||
DICTIONARY_0: 236,
|
||||
DICTIONARY_1: 237,
|
||||
DICTIONARY_2: 238,
|
||||
DICTIONARY_3: 239,
|
||||
LIST_8 : 248,
|
||||
LIST_16 : 249,
|
||||
JID_PAIR : 250,
|
||||
HEX_8 : 251,
|
||||
BINARY_8 : 252,
|
||||
BINARY_20 : 253,
|
||||
BINARY_32 : 254,
|
||||
NIBBLE_8 : 255,
|
||||
SINGLE_BYTE_MAX: 256,
|
||||
PACKED_MAX: 254
|
||||
}
|
||||
const WADoubleByteTokens = []
|
||||
|
||||
const WASingleByteTokens = [
|
||||
null,null,null,"200","400","404","500","501","502","action","add",
|
||||
"after","archive","author","available","battery","before","body",
|
||||
"broadcast","chat","clear","code","composing","contacts","count",
|
||||
"create","debug","delete","demote","duplicate","encoding","error",
|
||||
"false","filehash","from","g.us","group","groups_v2","height","id",
|
||||
"image","in","index","invis","item","jid","kind","last","leave",
|
||||
"live","log","media","message","mimetype","missing","modify","name",
|
||||
"notification","notify","out","owner","participant","paused",
|
||||
"picture","played","presence","preview","promote","query","raw",
|
||||
"read","receipt","received","recipient","recording","relay",
|
||||
"remove","response","resume","retry","s.whatsapp.net","seconds",
|
||||
"set","size","status","subject","subscribe","t","text","to","true",
|
||||
"type","unarchive","unavailable","url","user","value","web","width",
|
||||
"mute","read_only","admin","creator","short","update","powersave",
|
||||
"checksum","epoch","block","previous","409","replaced","reason",
|
||||
"spam","modify_tag","message_info","delivery","emoji","title",
|
||||
"description","canonical-url","matched-text","star","unstar",
|
||||
"media_key","filename","identity","unread","page","page_count",
|
||||
"search","media_message","security","call_log","profile","ciphertext",
|
||||
"invite","gif","vcard","frequent","privacy","blacklist","whitelist",
|
||||
"verify","location","document","elapsed","revoke_invite","expiration",
|
||||
"unsubscribe","disable","vname","old_jid","new_jid","announcement",
|
||||
"locked","prop","label","color","call","offer","call-id",
|
||||
"quick_reply", "sticker", "pay_t", "accept", "reject", "sticker_pack",
|
||||
"invalid", "canceled", "missed", "connected", "result", "audio",
|
||||
"video", "recent"
|
||||
]
|
||||
const WebMessageInfo = ProtoBuf.Root.fromJSON( require("./whatsapp_message_coding.json") ).lookupType("proto.WebMessageInfo")
|
||||
|
||||
class WhatsAppBinaryEncoder {
|
||||
|
||||
constructor () {
|
||||
this.data = []
|
||||
}
|
||||
pushByte (value) {
|
||||
|
||||
this.data.push((value & 0xFF))
|
||||
}
|
||||
pushInt (value, n, littleEndian=false) {
|
||||
for (var i = 0; i < n;i++) {
|
||||
const curShift = littleEndian ? i : (n-1-i)
|
||||
this.data.push( (value>>(curShift*8)) & 0xFF )
|
||||
}
|
||||
}
|
||||
pushInt20 (value) {
|
||||
this.pushBytes ( [(value >> 16) & 0x0F, (value >> 8) & 0xFF, value & 0xFF] )
|
||||
}
|
||||
pushInt16 (value) {
|
||||
this.pushInt(value, 2)
|
||||
}
|
||||
pushInt32 (value) {
|
||||
this.pushInt(value, 4)
|
||||
}
|
||||
pushInt64 (value) {
|
||||
this.pushInt(value, 8)
|
||||
}
|
||||
pushBytes (bytes) {
|
||||
this.data.push.apply(this.data, bytes)
|
||||
}
|
||||
pushString (str) {
|
||||
const bytes = new TextEncoder('utf-8').encode(str)
|
||||
this.pushBytes(bytes)
|
||||
}
|
||||
writeByteLength (length) {
|
||||
if (length >= 4294967296) {
|
||||
throw "string too large to encode: " + length
|
||||
}
|
||||
|
||||
if (length >= (1<<20)) {
|
||||
this.pushByte(WATags.BINARY_32)
|
||||
this.pushInt32(length)
|
||||
} else if (length >= 256) {
|
||||
this.pushByte(WATags.BINARY_20)
|
||||
this.pushInt20(length)
|
||||
} else {
|
||||
this.pushByte(WATags.BINARY_8)
|
||||
this.pushByte(length)
|
||||
}
|
||||
}
|
||||
writeStringRaw (string) {
|
||||
this.writeByteLength( string.length )
|
||||
this.pushString(string)
|
||||
}
|
||||
writeJid(left,right) {
|
||||
this.pushByte(WATags.JID_PAIR)
|
||||
if (left && left.length > 0) {
|
||||
this.writeString(left)
|
||||
} else {
|
||||
this.writeToken(WATags.LIST_EMPTY)
|
||||
}
|
||||
this.writeString(right)
|
||||
}
|
||||
writeToken (token) {
|
||||
if (token < 245) {
|
||||
this.pushByte(token)
|
||||
} else if (token <= 500) {
|
||||
throw "invalid token"
|
||||
}
|
||||
}
|
||||
writeString(token, i=null) {
|
||||
if (typeof token !== "string") {
|
||||
throw "invalid string: " + token
|
||||
}
|
||||
|
||||
if (token === "c.us") {
|
||||
token = "s.whatsapp.net"
|
||||
}
|
||||
|
||||
|
||||
const tokenIndex = WASingleByteTokens.indexOf(token)
|
||||
if (!i && token === "s.whatsapp.net") {
|
||||
this.writeToken( tokenIndex )
|
||||
} else if ( tokenIndex >= 0 ) {
|
||||
if (tokenIndex < WATags.SINGLE_BYTE_MAX) {
|
||||
this.writeToken(tokenIndex)
|
||||
} else {
|
||||
const overflow = tokenIndex-WATags.SINGLE_BYTE_MAX
|
||||
const dictionaryIndex = overflow >> 8
|
||||
if (dictionaryIndex < 0 || dictionaryIndex > 3) {
|
||||
throw "double byte dict token out of range: " + token + ", " + tokenIndex
|
||||
}
|
||||
this.writeToken(WATags.DICTIONARY_0 + dictionaryIndex)
|
||||
this.writeToken(overflow % 256)
|
||||
}
|
||||
} else {
|
||||
const jidSepIndex = token.indexOf("@")
|
||||
|
||||
if (jidSepIndex <= 0) {
|
||||
this.writeStringRaw(token)
|
||||
} else {
|
||||
this.writeJid(token.slice(0,jidSepIndex), token.slice(jidSepIndex+1, token.length))
|
||||
}
|
||||
}
|
||||
}
|
||||
writeAttributes (attrs) {
|
||||
if (!attrs) {
|
||||
return
|
||||
}
|
||||
Object.keys(attrs).forEach (key => {
|
||||
this.writeString( key )
|
||||
this.writeString( attrs[key] )
|
||||
})
|
||||
}
|
||||
writeListStart (listSize) {
|
||||
if (listSize === 0) {
|
||||
this.pushByte(WATags.LIST_EMPTY)
|
||||
} else if (listSize < 256) {
|
||||
this.pushBytes([WATags.LIST_8, listSize])
|
||||
} else {
|
||||
this.pushByte([WATags.LIST_16, listSize])
|
||||
}
|
||||
}
|
||||
writeChildren (children) {
|
||||
if (!children) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof children === "string") {
|
||||
this.writeString(children, true)
|
||||
} else if (typeof children === "Buffer" || typeof children === "Uint8Array") {
|
||||
this.writeByteLength(children.length)
|
||||
this.pushBytes(children)
|
||||
} else if (Array.isArray(children)) {
|
||||
this.writeListStart(children.length)
|
||||
children.forEach (c => {
|
||||
this.writeNode(c)
|
||||
})
|
||||
} else if (typeof children === "object") {
|
||||
//console.log(children)
|
||||
const buff = WebMessageInfo.encode(children).finish()
|
||||
this.writeByteLength(buff.length)
|
||||
this.pushBytes(buff)
|
||||
} else {
|
||||
throw "invalid children: " + children + " (" + (typeof children) + ")"
|
||||
}
|
||||
}
|
||||
getNumValidKeys (arr) {
|
||||
return arr ? Object.keys(arr).length : 0
|
||||
}
|
||||
writeNode (node) {
|
||||
if (!node) {
|
||||
return
|
||||
} else if (!Array.isArray(node) || node.length !== 3) {
|
||||
throw "invalid node given: " + node
|
||||
}
|
||||
|
||||
const numAttributes = this.getNumValidKeys(node[1])
|
||||
|
||||
this.writeListStart( 2*numAttributes + 1 + ( node[2] ? 1 : 0 ) )
|
||||
this.writeString(node[0])
|
||||
this.writeAttributes(node[1])
|
||||
this.writeChildren(node[2])
|
||||
}
|
||||
write (data) {
|
||||
this.data = new Array()
|
||||
this.writeNode(data)
|
||||
|
||||
return Buffer.from(this.data)
|
||||
}
|
||||
}
|
||||
class WhatsAppBinaryDecoder {
|
||||
|
||||
constructor () {
|
||||
this.buffer = null
|
||||
this.index = 0
|
||||
|
||||
}
|
||||
checkEOS (length) {
|
||||
if (this.index+length > this.buffer.length) {
|
||||
throw "end of stream"
|
||||
}
|
||||
}
|
||||
next () {
|
||||
const value = this.buffer[this.index]
|
||||
this.index += 1
|
||||
return value
|
||||
}
|
||||
readByte () {
|
||||
this.checkEOS(1)
|
||||
return this.next()
|
||||
}
|
||||
|
||||
readInt (n, littleEndian=false) {
|
||||
this.checkEOS(n)
|
||||
let val = 0
|
||||
for (var i = 0; i < n;i++) {
|
||||
const shift = (littleEndian) ? i : (n-1-i)
|
||||
val |= this.next() << (shift*8)
|
||||
}
|
||||
return val
|
||||
}
|
||||
readInt16 (littleEndian=false) {
|
||||
return this.readInt(2, littleEndian)
|
||||
}
|
||||
readInt20 () {
|
||||
this.checkEOS(3)
|
||||
return ( (this.next() & 15) << 16 ) + (this.next()<<8) + this.next()
|
||||
}
|
||||
readInt32 (littleEndian=false) {
|
||||
return this.readInt(4, littleEndian)
|
||||
}
|
||||
readInt64 (littleEndian=false) {
|
||||
return this.readInt(8, littleEndian)
|
||||
}
|
||||
unpackHex (value) {
|
||||
if (value >= 0 && value < 16) {
|
||||
return value<10 ? ('0'.charCodeAt(0)+value) : ('A'.charCodeAt(0)+value-10)
|
||||
}
|
||||
throw "invalid hex: " + value
|
||||
}
|
||||
unpackNibble(value) {
|
||||
if (value >= 0 && value <= 9) {
|
||||
return '0'.charCodeAt(0)+value
|
||||
}
|
||||
switch (value) {
|
||||
case 10:
|
||||
return '-'.charCodeAt(0)
|
||||
case 11:
|
||||
return '.'.charCodeAt(0)
|
||||
case 15:
|
||||
return '\0'.charCodeAt(0)
|
||||
default:
|
||||
throw "invalid nibble: " + value
|
||||
}
|
||||
}
|
||||
unpackByte (tag, value) {
|
||||
if (tag === WATags.NIBBLE_8) {
|
||||
return this.unpackNibble(value)
|
||||
} else if (tag === WATags.HEX_8) {
|
||||
return this.unpackHex(value)
|
||||
} else {
|
||||
throw "unknown tag: " + tag
|
||||
}
|
||||
}
|
||||
readPacked8(tag) {
|
||||
const startByte = this.readByte()
|
||||
let value = ""
|
||||
|
||||
for (var i = 0; i < (startByte&127);i++) {
|
||||
let curByte = this.readByte()
|
||||
value += String.fromCharCode( this.unpackByte(tag, (curByte&0xF0) >> 4) )
|
||||
value += String.fromCharCode( this.unpackByte(tag, curByte&0x0F) )
|
||||
}
|
||||
if ((startByte >> 7) !== 0) {
|
||||
value = value.slice(0,-1)
|
||||
}
|
||||
return value
|
||||
|
||||
}
|
||||
readRangedVarInt (min, max, description="unknown") {
|
||||
// value =
|
||||
throw "WTF"
|
||||
}
|
||||
isListTag (tag) {
|
||||
return tag === WATags.LIST_EMPTY || tag === WATags.LIST_8 || tag === WATags.LIST_16
|
||||
}
|
||||
readListSize (tag) {
|
||||
switch (tag) {
|
||||
case WATags.LIST_EMPTY:
|
||||
return 0
|
||||
case WATags.LIST_8:
|
||||
return this.readByte()
|
||||
case WATags.LIST_16:
|
||||
return this.readInt16()
|
||||
default:
|
||||
throw "invalid tag for list size: " + tag
|
||||
}
|
||||
}
|
||||
readStringFromChars (length) {
|
||||
this.checkEOS(length)
|
||||
const value = this.buffer.slice(this.index, this.index+length)
|
||||
|
||||
this.index += length
|
||||
return new TextDecoder('utf-8').decode(value)
|
||||
}
|
||||
readString (tag) {
|
||||
if (tag >= 3 && tag <= 235) {
|
||||
const token = this.getToken(tag)
|
||||
return token === "s.whatsapp.net" ? "c.us" : token
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case WATags.DICTIONARY_0:
|
||||
case WATags.DICTIONARY_1:
|
||||
case WATags.DICTIONARY_2:
|
||||
case WATags.DICTIONARY_3:
|
||||
return this.getTokenDouble( tag - WATags.DICTIONARY_0, this.readByte() )
|
||||
case WATags.LIST_EMPTY:
|
||||
return null
|
||||
case WATags.BINARY_8:
|
||||
return this.readStringFromChars( this.readByte() )
|
||||
case WATags.BINARY_20:
|
||||
return this.readStringFromChars( this.readInt20() )
|
||||
case WATags.BINARY_32:
|
||||
return this.readStringFromChars( this.readInt32() )
|
||||
case WATags.JID_PAIR:
|
||||
const i = this.readString( this.readByte() )
|
||||
const j = this.readString( this.readByte() )
|
||||
if (i && j) {
|
||||
return i + "@" + j
|
||||
}
|
||||
throw "invalid jid pair: " + i + ", " + j
|
||||
case WATags.HEX_8:
|
||||
case WATags.NIBBLE_8:
|
||||
return this.readPacked8(tag)
|
||||
default:
|
||||
throw "invalid string with tag: " + tag
|
||||
}
|
||||
}
|
||||
readAttributes (n) {
|
||||
if (n !== 0) {
|
||||
let attributes = {}
|
||||
for (var i = 0;i < n;i++) {
|
||||
const index = this.readString(this.readByte())
|
||||
const b = this.readByte()
|
||||
|
||||
attributes[index] = this.readString(b)
|
||||
}
|
||||
return attributes
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
readList (tag) {
|
||||
let list = Array( this.readListSize(tag) )
|
||||
for (var i = 0;i < list.length;i++) {
|
||||
list[i] = this.readNode()
|
||||
}
|
||||
return list
|
||||
}
|
||||
readBytes (n) {
|
||||
this.checkEOS(n)
|
||||
const value = this.buffer.slice(this.index, this.index+n)
|
||||
this.index += n
|
||||
return value
|
||||
}
|
||||
getToken (index) {
|
||||
if (index < 3 || index >= WASingleByteTokens.length) {
|
||||
throw "invalid token index: " + index
|
||||
}
|
||||
return WASingleByteTokens[index]
|
||||
}
|
||||
getTokenDouble (index1, index2) {
|
||||
const n = 256*index1 + index2
|
||||
if (n < 0 || n > WADoubleByteTokens.length) {
|
||||
throw "invalid double token index: " + n
|
||||
}
|
||||
return WADoubleByteTokens[n]
|
||||
}
|
||||
readNode () {
|
||||
const listSize = this.readListSize( this.readByte() )
|
||||
const descrTag = this.readByte()
|
||||
|
||||
if (descrTag === WATags.STREAM_END) {
|
||||
throw "unexpected stream end"
|
||||
}
|
||||
|
||||
const descr = this.readString(descrTag)
|
||||
if (listSize === 0 || !descr) {
|
||||
throw "invalid node"
|
||||
}
|
||||
//console.log(descr + "," + listSize)
|
||||
|
||||
let attrs = this.readAttributes( (listSize-1) >> 1 )
|
||||
let content = null
|
||||
|
||||
|
||||
if (listSize%2 === 0) {
|
||||
const tag = this.readByte()
|
||||
|
||||
if (this.isListTag(tag)) {
|
||||
content = this.readList(tag)
|
||||
} else {
|
||||
switch (tag) {
|
||||
case WATags.BINARY_8:
|
||||
content = this.readBytes( this.readByte() )
|
||||
break
|
||||
case WATags.BINARY_20:
|
||||
content = this.readBytes( this.readInt20() )
|
||||
break
|
||||
case WATags.BINARY_32:
|
||||
content = this.readBytes( this.readInt32() )
|
||||
break
|
||||
default:
|
||||
content = this.readString(tag)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
//console.log( descr + "," + JSON.stringify(attrs) + ", " + content)
|
||||
return [descr, attrs, content]
|
||||
}
|
||||
|
||||
read (buffer) {
|
||||
this.buffer = buffer
|
||||
this.index = 0
|
||||
|
||||
let node = this.readNode()
|
||||
|
||||
if (node[2]) {
|
||||
for (var i = 0; i < node[2].length;i++) {
|
||||
if (node[2][0][0] === "message") {
|
||||
node[2][i][2] = WebMessageInfo.decode( node[2][i][2] )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { Encoder: WhatsAppBinaryEncoder, Decoder: WhatsAppBinaryDecoder }
|
||||
Reference in New Issue
Block a user