mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
wip
This commit is contained in:
@@ -45,6 +45,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@adiwajshing/keyed-db": "^0.2.4",
|
"@adiwajshing/keyed-db": "^0.2.4",
|
||||||
|
"@peculiar/webcrypto": "^1.4.1",
|
||||||
"jimp": "^0.16.1",
|
"jimp": "^0.16.1",
|
||||||
"link-preview-js": "^3.0.0",
|
"link-preview-js": "^3.0.0",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ async function findAppModules() {
|
|||||||
const indentation = moduleIndentationMap[info.type]?.indentation
|
const indentation = moduleIndentationMap[info.type]?.indentation
|
||||||
let typeName = unnestName(info.type)
|
let typeName = unnestName(info.type)
|
||||||
if(indentation !== parentName && indentation) {
|
if(indentation !== parentName && indentation) {
|
||||||
typeName = `${indentation.replaceAll('$', '.')}.${typeName}`
|
typeName = `${indentation.replace(/\$/g, '.')}.${typeName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// if(info.enumValues) {
|
// if(info.enumValues) {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"version": [2, 2243, 7]
|
"version": [2, 2244, 6]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GetCatalogOptions, ProductCreate, ProductUpdate, SocketConfig } from '../Types'
|
import { ProductCreate, ProductUpdate, SocketConfig } from '../Types'
|
||||||
import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode, parseProductNode, toProductNode, uploadingNecessaryImagesOfProduct } from '../Utils/business'
|
import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode, parseProductNode, toProductNode, uploadingNecessaryImagesOfProduct } from '../Utils/business'
|
||||||
import { BinaryNode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
|
import { jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
|
||||||
import { getBinaryNodeChild } from '../WABinary/generic-utils'
|
import { getBinaryNodeChild } from '../WABinary/generic-utils'
|
||||||
import { makeMessagesRecvSocket } from './messages-recv'
|
import { makeMessagesRecvSocket } from './messages-recv'
|
||||||
|
|
||||||
@@ -12,36 +12,9 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
waUploadToServer
|
waUploadToServer
|
||||||
} = sock
|
} = sock
|
||||||
|
|
||||||
const getCatalog = async({ jid, limit, cursor }: GetCatalogOptions) => {
|
const getCatalog = async(jid?: string, limit = 10) => {
|
||||||
jid = jid || authState.creds.me?.id
|
jid = jid || authState.creds.me?.id
|
||||||
jid = jidNormalizedUser(jid!)
|
jid = jidNormalizedUser(jid!)
|
||||||
|
|
||||||
const queryParamNodes: BinaryNode[] = [
|
|
||||||
{
|
|
||||||
tag: 'limit',
|
|
||||||
attrs: { },
|
|
||||||
content: Buffer.from((limit || 10).toString())
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'width',
|
|
||||||
attrs: { },
|
|
||||||
content: Buffer.from('100')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'height',
|
|
||||||
attrs: { },
|
|
||||||
content: Buffer.from('100')
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
if(cursor) {
|
|
||||||
queryParamNodes.push({
|
|
||||||
tag: 'after',
|
|
||||||
attrs: { },
|
|
||||||
content: cursor
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await query({
|
const result = await query({
|
||||||
tag: 'iq',
|
tag: 'iq',
|
||||||
attrs: {
|
attrs: {
|
||||||
@@ -56,7 +29,23 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
jid,
|
jid,
|
||||||
allow_shop_source: 'true'
|
allow_shop_source: 'true'
|
||||||
},
|
},
|
||||||
content: queryParamNodes
|
content: [
|
||||||
|
{
|
||||||
|
tag: 'limit',
|
||||||
|
attrs: { },
|
||||||
|
content: Buffer.from(limit.toString())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'width',
|
||||||
|
attrs: { },
|
||||||
|
content: Buffer.from('100')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'height',
|
||||||
|
attrs: { },
|
||||||
|
content: Buffer.from('100')
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -494,27 +494,14 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const presenceSubscribe = (toJid: string) => (
|
||||||
* @param toJid the jid to subscribe to
|
|
||||||
* @param tcToken token for subscription, use if present
|
|
||||||
*/
|
|
||||||
const presenceSubscribe = (toJid: string, tcToken?: Buffer) => (
|
|
||||||
sendNode({
|
sendNode({
|
||||||
tag: 'presence',
|
tag: 'presence',
|
||||||
attrs: {
|
attrs: {
|
||||||
to: toJid,
|
to: toJid,
|
||||||
id: generateMessageTag(),
|
id: generateMessageTag(),
|
||||||
type: 'subscribe'
|
type: 'subscribe'
|
||||||
},
|
}
|
||||||
content: tcToken
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
tag: 'tctoken',
|
|
||||||
attrs: { },
|
|
||||||
content: tcToken
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: undefined
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -738,7 +725,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
)
|
)
|
||||||
: false
|
: false
|
||||||
|
|
||||||
if(historyMsg && !authState.creds.myAppStateKeyId) {
|
if(shouldProcessHistoryMsg && !authState.creds.myAppStateKeyId) {
|
||||||
logger.warn('skipping app state sync, as myAppStateKeyId is not set')
|
logger.warn('skipping app state sync, as myAppStateKeyId is not set')
|
||||||
pendingAppStateSync = true
|
pendingAppStateSync = true
|
||||||
}
|
}
|
||||||
@@ -746,7 +733,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
(async() => {
|
(async() => {
|
||||||
if(
|
if(
|
||||||
historyMsg
|
shouldProcessHistoryMsg
|
||||||
&& authState.creds.myAppStateKeyId
|
&& authState.creds.myAppStateKeyId
|
||||||
) {
|
) {
|
||||||
pendingAppStateSync = false
|
pendingAppStateSync = false
|
||||||
@@ -835,8 +822,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
// we keep buffering events until we finally have
|
// we keep buffering events until we finally have
|
||||||
// the key and can sync the messages
|
// the key and can sync the messages
|
||||||
if(!authState.creds?.myAppStateKeyId) {
|
if(!authState.creds?.myAppStateKeyId) {
|
||||||
ev.buffer()
|
needToFlushWithAppStateSync = ev.buffer()
|
||||||
needToFlushWithAppStateSync = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -268,21 +268,6 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const from = jidNormalizedUser(node.attrs.from)
|
const from = jidNormalizedUser(node.attrs.from)
|
||||||
|
|
||||||
switch (nodeType) {
|
switch (nodeType) {
|
||||||
case 'privacy_token':
|
|
||||||
const tokenList = getBinaryNodeChildren(child, 'token')
|
|
||||||
for(const { attrs, content } of tokenList) {
|
|
||||||
const jid = attrs.jid
|
|
||||||
ev.emit('chats.update', [
|
|
||||||
{
|
|
||||||
id: jid,
|
|
||||||
tcToken: content as Buffer
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
logger.debug({ jid }, 'got privacy token update')
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
case 'w:gp2':
|
case 'w:gp2':
|
||||||
handleGroupNotification(node.attrs.participant, child, result)
|
handleGroupNotification(node.attrs.participant, child, result)
|
||||||
break
|
break
|
||||||
@@ -660,9 +645,16 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
identifier: string,
|
identifier: string,
|
||||||
exec: (node: BinaryNode) => Promise<any>
|
exec: (node: BinaryNode) => Promise<any>
|
||||||
) => {
|
) => {
|
||||||
ev.buffer()
|
const started = ev.buffer()
|
||||||
await execTask()
|
if(started) {
|
||||||
ev.flush()
|
await execTask()
|
||||||
|
if(started) {
|
||||||
|
await ev.flush()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const task = execTask()
|
||||||
|
ev.processInBuffer(task)
|
||||||
|
}
|
||||||
|
|
||||||
function execTask() {
|
function execTask() {
|
||||||
return exec(node)
|
return exec(node)
|
||||||
@@ -670,6 +662,17 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// called when all offline notifs are handled
|
||||||
|
ws.on('CB:ib,,offline', async(node: BinaryNode) => {
|
||||||
|
const child = getBinaryNodeChild(node, 'offline')
|
||||||
|
const offlineNotifs = +(child?.attrs.count || 0)
|
||||||
|
|
||||||
|
logger.info(`handled ${offlineNotifs} offline messages/notifications`)
|
||||||
|
await ev.flush()
|
||||||
|
|
||||||
|
ev.emit('connection.update', { receivedPendingNotifications: true })
|
||||||
|
})
|
||||||
|
|
||||||
// recv a message
|
// recv a message
|
||||||
ws.on('CB:message', (node: BinaryNode) => {
|
ws.on('CB:message', (node: BinaryNode) => {
|
||||||
processNodeWithBuffer(node, 'processing message', handleMessage)
|
processNodeWithBuffer(node, 'processing message', handleMessage)
|
||||||
|
|||||||
@@ -532,32 +532,11 @@ export const makeSocket = ({
|
|||||||
end(new Boom('Multi-device beta not joined', { statusCode: DisconnectReason.multideviceMismatch }))
|
end(new Boom('Multi-device beta not joined', { statusCode: DisconnectReason.multideviceMismatch }))
|
||||||
})
|
})
|
||||||
|
|
||||||
let didStartBuffer = false
|
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
if(creds.me?.id) {
|
// start buffering important events
|
||||||
// start buffering important events
|
ev.buffer()
|
||||||
// if we're logged in
|
|
||||||
ev.buffer()
|
|
||||||
didStartBuffer = true
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined })
|
ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined })
|
||||||
})
|
})
|
||||||
|
|
||||||
// called when all offline notifs are handled
|
|
||||||
ws.on('CB:ib,,offline', (node: BinaryNode) => {
|
|
||||||
const child = getBinaryNodeChild(node, 'offline')
|
|
||||||
const offlineNotifs = +(child?.attrs.count || 0)
|
|
||||||
|
|
||||||
logger.info(`handled ${offlineNotifs} offline messages/notifications`)
|
|
||||||
if(didStartBuffer) {
|
|
||||||
ev.flush()
|
|
||||||
logger.trace('flushed events for initial buffer')
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.emit('connection.update', { receivedPendingNotifications: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
// update credentials when required
|
// update credentials when required
|
||||||
ev.on('creds.update', update => {
|
ev.on('creds.update', update => {
|
||||||
const name = update.me?.name
|
const name = update.me?.name
|
||||||
|
|||||||
@@ -22,25 +22,16 @@ describe('Event Buffer Tests', () => {
|
|||||||
ev.on('chats.update', () => fail('should not emit update event'))
|
ev.on('chats.update', () => fail('should not emit update event'))
|
||||||
|
|
||||||
ev.buffer()
|
ev.buffer()
|
||||||
await Promise.all([
|
ev.processInBuffer((async() => {
|
||||||
(async() => {
|
await delay(100)
|
||||||
ev.buffer()
|
ev.emit('chats.upsert', [{ id: chatId, conversationTimestamp: 123, unreadCount: 1 }])
|
||||||
await delay(100)
|
})())
|
||||||
ev.emit('chats.upsert', [{ id: chatId, conversationTimestamp: 123, unreadCount: 1 }])
|
ev.processInBuffer((async() => {
|
||||||
const flushed = ev.flush()
|
await delay(200)
|
||||||
expect(flushed).toBeFalsy()
|
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 124, unreadCount: 1 }])
|
||||||
})(),
|
})())
|
||||||
(async() => {
|
|
||||||
ev.buffer()
|
|
||||||
await delay(200)
|
|
||||||
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 124, unreadCount: 1 }])
|
|
||||||
const flushed = ev.flush()
|
|
||||||
expect(flushed).toBeFalsy()
|
|
||||||
})()
|
|
||||||
])
|
|
||||||
|
|
||||||
const flushed = ev.flush()
|
await ev.flush()
|
||||||
expect(flushed).toBeTruthy()
|
|
||||||
|
|
||||||
expect(chats).toHaveLength(1)
|
expect(chats).toHaveLength(1)
|
||||||
expect(chats[0].conversationTimestamp).toEqual(124)
|
expect(chats[0].conversationTimestamp).toEqual(124)
|
||||||
@@ -60,7 +51,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
ev.emit('chats.delete', [chatId])
|
ev.emit('chats.delete', [chatId])
|
||||||
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 124, unreadCount: 1 }])
|
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 124, unreadCount: 1 }])
|
||||||
|
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
expect(chats).toHaveLength(1)
|
expect(chats).toHaveLength(1)
|
||||||
})
|
})
|
||||||
@@ -77,7 +68,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 123, unreadCount: 1 }])
|
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 123, unreadCount: 1 }])
|
||||||
ev.emit('chats.delete', [chatId])
|
ev.emit('chats.delete', [chatId])
|
||||||
|
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
expect(chatsDeleted).toHaveLength(1)
|
expect(chatsDeleted).toHaveLength(1)
|
||||||
})
|
})
|
||||||
@@ -112,7 +103,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
}
|
}
|
||||||
}])
|
}])
|
||||||
|
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
ev.buffer()
|
ev.buffer()
|
||||||
ev.emit('chats.upsert', [{
|
ev.emit('chats.upsert', [{
|
||||||
@@ -132,7 +123,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
messages: [],
|
messages: [],
|
||||||
isLatest: false
|
isLatest: false
|
||||||
})
|
})
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
expect(chatsUpserted).toHaveLength(1)
|
expect(chatsUpserted).toHaveLength(1)
|
||||||
expect(chatsUpserted[0].id).toEqual(chatId)
|
expect(chatsUpserted[0].id).toEqual(chatId)
|
||||||
@@ -168,7 +159,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
muteEndTime: 123
|
muteEndTime: 123
|
||||||
}])
|
}])
|
||||||
|
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
expect(chatsUpserted).toHaveLength(1)
|
expect(chatsUpserted).toHaveLength(1)
|
||||||
expect(chatsUpserted[0].archived).toBeUndefined()
|
expect(chatsUpserted[0].archived).toBeUndefined()
|
||||||
@@ -193,7 +184,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
})
|
})
|
||||||
ev.emit('chats.update', [{ id: chatId, archived: true }])
|
ev.emit('chats.update', [{ id: chatId, archived: true }])
|
||||||
|
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
expect(chatRecv).toBeDefined()
|
expect(chatRecv).toBeDefined()
|
||||||
expect(chatRecv?.archived).toBeTruthy()
|
expect(chatRecv?.archived).toBeTruthy()
|
||||||
@@ -227,7 +218,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
ev.emit('messages.upsert', { messages: [proto.WebMessageInfo.fromObject(msg)], type: 'notify' })
|
ev.emit('messages.upsert', { messages: [proto.WebMessageInfo.fromObject(msg)], type: 'notify' })
|
||||||
ev.emit('messages.update', [{ key: msg.key, update: { status: WAMessageStatus.READ } }])
|
ev.emit('messages.update', [{ key: msg.key, update: { status: WAMessageStatus.READ } }])
|
||||||
|
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
expect(msgs).toHaveLength(1)
|
expect(msgs).toHaveLength(1)
|
||||||
expect(msgs[0].message).toBeTruthy()
|
expect(msgs[0].message).toBeTruthy()
|
||||||
@@ -263,7 +254,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
expect(msgs).toHaveLength(1)
|
expect(msgs).toHaveLength(1)
|
||||||
expect(msgs[0].userReceipt).toHaveLength(1)
|
expect(msgs[0].userReceipt).toHaveLength(1)
|
||||||
@@ -284,7 +275,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
ev.emit('messages.update', [{ key, update: { status: WAMessageStatus.DELIVERY_ACK } }])
|
ev.emit('messages.update', [{ key, update: { status: WAMessageStatus.DELIVERY_ACK } }])
|
||||||
ev.emit('messages.update', [{ key, update: { status: WAMessageStatus.READ } }])
|
ev.emit('messages.update', [{ key, update: { status: WAMessageStatus.READ } }])
|
||||||
|
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
expect(msgs).toHaveLength(1)
|
expect(msgs).toHaveLength(1)
|
||||||
expect(msgs[0].update.status).toEqual(WAMessageStatus.READ)
|
expect(msgs[0].update.status).toEqual(WAMessageStatus.READ)
|
||||||
@@ -312,7 +303,7 @@ describe('Event Buffer Tests', () => {
|
|||||||
ev.emit('chats.update', [{ id: msg.key.remoteJid!, unreadCount: 1, conversationTimestamp: msg.messageTimestamp }])
|
ev.emit('chats.update', [{ id: msg.key.remoteJid!, unreadCount: 1, conversationTimestamp: msg.messageTimestamp }])
|
||||||
ev.emit('messages.update', [{ key: msg.key, update: { status: WAMessageStatus.READ } }])
|
ev.emit('messages.update', [{ key: msg.key, update: { status: WAMessageStatus.READ } }])
|
||||||
|
|
||||||
ev.flush()
|
await ev.flush()
|
||||||
|
|
||||||
expect(chats[0].unreadCount).toBeUndefined()
|
expect(chats[0].unreadCount).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { WAMessageContent } from '../Types'
|
|
||||||
import { normalizeMessageContent } from '../Utils'
|
|
||||||
|
|
||||||
describe('Messages Tests', () => {
|
|
||||||
|
|
||||||
it('should correctly unwrap messages', () => {
|
|
||||||
const CONTENT = { imageMessage: { } }
|
|
||||||
expectRightContent(CONTENT)
|
|
||||||
expectRightContent({
|
|
||||||
ephemeralMessage: { message: CONTENT }
|
|
||||||
})
|
|
||||||
expectRightContent({
|
|
||||||
viewOnceMessage: {
|
|
||||||
message: {
|
|
||||||
ephemeralMessage: { message: CONTENT }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
expectRightContent({
|
|
||||||
viewOnceMessage: {
|
|
||||||
message: {
|
|
||||||
viewOnceMessageV2: {
|
|
||||||
message: {
|
|
||||||
ephemeralMessage: { message: CONTENT }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function expectRightContent(content: WAMessageContent) {
|
|
||||||
expect(
|
|
||||||
normalizeMessageContent(content)
|
|
||||||
).toHaveProperty('imageMessage')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -79,6 +79,12 @@ type WithDimensions = {
|
|||||||
height?: number
|
height?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PollMessageOptions = {
|
||||||
|
name: string
|
||||||
|
selectableCount?: number
|
||||||
|
values: Array<string>
|
||||||
|
}
|
||||||
|
|
||||||
export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING
|
export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING
|
||||||
export type AnyMediaMessageContent = (
|
export type AnyMediaMessageContent = (
|
||||||
({
|
({
|
||||||
@@ -127,6 +133,9 @@ export type AnyRegularMessageContent = (
|
|||||||
}
|
}
|
||||||
& Mentionable & Buttonable & Templatable & Listable)
|
& Mentionable & Buttonable & Templatable & Listable)
|
||||||
| AnyMediaMessageContent
|
| AnyMediaMessageContent
|
||||||
|
| ({
|
||||||
|
poll: PollMessageOptions
|
||||||
|
} & Mentionable & Buttonable & Templatable)
|
||||||
| {
|
| {
|
||||||
contacts: {
|
contacts: {
|
||||||
displayName?: string
|
displayName?: string
|
||||||
|
|||||||
@@ -70,15 +70,4 @@ export type OrderProduct = {
|
|||||||
export type OrderDetails = {
|
export type OrderDetails = {
|
||||||
price: OrderPrice
|
price: OrderPrice
|
||||||
products: OrderProduct[]
|
products: OrderProduct[]
|
||||||
}
|
|
||||||
|
|
||||||
export type CatalogCursor = string
|
|
||||||
|
|
||||||
export type GetCatalogOptions = {
|
|
||||||
/** cursor to start from */
|
|
||||||
cursor?: CatalogCursor
|
|
||||||
/** number of products to fetch */
|
|
||||||
limit?: number
|
|
||||||
|
|
||||||
jid?: string
|
|
||||||
}
|
}
|
||||||
@@ -7,14 +7,7 @@ import { getStream, getUrlFromDirectPath, toReadable } from './messages-media'
|
|||||||
export const parseCatalogNode = (node: BinaryNode) => {
|
export const parseCatalogNode = (node: BinaryNode) => {
|
||||||
const catalogNode = getBinaryNodeChild(node, 'product_catalog')
|
const catalogNode = getBinaryNodeChild(node, 'product_catalog')
|
||||||
const products = getBinaryNodeChildren(catalogNode, 'product').map(parseProductNode)
|
const products = getBinaryNodeChildren(catalogNode, 'product').map(parseProductNode)
|
||||||
const paging = getBinaryNodeChild(catalogNode, 'paging')
|
return { products }
|
||||||
|
|
||||||
return {
|
|
||||||
products,
|
|
||||||
nextPageCursor: paging
|
|
||||||
? getBinaryNodeChildString(paging, 'after')
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseCollectionsNode = (node: BinaryNode) => {
|
export const parseCollectionsNode = (node: BinaryNode) => {
|
||||||
|
|||||||
@@ -39,16 +39,15 @@ type BaileysBufferableEventEmitter = BaileysEventEmitter & {
|
|||||||
process(handler: (events: BaileysEventData) => void | Promise<void>): (() => void)
|
process(handler: (events: BaileysEventData) => void | Promise<void>): (() => void)
|
||||||
/**
|
/**
|
||||||
* starts buffering events, call flush() to release them
|
* starts buffering events, call flush() to release them
|
||||||
|
* @returns true if buffering just started, false if it was already buffering
|
||||||
* */
|
* */
|
||||||
buffer(): void
|
buffer(): boolean
|
||||||
/** buffers all events till the promise completes */
|
/** buffers all events till the promise completes */
|
||||||
createBufferedFunction<A extends any[], T>(work: (...args: A) => Promise<T>): ((...args: A) => Promise<T>)
|
createBufferedFunction<A extends any[], T>(work: (...args: A) => Promise<T>): ((...args: A) => Promise<T>)
|
||||||
/**
|
/** flushes all buffered events */
|
||||||
* flushes all buffered events
|
flush(): Promise<void>
|
||||||
* @param force if true, will flush all data regardless of any pending buffers
|
/** waits for the task to complete, before releasing the buffer */
|
||||||
* @returns returns true if the flush actually happened, otherwise false
|
processInBuffer(task: Promise<any>)
|
||||||
*/
|
|
||||||
flush(force?: boolean): boolean
|
|
||||||
/** is there an ongoing buffer */
|
/** is there an ongoing buffer */
|
||||||
isBuffering(): boolean
|
isBuffering(): boolean
|
||||||
}
|
}
|
||||||
@@ -63,7 +62,9 @@ export const makeEventBuffer = (logger: Logger): BaileysBufferableEventEmitter =
|
|||||||
const historyCache = new Set<string>()
|
const historyCache = new Set<string>()
|
||||||
|
|
||||||
let data = makeBufferData()
|
let data = makeBufferData()
|
||||||
let buffersInProgress = 0
|
let isBuffering = false
|
||||||
|
let preBufferTask: Promise<any> = Promise.resolve()
|
||||||
|
let preBufferTraces: string[] = []
|
||||||
|
|
||||||
// take the generic event and fire it as a baileys event
|
// take the generic event and fire it as a baileys event
|
||||||
ev.on('event', (map: BaileysEventData) => {
|
ev.on('event', (map: BaileysEventData) => {
|
||||||
@@ -73,24 +74,25 @@ export const makeEventBuffer = (logger: Logger): BaileysBufferableEventEmitter =
|
|||||||
})
|
})
|
||||||
|
|
||||||
function buffer() {
|
function buffer() {
|
||||||
buffersInProgress += 1
|
if(!isBuffering) {
|
||||||
|
logger.trace('buffering events')
|
||||||
|
isBuffering = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function flush(force = false) {
|
async function flush() {
|
||||||
// no buffer going on
|
if(!isBuffering) {
|
||||||
if(!buffersInProgress) {
|
return
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!force) {
|
logger.trace({ preBufferTraces }, 'releasing buffered events...')
|
||||||
// reduce the number of buffers in progress
|
await preBufferTask
|
||||||
buffersInProgress -= 1
|
|
||||||
// if there are still some buffers going on
|
preBufferTraces = []
|
||||||
// then we don't flush now
|
isBuffering = false
|
||||||
if(buffersInProgress) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = makeBufferData()
|
const newData = makeBufferData()
|
||||||
const chatUpdates = Object.values(data.chatUpdates)
|
const chatUpdates = Object.values(data.chatUpdates)
|
||||||
@@ -115,8 +117,6 @@ export const makeEventBuffer = (logger: Logger): BaileysBufferableEventEmitter =
|
|||||||
{ conditionalChatUpdatesLeft },
|
{ conditionalChatUpdatesLeft },
|
||||||
'released buffered events'
|
'released buffered events'
|
||||||
)
|
)
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -131,26 +131,34 @@ export const makeEventBuffer = (logger: Logger): BaileysBufferableEventEmitter =
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
emit<T extends BaileysEvent>(event: BaileysEvent, evData: BaileysEventMap[T]) {
|
emit<T extends BaileysEvent>(event: BaileysEvent, evData: BaileysEventMap[T]) {
|
||||||
if(buffersInProgress && BUFFERABLE_EVENT_SET.has(event)) {
|
if(isBuffering && BUFFERABLE_EVENT_SET.has(event)) {
|
||||||
append(data, historyCache, event as any, evData, logger)
|
append(data, historyCache, event as any, evData, logger)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return ev.emit('event', { [event]: evData })
|
return ev.emit('event', { [event]: evData })
|
||||||
},
|
},
|
||||||
|
processInBuffer(task) {
|
||||||
|
if(isBuffering) {
|
||||||
|
preBufferTask = Promise.allSettled([ preBufferTask, task ])
|
||||||
|
preBufferTraces.push(new Error('').stack!)
|
||||||
|
}
|
||||||
|
},
|
||||||
isBuffering() {
|
isBuffering() {
|
||||||
return buffersInProgress > 0
|
return isBuffering
|
||||||
},
|
},
|
||||||
buffer,
|
buffer,
|
||||||
flush,
|
flush,
|
||||||
createBufferedFunction(work) {
|
createBufferedFunction(work) {
|
||||||
return async(...args) => {
|
return async(...args) => {
|
||||||
buffer()
|
const started = buffer()
|
||||||
try {
|
try {
|
||||||
const result = await work(...args)
|
const result = await work(...args)
|
||||||
return result
|
return result
|
||||||
} finally {
|
} finally {
|
||||||
flush()
|
if(started) {
|
||||||
|
await flush()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ export * from './baileys-event-stream'
|
|||||||
export * from './use-multi-file-auth-state'
|
export * from './use-multi-file-auth-state'
|
||||||
export * from './link-preview'
|
export * from './link-preview'
|
||||||
export * from './event-buffer'
|
export * from './event-buffer'
|
||||||
export * from './process-message'
|
export * from './process-message'
|
||||||
|
export * from './messages-poll'
|
||||||
|
|||||||
@@ -1,35 +1,16 @@
|
|||||||
import logger from './logger'
|
|
||||||
|
|
||||||
const MUTEX_TIMEOUT_MS = 60_000
|
|
||||||
|
|
||||||
export const makeMutex = () => {
|
export const makeMutex = () => {
|
||||||
let task = Promise.resolve() as Promise<any>
|
let task = Promise.resolve() as Promise<any>
|
||||||
|
|
||||||
let taskTimeout: NodeJS.Timeout | undefined
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mutex<T>(code: () => Promise<T> | T): Promise<T> {
|
mutex<T>(code: () => Promise<T> | T): Promise<T> {
|
||||||
task = (async() => {
|
task = (async() => {
|
||||||
const stack = new Error('mutex start').stack
|
|
||||||
let waitOver = false
|
|
||||||
taskTimeout = setTimeout(() => {
|
|
||||||
logger.warn({ stack, waitOver }, 'possible mutex deadlock')
|
|
||||||
}, MUTEX_TIMEOUT_MS)
|
|
||||||
// wait for the previous task to complete
|
// wait for the previous task to complete
|
||||||
// if there is an error, we swallow so as to not block the queue
|
// if there is an error, we swallow so as to not block the queue
|
||||||
try {
|
try {
|
||||||
await task
|
await task
|
||||||
} catch{ }
|
} catch{ }
|
||||||
|
|
||||||
waitOver = true
|
// execute the current task
|
||||||
|
return code()
|
||||||
try {
|
|
||||||
// execute the current task
|
|
||||||
const result = await code()
|
|
||||||
return result
|
|
||||||
} finally {
|
|
||||||
clearTimeout(taskTimeout)
|
|
||||||
}
|
|
||||||
})()
|
})()
|
||||||
// we replace the existing task, appending the new piece of execution to it
|
// we replace the existing task, appending the new piece of execution to it
|
||||||
// so the next task will have to wait for this one to finish
|
// so the next task will have to wait for this one to finish
|
||||||
|
|||||||
139
src/Utils/messages-poll.ts
Normal file
139
src/Utils/messages-poll.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
// original code: https://gist.github.com/PurpShell/44433d21631ff0aefbea57f7b5e31139
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create crypto instance.
|
||||||
|
* @description If your nodejs crypto module doesn't have WebCrypto, you must install `@peculiar/webcrypto` first
|
||||||
|
* @return {Crypto}
|
||||||
|
*/
|
||||||
|
export const getCrypto = (): Crypto => {
|
||||||
|
const c = require('crypto')
|
||||||
|
|
||||||
|
return 'subtle' in (c?.webcrypto || {}) ? c.webcrypto : new (require('@peculiar/webcrypto').Crypto)()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare the SHA-256 hashes of the poll options from the update to find the original choices
|
||||||
|
* @param options Options from the poll creation message
|
||||||
|
* @param pollOptionHashes hash from `decryptPollMessageRaw()`
|
||||||
|
* @return {Promise<string[]>} the original option, can be empty when none are currently selected
|
||||||
|
*/
|
||||||
|
export const comparePollMessage = async(options: string[], pollOptionHashes: string[]): Promise<string[]> => {
|
||||||
|
const selectedOptions: string[] = []
|
||||||
|
const crypto = getCrypto()
|
||||||
|
for(const option of options) {
|
||||||
|
const hash = Buffer
|
||||||
|
.from(
|
||||||
|
await crypto.subtle.digest(
|
||||||
|
'SHA-256',
|
||||||
|
(new TextEncoder).encode(option)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.toString('hex').toUpperCase()
|
||||||
|
|
||||||
|
if(pollOptionHashes.findIndex(h => h === hash) > -1) {
|
||||||
|
selectedOptions.push(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
return selectedOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw method to decrypt the message after gathering all information
|
||||||
|
* @description Use `decryptPollMessage()` instead, only use this if you know what you are doing
|
||||||
|
* @param encPayload Encryption payload/contents want to decrypt, you can get it from `pollUpdateMessage.vote.encPayload`
|
||||||
|
* @param encIv Encryption iv (used to decrypt the payload), you can get it from `pollUpdateMessage.vote.encIv`
|
||||||
|
* @param additionalData poll Additional data to decrypt poll message
|
||||||
|
* @param decryptionKey Generated decryption key to decrypt the poll message
|
||||||
|
* @return {Promise<Uint8Array>}
|
||||||
|
*/
|
||||||
|
const decryptPollMessageInternal = async(
|
||||||
|
encPayload: Uint8Array,
|
||||||
|
encIv: Uint8Array,
|
||||||
|
additionalData: Uint8Array,
|
||||||
|
decryptionKey: Uint8Array,
|
||||||
|
): Promise<Uint8Array> => {
|
||||||
|
const crypto = getCrypto()
|
||||||
|
|
||||||
|
const tagSize_multiplier = 16
|
||||||
|
const encoded = encPayload
|
||||||
|
const key = await crypto.subtle.importKey('raw', decryptionKey, 'AES-GCM', false, ['encrypt', 'decrypt'])
|
||||||
|
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: encIv, additionalData: additionalData, tagLength: 8 * tagSize_multiplier }, key, encoded)
|
||||||
|
return new Uint8Array(decrypted).slice(2) // remove 2 bytes (OA20)(space+newline)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the message from `decryptPollMessageInternal()`
|
||||||
|
* @param decryptedMessage the message from `decrpytPollMessageInternal()`
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export const decodePollMessage = (decryptedMessage: Uint8Array): string => {
|
||||||
|
const n = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70]
|
||||||
|
const outarr: number[] = []
|
||||||
|
|
||||||
|
for(let i = 0; i < decryptedMessage.length; i++) {
|
||||||
|
const val = decryptedMessage[i]
|
||||||
|
outarr.push(n[val >> 4], n[15 & val])
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.fromCharCode(...outarr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* raw function to decrypt a poll message update
|
||||||
|
* @param encPayload Encryption payload/contents want to decrypt, you can get it from `pollUpdateMessage.vote.encPayload`
|
||||||
|
* @param encIv Encryption iv (used to decrypt the payload), you can get it from `pollUpdateMessage.vote.encIv`
|
||||||
|
* @param encKey Encryption key (used to decrypt the payload), you need to store/save the encKey. If you want get the encKey, you could get it from `Message.messageContextInfo.messageSecret`, only available on poll creation message.
|
||||||
|
* @param pollMsgSender sender The sender's jid of poll message, you can use `pollUpdateMessage.pollCreationMessageKey.participant` (Note: you need to normalize the jid first)
|
||||||
|
* @param pollMsgId The ID of poll message, you can use `pollUpdateMessage.pollMessageCreationKey.id`
|
||||||
|
* @param voteMsgSender The poll voter's jid, you can use `Message.key.remoteJid`, `Message.key.participant`, or `Message.participant`. (Note: you need to normalize the jid first)
|
||||||
|
* @return {Promise<string[]>} The option or empty array if something went wrong OR everything was unticked
|
||||||
|
*/
|
||||||
|
export const decryptPollMessageRaw = async(
|
||||||
|
encKey: Uint8Array,
|
||||||
|
encPayload: Uint8Array,
|
||||||
|
encIv: Uint8Array,
|
||||||
|
pollMsgSender: string,
|
||||||
|
pollMsgId: string,
|
||||||
|
voteMsgSender: string
|
||||||
|
): Promise<string[]> => {
|
||||||
|
const enc = new TextEncoder()
|
||||||
|
const crypto = getCrypto()
|
||||||
|
|
||||||
|
const stanzaId = enc.encode(pollMsgId)
|
||||||
|
const parentMsgOriginalSender = enc.encode(pollMsgSender)
|
||||||
|
const modificationSender = enc.encode(voteMsgSender)
|
||||||
|
const modificationType = enc.encode('Poll Vote')
|
||||||
|
const pad = new Uint8Array([1])
|
||||||
|
|
||||||
|
const signMe = new Uint8Array([...stanzaId, ...parentMsgOriginalSender, ...modificationSender, ...modificationType, pad] as any)
|
||||||
|
|
||||||
|
const createSignKey = async(n: Uint8Array = new Uint8Array(32)) => {
|
||||||
|
return (await crypto.subtle.importKey('raw', n,
|
||||||
|
{ 'name': 'HMAC', 'hash': 'SHA-256' }, false, ['sign']
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
const sign = async(n: Uint8Array, key: CryptoKey) => {
|
||||||
|
return (await crypto.subtle.sign({ 'name': 'HMAC', 'hash': 'SHA-256' }, key, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = await createSignKey()
|
||||||
|
|
||||||
|
const temp = await sign(encKey, key)
|
||||||
|
|
||||||
|
key = await createSignKey(new Uint8Array(temp))
|
||||||
|
|
||||||
|
const decryptionKey = new Uint8Array(await sign(signMe, key))
|
||||||
|
|
||||||
|
const additionalData = enc.encode(`${pollMsgId}\u0000${voteMsgSender}`)
|
||||||
|
|
||||||
|
const decryptedMessage = await decryptPollMessageInternal(encPayload, encIv, additionalData, decryptionKey)
|
||||||
|
|
||||||
|
const pollOptionHash = decodePollMessage(decryptedMessage)
|
||||||
|
|
||||||
|
// '0A20' in hex represents unicode " " and "\n" thus declaring the end of one option
|
||||||
|
// we want multiple hashes to make it easier to iterate and understand for your use cases
|
||||||
|
return pollOptionHash.split('0A20') || []
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import { randomBytes } from 'crypto'
|
||||||
import { promises as fs } from 'fs'
|
import { promises as fs } from 'fs'
|
||||||
import { Logger } from 'pino'
|
import { Logger } from 'pino'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
@@ -26,6 +27,7 @@ import {
|
|||||||
import { isJidGroup, jidNormalizedUser } from '../WABinary'
|
import { isJidGroup, jidNormalizedUser } from '../WABinary'
|
||||||
import { generateMessageID, unixTimestampSeconds } from './generics'
|
import { generateMessageID, unixTimestampSeconds } from './generics'
|
||||||
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, MediaDownloadOptions } from './messages-media'
|
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, MediaDownloadOptions } from './messages-media'
|
||||||
|
import { comparePollMessage, decryptPollMessageRaw } from './messages-poll'
|
||||||
|
|
||||||
type MediaUploadData = {
|
type MediaUploadData = {
|
||||||
media: WAMediaUpload
|
media: WAMediaUpload
|
||||||
@@ -375,6 +377,37 @@ export const generateWAMessageContent = async(
|
|||||||
})
|
})
|
||||||
} else if('listReply' in message) {
|
} else if('listReply' in message) {
|
||||||
m.listResponseMessage = { ...message.listReply }
|
m.listResponseMessage = { ...message.listReply }
|
||||||
|
} else if('poll' in message) {
|
||||||
|
if(typeof message.poll.selectableCount !== 'number') {
|
||||||
|
message.poll.selectableCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Array.isArray(message.poll.values)) {
|
||||||
|
throw new Boom('Invalid poll values', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) {
|
||||||
|
throw new Boom(
|
||||||
|
`poll.selectableCount in poll should be between 0 and ${
|
||||||
|
message.poll.values.length
|
||||||
|
} or equal to the items length`, { statusCode: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// link: https://github.com/adiwajshing/Baileys/pull/2290#issuecomment-1304413425
|
||||||
|
m.messageContextInfo = {
|
||||||
|
messageSecret: randomBytes(32), // encKey
|
||||||
|
}
|
||||||
|
|
||||||
|
m.pollCreationMessage = WAProto.Message.PollCreationMessage.fromObject({
|
||||||
|
name: message.poll.name,
|
||||||
|
selectableOptionsCount: message.poll.selectableCount,
|
||||||
|
options: message.poll.values.map(
|
||||||
|
value => WAProto.Message.PollCreationMessage.Option.fromObject({
|
||||||
|
optionName: value,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
m = await prepareWAMessageMedia(
|
m = await prepareWAMessageMedia(
|
||||||
message,
|
message,
|
||||||
@@ -569,31 +602,13 @@ export const getContentType = (content: WAProto.IMessage | undefined) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const normalizeMessageContent = (content: WAMessageContent | null | undefined): WAMessageContent | undefined => {
|
export const normalizeMessageContent = (content: WAMessageContent | null | undefined): WAMessageContent | undefined => {
|
||||||
if(!content) {
|
content = content?.ephemeralMessage?.message?.viewOnceMessage?.message ||
|
||||||
return undefined
|
content?.ephemeralMessage?.message ||
|
||||||
}
|
content?.viewOnceMessage?.message ||
|
||||||
|
content?.documentWithCaptionMessage?.message ||
|
||||||
// set max iterations to prevent an infinite loop
|
content ||
|
||||||
for(let i = 0;i < 5;i++) {
|
undefined
|
||||||
const inner = getFutureProofMessage(content)
|
return content
|
||||||
if(!inner) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
content = inner.message
|
|
||||||
}
|
|
||||||
|
|
||||||
return content!
|
|
||||||
|
|
||||||
function getFutureProofMessage(message: typeof content) {
|
|
||||||
return (
|
|
||||||
message?.ephemeralMessage
|
|
||||||
|| message?.viewOnceMessage
|
|
||||||
|| message?.documentWithCaptionMessage
|
|
||||||
|| message?.viewOnceMessageV2
|
|
||||||
|| message?.editedMessage
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -790,3 +805,49 @@ export const assertMediaContent = (content: proto.IMessage | null | undefined) =
|
|||||||
|
|
||||||
return mediaContent
|
return mediaContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt/Get Poll Update Message Values
|
||||||
|
* @param msg Full message info contains PollUpdateMessage, you can use `msg`
|
||||||
|
* @param pollCreationData An object contains `encKey` (used to decrypt the poll message), `sender` (used to create decryption key), and `options` (you should fill it with poll options, e.g. Apple, Orange, etc...)
|
||||||
|
* @param withSelectedOptions Get user's selected options condition, set it to true if you want get the results.
|
||||||
|
* @return {Promise<{ hash: string[] } | { hash: string[], selectedOptions: string[] }>} Property `hash` is an array which contains selected options hash, you can use `comparePollMessage` to compare it with original values. Property `selectedOptions` is an array, and the results is from `comparePollMessage` function.
|
||||||
|
*/
|
||||||
|
export const getPollUpdateMessage = async(
|
||||||
|
msg: WAProto.IWebMessageInfo,
|
||||||
|
pollCreationData: { encKey: Uint8Array; sender: string; options: string[]; },
|
||||||
|
withSelectedOptions: boolean = false,
|
||||||
|
): Promise<{ hash: string[] } | { hash: string[]; selectedOptions: string[] }> => {
|
||||||
|
if(!msg.message?.pollUpdateMessage || !pollCreationData?.encKey) {
|
||||||
|
throw new Boom('Missing pollUpdateMessage, or encKey', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
pollCreationData.sender = msg.message?.pollUpdateMessage?.pollCreationMessageKey?.participant || pollCreationData.sender
|
||||||
|
if(!pollCreationData.sender?.length) {
|
||||||
|
throw new Boom('Missing sender', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash = await decryptPollMessageRaw(
|
||||||
|
pollCreationData.encKey, // encKey
|
||||||
|
msg.message?.pollUpdateMessage?.vote?.encPayload!, // enc payload
|
||||||
|
msg.message?.pollUpdateMessage?.vote?.encIv!, // enc iv
|
||||||
|
jidNormalizedUser(pollCreationData.sender), // sender
|
||||||
|
msg.message?.pollUpdateMessage?.pollCreationMessageKey?.id!, // poll id
|
||||||
|
jidNormalizedUser(
|
||||||
|
msg.key.remoteJid?.endsWith('@g.us') ?
|
||||||
|
(msg.key.participant || msg.participant)! : msg.key.remoteJid!
|
||||||
|
), // voter
|
||||||
|
)
|
||||||
|
|
||||||
|
if(hash.length === 1 && !hash[0].length) {
|
||||||
|
hash = []
|
||||||
|
}
|
||||||
|
|
||||||
|
return withSelectedOptions ? {
|
||||||
|
hash,
|
||||||
|
selectedOptions: await comparePollMessage(
|
||||||
|
pollCreationData.options || [],
|
||||||
|
hash,
|
||||||
|
)
|
||||||
|
} : { hash }
|
||||||
|
}
|
||||||
61
yarn.lock
61
yarn.lock
@@ -898,6 +898,33 @@
|
|||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.0":
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.3.tgz#21418e1f3819e0b353ceff0c2dad8ccb61acd777"
|
||||||
|
integrity sha512-6GptMYDMyWBHTUKndHaDsRZUO/XMSgIns2krxcm2L7SEExRHwawFvSwNBhqNPR9HJwv3MruAiF1bhN0we6j6GQ==
|
||||||
|
dependencies:
|
||||||
|
asn1js "^3.0.5"
|
||||||
|
pvtsutils "^1.3.2"
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
|
"@peculiar/json-schema@^1.1.12":
|
||||||
|
version "1.1.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz#fe61e85259e3b5ba5ad566cb62ca75b3d3cd5339"
|
||||||
|
integrity sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
"@peculiar/webcrypto@^1.4.1":
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.1.tgz#821493bd5ad0f05939bd5f53b28536f68158360a"
|
||||||
|
integrity sha512-eK4C6WTNYxoI7JOabMoZICiyqRRtJB220bh0Mbj5RwRycleZf9BPyZoxsTvpP0FpmVS2aS13NKOuh5/tN3sIRw==
|
||||||
|
dependencies:
|
||||||
|
"@peculiar/asn1-schema" "^2.3.0"
|
||||||
|
"@peculiar/json-schema" "^1.1.12"
|
||||||
|
pvtsutils "^1.3.2"
|
||||||
|
tslib "^2.4.1"
|
||||||
|
webcrypto-core "^1.7.4"
|
||||||
|
|
||||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||||
@@ -1394,6 +1421,15 @@ array.prototype.flatmap@^1.3.0:
|
|||||||
es-abstract "^1.19.2"
|
es-abstract "^1.19.2"
|
||||||
es-shim-unscopables "^1.0.0"
|
es-shim-unscopables "^1.0.0"
|
||||||
|
|
||||||
|
asn1js@^3.0.1, asn1js@^3.0.5:
|
||||||
|
version "3.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38"
|
||||||
|
integrity sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==
|
||||||
|
dependencies:
|
||||||
|
pvtsutils "^1.3.2"
|
||||||
|
pvutils "^1.1.3"
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
asynckit@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
@@ -4206,6 +4242,18 @@ punycode@^2.1.0, punycode@^2.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||||
|
|
||||||
|
pvtsutils@^1.3.2:
|
||||||
|
version "1.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.2.tgz#9f8570d132cdd3c27ab7d51a2799239bf8d8d5de"
|
||||||
|
integrity sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
|
pvutils@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.3.tgz#f35fc1d27e7cd3dfbd39c0826d173e806a03f5a3"
|
||||||
|
integrity sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==
|
||||||
|
|
||||||
qrcode-terminal@^0.12.0:
|
qrcode-terminal@^0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819"
|
resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819"
|
||||||
@@ -4841,7 +4889,7 @@ tslib@^1.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslib@^2.4.0:
|
tslib@^2.0.0, tslib@^2.4.0, tslib@^2.4.1:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
||||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
||||||
@@ -5004,6 +5052,17 @@ walker@^1.0.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
makeerror "1.0.12"
|
makeerror "1.0.12"
|
||||||
|
|
||||||
|
webcrypto-core@^1.7.4:
|
||||||
|
version "1.7.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.5.tgz#c02104c953ca7107557f9c165d194c6316587ca4"
|
||||||
|
integrity sha512-gaExY2/3EHQlRNNNVSrbG2Cg94Rutl7fAaKILS1w8ZDhGxdFOaw6EbCfHIxPy9vt/xwp5o0VQAx9aySPF6hU1A==
|
||||||
|
dependencies:
|
||||||
|
"@peculiar/asn1-schema" "^2.1.6"
|
||||||
|
"@peculiar/json-schema" "^1.1.12"
|
||||||
|
asn1js "^3.0.1"
|
||||||
|
pvtsutils "^1.3.2"
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
webidl-conversions@^3.0.0:
|
webidl-conversions@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||||
|
|||||||
Reference in New Issue
Block a user