mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
feat: functional legacy socket
This commit is contained in:
198
src/WABinary/Legacy/constants.ts
Normal file
198
src/WABinary/Legacy/constants.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
|
||||
export const Tags = {
|
||||
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,
|
||||
}
|
||||
export const DoubleByteTokens = []
|
||||
export const SingleByteTokens = [
|
||||
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',
|
||||
]
|
||||
337
src/WABinary/Legacy/index.ts
Normal file
337
src/WABinary/Legacy/index.ts
Normal file
@@ -0,0 +1,337 @@
|
||||
|
||||
import { BinaryNode } from '../types'
|
||||
import { DoubleByteTokens, SingleByteTokens, Tags } from './constants'
|
||||
|
||||
export const isLegacyBinaryNode = (buffer: Buffer) => {
|
||||
switch(buffer[0]) {
|
||||
case Tags.LIST_EMPTY:
|
||||
case Tags.LIST_8:
|
||||
case Tags.LIST_16:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
|
||||
|
||||
const checkEOS = (length: number) => {
|
||||
if (indexRef.index + length > buffer.length) {
|
||||
throw new Error('end of stream')
|
||||
}
|
||||
}
|
||||
const next = () => {
|
||||
const value = buffer[indexRef.index]
|
||||
indexRef.index += 1
|
||||
return value
|
||||
}
|
||||
const readByte = () => {
|
||||
checkEOS(1)
|
||||
return next()
|
||||
}
|
||||
const readStringFromChars = (length: number) => {
|
||||
checkEOS(length)
|
||||
const value = buffer.slice(indexRef.index, indexRef.index + length)
|
||||
|
||||
indexRef.index += length
|
||||
return value.toString('utf-8')
|
||||
}
|
||||
const readBytes = (n: number) => {
|
||||
checkEOS(n)
|
||||
const value = buffer.slice(indexRef.index, indexRef.index + n)
|
||||
indexRef.index += n
|
||||
return value
|
||||
}
|
||||
const readInt = (n: number, littleEndian = false) => {
|
||||
checkEOS(n)
|
||||
let val = 0
|
||||
for (let i = 0; i < n; i++) {
|
||||
const shift = littleEndian ? i : n - 1 - i
|
||||
val |= next() << (shift * 8)
|
||||
}
|
||||
return val
|
||||
}
|
||||
const readInt20 = () => {
|
||||
checkEOS(3)
|
||||
return ((next() & 15) << 16) + (next() << 8) + next()
|
||||
}
|
||||
const unpackHex = (value: number) => {
|
||||
if (value >= 0 && value < 16) {
|
||||
return value < 10 ? '0'.charCodeAt(0) + value : 'A'.charCodeAt(0) + value - 10
|
||||
}
|
||||
throw new Error('invalid hex: ' + value)
|
||||
}
|
||||
const unpackNibble = (value: number) => {
|
||||
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 new Error('invalid nibble: ' + value)
|
||||
}
|
||||
}
|
||||
const unpackByte = (tag: number, value: number) => {
|
||||
if (tag === Tags.NIBBLE_8) {
|
||||
return unpackNibble(value)
|
||||
} else if (tag === Tags.HEX_8) {
|
||||
return unpackHex(value)
|
||||
} else {
|
||||
throw new Error('unknown tag: ' + tag)
|
||||
}
|
||||
}
|
||||
const readPacked8 = (tag: number) => {
|
||||
const startByte = readByte()
|
||||
let value = ''
|
||||
|
||||
for (let i = 0; i < (startByte & 127); i++) {
|
||||
const curByte = readByte()
|
||||
value += String.fromCharCode(unpackByte(tag, (curByte & 0xf0) >> 4))
|
||||
value += String.fromCharCode(unpackByte(tag, curByte & 0x0f))
|
||||
}
|
||||
if (startByte >> 7 !== 0) {
|
||||
value = value.slice(0, -1)
|
||||
}
|
||||
return value
|
||||
}
|
||||
const isListTag = (tag: number) => {
|
||||
return tag === Tags.LIST_EMPTY || tag === Tags.LIST_8 || tag === Tags.LIST_16
|
||||
}
|
||||
const readListSize = (tag: number) => {
|
||||
switch (tag) {
|
||||
case Tags.LIST_EMPTY:
|
||||
return 0
|
||||
case Tags.LIST_8:
|
||||
return readByte()
|
||||
case Tags.LIST_16:
|
||||
return readInt(2)
|
||||
default:
|
||||
throw new Error('invalid tag for list size: ' + tag)
|
||||
}
|
||||
}
|
||||
const getToken = (index: number) => {
|
||||
if (index < 3 || index >= SingleByteTokens.length) {
|
||||
throw new Error('invalid token index: ' + index)
|
||||
}
|
||||
return SingleByteTokens[index]
|
||||
}
|
||||
const readString = (tag: number) => {
|
||||
if (tag >= 3 && tag <= 235) {
|
||||
const token = getToken(tag)
|
||||
return token// === 's.whatsapp.net' ? 'c.us' : token
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case Tags.DICTIONARY_0:
|
||||
case Tags.DICTIONARY_1:
|
||||
case Tags.DICTIONARY_2:
|
||||
case Tags.DICTIONARY_3:
|
||||
return getTokenDouble(tag - Tags.DICTIONARY_0, readByte())
|
||||
case Tags.LIST_EMPTY:
|
||||
return null
|
||||
case Tags.BINARY_8:
|
||||
return readStringFromChars(readByte())
|
||||
case Tags.BINARY_20:
|
||||
return readStringFromChars(readInt20())
|
||||
case Tags.BINARY_32:
|
||||
return readStringFromChars(readInt(4))
|
||||
case Tags.JID_PAIR:
|
||||
const i = readString(readByte())
|
||||
const j = readString(readByte())
|
||||
if (typeof i === 'string' && j) {
|
||||
return i + '@' + j
|
||||
}
|
||||
throw new Error('invalid jid pair: ' + i + ', ' + j)
|
||||
case Tags.HEX_8:
|
||||
case Tags.NIBBLE_8:
|
||||
return readPacked8(tag)
|
||||
default:
|
||||
throw new Error('invalid string with tag: ' + tag)
|
||||
}
|
||||
}
|
||||
const readList = (tag: number) => (
|
||||
[...new Array(readListSize(tag))].map(() => decode(buffer, indexRef))
|
||||
)
|
||||
const getTokenDouble = (index1: number, index2: number) => {
|
||||
const n = 256 * index1 + index2
|
||||
if (n < 0 || n > DoubleByteTokens.length) {
|
||||
throw new Error('invalid double token index: ' + n)
|
||||
}
|
||||
return DoubleByteTokens[n]
|
||||
}
|
||||
|
||||
const listSize = readListSize(readByte())
|
||||
const descrTag = readByte()
|
||||
if (descrTag === Tags.STREAM_END) {
|
||||
throw new Error('unexpected stream end')
|
||||
}
|
||||
const header = readString(descrTag)
|
||||
const attrs: BinaryNode['attrs'] = { }
|
||||
let data: BinaryNode['content']
|
||||
if (listSize === 0 || !header) {
|
||||
throw new Error('invalid node')
|
||||
}
|
||||
// read the attributes in
|
||||
|
||||
const attributesLength = (listSize - 1) >> 1
|
||||
for (let i = 0; i < attributesLength; i++) {
|
||||
const key = readString(readByte())
|
||||
const b = readByte()
|
||||
|
||||
attrs[key] = readString(b)
|
||||
}
|
||||
|
||||
if (listSize % 2 === 0) {
|
||||
const tag = readByte()
|
||||
if (isListTag(tag)) {
|
||||
data = readList(tag)
|
||||
} else {
|
||||
let decoded: Buffer | string
|
||||
switch (tag) {
|
||||
case Tags.BINARY_8:
|
||||
decoded = readBytes(readByte())
|
||||
break
|
||||
case Tags.BINARY_20:
|
||||
decoded = readBytes(readInt20())
|
||||
break
|
||||
case Tags.BINARY_32:
|
||||
decoded = readBytes(readInt(4))
|
||||
break
|
||||
default:
|
||||
decoded = readString(tag)
|
||||
break
|
||||
}
|
||||
data = decoded
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tag: header,
|
||||
attrs,
|
||||
content: data
|
||||
}
|
||||
}
|
||||
|
||||
const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => {
|
||||
|
||||
const pushByte = (value: number) => buffer.push(value & 0xff)
|
||||
|
||||
const pushInt = (value: number, n: number, littleEndian=false) => {
|
||||
for (let i = 0; i < n; i++) {
|
||||
const curShift = littleEndian ? i : n - 1 - i
|
||||
buffer.push((value >> (curShift * 8)) & 0xff)
|
||||
}
|
||||
}
|
||||
const pushBytes = (bytes: Uint8Array | Buffer | number[]) => (
|
||||
bytes.forEach (b => buffer.push(b))
|
||||
)
|
||||
const pushInt20 = (value: number) => (
|
||||
pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff])
|
||||
)
|
||||
const writeByteLength = (length: number) => {
|
||||
if (length >= 4294967296) throw new Error('string too large to encode: ' + length)
|
||||
|
||||
if (length >= 1 << 20) {
|
||||
pushByte(Tags.BINARY_32)
|
||||
pushInt(length, 4) // 32 bit integer
|
||||
} else if (length >= 256) {
|
||||
pushByte(Tags.BINARY_20)
|
||||
pushInt20(length)
|
||||
} else {
|
||||
pushByte(Tags.BINARY_8)
|
||||
pushByte(length)
|
||||
}
|
||||
}
|
||||
const writeStringRaw = (str: string) => {
|
||||
const bytes = Buffer.from (str, 'utf-8')
|
||||
writeByteLength(bytes.length)
|
||||
pushBytes(bytes)
|
||||
}
|
||||
const writeToken = (token: number) => {
|
||||
if (token < 245) {
|
||||
pushByte(token)
|
||||
} else if (token <= 500) {
|
||||
throw new Error('invalid token')
|
||||
}
|
||||
}
|
||||
const writeString = (token: string, i?: boolean) => {
|
||||
if (token === 'c.us') token = 's.whatsapp.net'
|
||||
|
||||
const tokenIndex = SingleByteTokens.indexOf(token)
|
||||
if (!i && token === 's.whatsapp.net') {
|
||||
writeToken(tokenIndex)
|
||||
} else if (tokenIndex >= 0) {
|
||||
if (tokenIndex < Tags.SINGLE_BYTE_MAX) {
|
||||
writeToken(tokenIndex)
|
||||
} else {
|
||||
const overflow = tokenIndex - Tags.SINGLE_BYTE_MAX
|
||||
const dictionaryIndex = overflow >> 8
|
||||
if (dictionaryIndex < 0 || dictionaryIndex > 3) {
|
||||
throw new Error('double byte dict token out of range: ' + token + ', ' + tokenIndex)
|
||||
}
|
||||
writeToken(Tags.DICTIONARY_0 + dictionaryIndex)
|
||||
writeToken(overflow % 256)
|
||||
}
|
||||
} else if (token) {
|
||||
const jidSepIndex = token.indexOf('@')
|
||||
if (jidSepIndex <= 0) {
|
||||
writeStringRaw(token)
|
||||
} else {
|
||||
writeJid(token.slice(0, jidSepIndex), token.slice(jidSepIndex + 1, token.length))
|
||||
}
|
||||
}
|
||||
}
|
||||
const writeJid = (left: string, right: string) => {
|
||||
pushByte(Tags.JID_PAIR)
|
||||
left && left.length > 0 ? writeString(left) : writeToken(Tags.LIST_EMPTY)
|
||||
writeString(right)
|
||||
}
|
||||
const writeListStart = (listSize: number) => {
|
||||
if (listSize === 0) {
|
||||
pushByte(Tags.LIST_EMPTY)
|
||||
} else if (listSize < 256) {
|
||||
pushBytes([Tags.LIST_8, listSize])
|
||||
} else {
|
||||
pushBytes([Tags.LIST_16, listSize])
|
||||
}
|
||||
}
|
||||
const validAttributes = Object.keys(attrs).filter(k => (
|
||||
typeof attrs[k] !== 'undefined' && attrs[k] !== null
|
||||
))
|
||||
|
||||
writeListStart(2*validAttributes.length + 1 + (typeof content !== 'undefined' && content !== null ? 1 : 0))
|
||||
writeString(tag)
|
||||
|
||||
validAttributes.forEach((key) => {
|
||||
if(typeof attrs[key] === 'string') {
|
||||
writeString(key)
|
||||
writeString(attrs[key])
|
||||
}
|
||||
})
|
||||
|
||||
if (typeof content === 'string') {
|
||||
writeString(content, true)
|
||||
} else if (Buffer.isBuffer(content)) {
|
||||
writeByteLength(content.length)
|
||||
pushBytes(content)
|
||||
} else if (Array.isArray(content)) {
|
||||
writeListStart(content.length)
|
||||
for(const item of content) {
|
||||
if(item) encode(item, buffer)
|
||||
}
|
||||
} else if(typeof content === 'undefined' || content === null) {
|
||||
|
||||
} else {
|
||||
throw new Error(`invalid children for header "${tag}": ${content} (${typeof content})`)
|
||||
}
|
||||
|
||||
return Buffer.from(buffer)
|
||||
}
|
||||
|
||||
export const encodeBinaryNodeLegacy = encode
|
||||
export const decodeBinaryNodeLegacy = decode
|
||||
@@ -3,6 +3,7 @@ import { jidDecode, jidEncode } from './jid-utils';
|
||||
import { Binary, numUtf8Bytes } from '../../WABinary/Binary';
|
||||
import { Boom } from '@hapi/boom';
|
||||
import { proto } from '../../WAProto';
|
||||
import { BinaryNode } from './types';
|
||||
|
||||
const LIST1 = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.', '<27>', '<27>', '<27>', '<27>'];
|
||||
const LIST2 = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
@@ -210,20 +211,6 @@ function bufferToUInt(e: Uint8Array | Buffer, t: number) {
|
||||
for (let i = 0; i < t; i++) a = 256 * a + e[i]
|
||||
return a
|
||||
}
|
||||
/**
|
||||
* the binary node WA uses internally for communication
|
||||
*
|
||||
* this is manipulated soley as an object and it does not have any functions.
|
||||
* This is done for easy serialization, to prevent running into issues with prototypes &
|
||||
* to maintain functional code structure
|
||||
* */
|
||||
export type BinaryNode = {
|
||||
tag: string
|
||||
attrs: { [key: string]: string }
|
||||
content?: BinaryNode[] | string | Uint8Array
|
||||
}
|
||||
export type BinaryNodeAttributes = BinaryNode['attrs']
|
||||
export type BinaryNodeData = BinaryNode['content']
|
||||
|
||||
export const decodeBinaryNode = (data: Binary): BinaryNode => {
|
||||
//U
|
||||
@@ -333,4 +320,6 @@ export const getBinaryNodeMessages = ({ content }: BinaryNode) => {
|
||||
}
|
||||
|
||||
export * from './jid-utils'
|
||||
export { Binary } from '../../WABinary/Binary'
|
||||
export { Binary } from '../../WABinary/Binary'
|
||||
export * from './types'
|
||||
export * from './Legacy'
|
||||
14
src/WABinary/types.ts
Normal file
14
src/WABinary/types.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* the binary node WA uses internally for communication
|
||||
*
|
||||
* this is manipulated soley as an object and it does not have any functions.
|
||||
* This is done for easy serialization, to prevent running into issues with prototypes &
|
||||
* to maintain functional code structure
|
||||
* */
|
||||
export type BinaryNode = {
|
||||
tag: string
|
||||
attrs: { [key: string]: string }
|
||||
content?: BinaryNode[] | string | Uint8Array
|
||||
}
|
||||
export type BinaryNodeAttributes = BinaryNode['attrs']
|
||||
export type BinaryNodeData = BinaryNode['content']
|
||||
Reference in New Issue
Block a user