Files
InfiniteAPI/src/WABinary/generic-utils.ts
T
Rajeh Taher 90e8ba82f4 Cache the children after a getBinaryNodeChild/ren call to avoid traversing arrays (#2093)
* generic-utils: cache the get

* generic-utils: increased type safety

* chore: lint
2026-01-20 12:43:27 +02:00

142 lines
3.6 KiB
TypeScript

import { Boom } from '@hapi/boom'
import { proto } from '../../WAProto/index.js'
import { type BinaryNode } from './types'
// some extra useful utilities
const indexCache = new WeakMap<BinaryNode, Map<string, BinaryNode[]>>()
export const getBinaryNodeChildren = (node: BinaryNode | undefined, childTag: string) => {
if (!node || !Array.isArray(node.content)) return []
let index = indexCache.get(node)
// Build the index once per node
if (!index) {
index = new Map<string, BinaryNode[]>()
for (const child of node.content) {
let arr = index.get(child.tag)
if (!arr) index.set(child.tag, (arr = []))
arr.push(child)
}
indexCache.set(node, index)
}
// Return first matching child
return index.get(childTag) || []
}
export const getBinaryNodeChild = (node: BinaryNode | undefined, childTag: string) => {
return getBinaryNodeChildren(node, childTag)[0]
}
export const getAllBinaryNodeChildren = ({ content }: BinaryNode) => {
if (Array.isArray(content)) {
return content
}
return []
}
export const getBinaryNodeChildBuffer = (node: BinaryNode | undefined, childTag: string) => {
const child = getBinaryNodeChild(node, childTag)?.content
if (Buffer.isBuffer(child) || child instanceof Uint8Array) {
return child
}
}
export const getBinaryNodeChildString = (node: BinaryNode | undefined, childTag: string) => {
const child = getBinaryNodeChild(node, childTag)?.content
if (Buffer.isBuffer(child) || child instanceof Uint8Array) {
return Buffer.from(child).toString('utf-8')
} else if (typeof child === 'string') {
return child
}
}
export const getBinaryNodeChildUInt = (node: BinaryNode, childTag: string, length: number) => {
const buff = getBinaryNodeChildBuffer(node, childTag)
if (buff) {
return bufferToUInt(buff, length)
}
}
export const assertNodeErrorFree = (node: BinaryNode) => {
const errNode = getBinaryNodeChild(node, 'error')
if (errNode) {
throw new Boom(errNode.attrs.text || 'Unknown error', { data: +errNode.attrs.code! })
}
}
export const reduceBinaryNodeToDictionary = (node: BinaryNode, tag: string) => {
const nodes = getBinaryNodeChildren(node, tag)
const dict = nodes.reduce(
(dict, { attrs }) => {
if (typeof attrs.name === 'string') {
dict[attrs.name] = attrs.value! || attrs.config_value!
} else {
dict[attrs.config_code!] = attrs.value! || attrs.config_value!
}
return dict
},
{} as { [_: string]: string }
)
return dict
}
export const getBinaryNodeMessages = ({ content }: BinaryNode) => {
const msgs: proto.WebMessageInfo[] = []
if (Array.isArray(content)) {
for (const item of content) {
if (item.tag === 'message') {
msgs.push(proto.WebMessageInfo.decode(item.content as Buffer).toJSON() as proto.WebMessageInfo)
}
}
}
return msgs
}
function bufferToUInt(e: Uint8Array | Buffer, t: number) {
let a = 0
for (let i = 0; i < t; i++) {
a = 256 * a + e[i]!
}
return a
}
const tabs = (n: number) => '\t'.repeat(n)
export function binaryNodeToString(node: BinaryNode | BinaryNode['content'], i = 0): string {
if (!node) {
return node!
}
if (typeof node === 'string') {
return tabs(i) + node
}
if (node instanceof Uint8Array) {
return tabs(i) + Buffer.from(node).toString('hex')
}
if (Array.isArray(node)) {
return node.map(x => tabs(i + 1) + binaryNodeToString(x, i + 1)).join('\n')
}
const children = binaryNodeToString(node.content, i + 1)
const tag = `<${node.tag} ${Object.entries(node.attrs || {})
.filter(([, v]) => v !== undefined)
.map(([k, v]) => `${k}='${v}'`)
.join(' ')}`
const content: string = children ? `>\n${children}\n${tabs(i)}</${node.tag}>` : '/>'
return tag + content
}