feat: add "strictNullChecks"

This commit is contained in:
Adhiraj Singh
2022-07-08 10:38:25 +05:30
parent 7426b7aa2f
commit 40a1e268aa
42 changed files with 350 additions and 339 deletions

View File

@@ -25,7 +25,7 @@ export const WA_CERT_DETAILS = {
SERIAL: 0, SERIAL: 0,
} }
const BASE_CONNECTION_CONFIG: CommonSocketConfig<any> = { const BASE_CONNECTION_CONFIG: CommonSocketConfig = {
version: version as any, version: version as any,
browser: Browsers.baileys('Chrome'), browser: Browsers.baileys('Chrome'),
@@ -42,6 +42,7 @@ const BASE_CONNECTION_CONFIG: CommonSocketConfig<any> = {
export const DEFAULT_CONNECTION_CONFIG: SocketConfig = { export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
...BASE_CONNECTION_CONFIG, ...BASE_CONNECTION_CONFIG,
auth: undefined as any,
downloadHistory: true, downloadHistory: true,
markOnlineOnConnect: true, markOnlineOnConnect: true,
linkPreviewImageThumbnailWidth: 192, linkPreviewImageThumbnailWidth: 192,

View File

@@ -27,7 +27,7 @@ const makeAuthSocket = (config: LegacySocketConfig) => {
const socket = makeSocket(config) const socket = makeSocket(config)
const { ws } = socket const { ws } = socket
let curveKeys: CurveKeyPair let curveKeys: CurveKeyPair
let initTimeout: NodeJS.Timeout let initTimeout: NodeJS.Timeout | undefined
ws.on('phone-connection', ({ value: phoneConnected }) => { ws.on('phone-connection', ({ value: phoneConnected }) => {
updateState({ legacy: { ...state.legacy, phoneConnected } }) updateState({ legacy: { ...state.legacy, phoneConnected } })
@@ -133,7 +133,7 @@ const makeAuthSocket = (config: LegacySocketConfig) => {
generateKeysForAuth(ref, ttl) generateKeysForAuth(ref, ttl)
} }
})() })()
let loginTag: string let loginTag: string | undefined
if(canDoLogin) { if(canDoLogin) {
updateEncKeys() updateEncKeys()
// if we have the info to restore a closed session // if we have the info to restore a closed session

View File

@@ -114,9 +114,9 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
{ {
tag: 'read', tag: 'read',
attrs: { attrs: {
jid: fromMessage.remoteJid, jid: fromMessage.remoteJid!,
count: count.toString(), count: count.toString(),
index: fromMessage.id, index: fromMessage.id!,
owner: fromMessage.fromMe ? 'true' : 'false' owner: fromMessage.fromMe ? 'true' : 'false'
} }
} }
@@ -124,7 +124,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
[ WAMetric.read, WAFlag.ignore ] [ WAMetric.read, WAFlag.ignore ]
) )
if(config.emitOwnEvents) { if(config.emitOwnEvents) {
ev.emit('chats.update', [{ id: fromMessage.remoteJid, unreadCount: count < 0 ? -1 : 0 }]) ev.emit('chats.update', [{ id: fromMessage.remoteJid!, unreadCount: count < 0 ? -1 : 0 }])
} }
} }
@@ -225,10 +225,11 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
}) })
// User Profile Name Updates // User Profile Name Updates
socketEvents.on('CB:Conn,pushname', json => { socketEvents.on('CB:Conn,pushname', json => {
const { legacy: { user }, connection } = state const { legacy, connection } = state
if(connection === 'open' && json[1].pushname !== user.name) { const { user } = legacy!
user.name = json[1].pushname if(connection === 'open' && json[1].pushname !== user!.name) {
ev.emit('connection.update', { legacy: { ...state.legacy, user } }) user!.name = json[1].pushname
ev.emit('connection.update', { legacy: { ...legacy!, user } })
} }
}) })
// read updates // read updates
@@ -354,7 +355,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
} }
)) ))
} else if('markRead' in modification) { } else if('markRead' in modification) {
const indexKey = getIndexKey(modification.lastMessages) const indexKey = getIndexKey(modification.lastMessages)!
return chatRead(indexKey, modification.markRead ? 0 : -1) return chatRead(indexKey, modification.markRead ? 0 : -1)
} else if('delete' in modification) { } else if('delete' in modification) {
chatAttrs.type = 'delete' chatAttrs.type = 'delete'
@@ -363,7 +364,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
if('lastMessages' in modification) { if('lastMessages' in modification) {
const indexKey = getIndexKey(modification.lastMessages) const indexKey = getIndexKey(modification.lastMessages)
if(indexKey) { if(indexKey) {
chatAttrs.index = indexKey.id chatAttrs.index = indexKey.id!
chatAttrs.owner = indexKey.fromMe ? 'true' : 'false' chatAttrs.owner = indexKey.fromMe ? 'true' : 'false'
} }
} }
@@ -409,7 +410,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
content: [ content: [
{ {
tag: 'presence', tag: 'presence',
attrs: { type: type, to: toJid } attrs: { type: type, to: toJid! }
} }
] ]
} }
@@ -463,7 +464,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
if(config.emitOwnEvents) { if(config.emitOwnEvents) {
const user = { ...state.legacy!.user!, name } const user = { ...state.legacy!.user!, name }
ev.emit('connection.update', { legacy: { ev.emit('connection.update', { legacy: {
...state.legacy, user ...state.legacy!, user
} }) } })
ev.emit('contacts.update', [{ id: user.id, name }]) ev.emit('contacts.update', [{ id: user.id, name }])
} }
@@ -493,11 +494,11 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
const { eurl } = await this.setQuery ([query], [WAMetric.picture, 136], tag) as { eurl: string, status: number } const { eurl } = await this.setQuery ([query], [WAMetric.picture, 136], tag) as { eurl: string, status: number }
if(config.emitOwnEvents) { if(config.emitOwnEvents) {
if(jid === user.id) { if(jid === user?.id) {
user.imgUrl = eurl user.imgUrl = eurl
ev.emit('connection.update', { ev.emit('connection.update', {
legacy: { legacy: {
...state.legacy, ...state.legacy!,
user user
} }
}) })

View File

@@ -23,11 +23,11 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
{ {
tag: 'group', tag: 'group',
attrs: { attrs: {
author: state.legacy?.user?.id, author: state.legacy?.user?.id!,
id: tag, id: tag,
type: type, type: type,
jid: jid, jid: jid!,
subject: subject, subject: subject!,
}, },
content: participants ? content: participants ?
participants.map(jid => ( participants.map(jid => (
@@ -96,7 +96,7 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
id: jid, id: jid,
owner: attrs?.creator, owner: attrs?.creator,
creation: +attrs?.create, creation: +attrs?.create,
subject: null, subject: '',
desc, desc,
participants participants
} }
@@ -146,8 +146,8 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
* @param participants people to include in the group * @param participants people to include in the group
*/ */
groupCreate: async(title: string, participants: string[]) => { groupCreate: async(title: string, participants: string[]) => {
const response = await groupQuery('create', null, title, participants) as WAGroupCreateResponse const response = await groupQuery('create', undefined, title, participants) as WAGroupCreateResponse
const gid = response.gid const gid = response.gid!
let metadata: GroupMetadata let metadata: GroupMetadata
try { try {
metadata = await groupMetadataFull(gid) metadata = await groupMetadataFull(gid)
@@ -199,11 +199,11 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
const metadata = await groupMetadataFull(jid) const metadata = await groupMetadataFull(jid)
const node: BinaryNode = { const node: BinaryNode = {
tag: 'description', tag: 'description',
attrs: { id: generateMessageID(), prev: metadata?.descId }, attrs: { id: generateMessageID(), prev: metadata?.descId! },
content: Buffer.from(description, 'utf-8') content: Buffer.from(description, 'utf-8')
} }
const response = await groupQuery ('description', jid, null, null, [node]) const response = await groupQuery('description', jid, undefined, undefined, [node])
ev.emit('groups.update', [ { id: jid, desc: description } ]) ev.emit('groups.update', [ { id: jid, desc: description } ])
return response return response
}, },
@@ -213,7 +213,7 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
* @param participants the people to add * @param participants the people to add
*/ */
groupParticipantsUpdate: async(id: string, participants: string[], action: ParticipantAction) => { groupParticipantsUpdate: async(id: string, participants: string[], action: ParticipantAction) => {
const result: GroupModificationResponse = await groupQuery(action, id, null, participants) const result: GroupModificationResponse = await groupQuery(action, id, undefined, participants)
const jids = Object.keys(result.participants || {}) const jids = Object.keys(result.participants || {})
ev.emit('group-participants.update', { id, participants: jids, action }) ev.emit('group-participants.update', { id, participants: jids, action })
return Object.keys(result.participants || {}).map( return Object.keys(result.participants || {}).map(
@@ -237,7 +237,6 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
const metadata: GroupMetadata = { const metadata: GroupMetadata = {
subject: result.name, subject: result.name,
id: jid, id: jid,
creation: undefined,
owner: state.legacy?.user?.id, owner: state.legacy?.user?.id,
participants: result.recipients!.map(({ id }) => ( participants: result.recipients!.map(({ id }) => (
{ id: jidNormalizedUser(id), isAdmin: false, isSuperAdmin: false } { id: jidNormalizedUser(id), isAdmin: false, isSuperAdmin: false }

View File

@@ -47,7 +47,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
count: number, count: number,
cursor?: WAMessageCursor cursor?: WAMessageCursor
) => { ) => {
let key: WAMessageKey let key: WAMessageKey | undefined
if(cursor) { if(cursor) {
key = 'before' in cursor ? cursor.before : cursor.after key = 'before' in cursor ? cursor.before : cursor.after
} }
@@ -61,7 +61,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
jid: jid, jid: jid,
kind: !cursor || 'before' in cursor ? 'before' : 'after', kind: !cursor || 'before' in cursor ? 'before' : 'after',
count: count.toString(), count: count.toString(),
index: key?.id, index: key?.id!,
owner: key?.fromMe === false ? 'false' : 'true', owner: key?.fromMe === false ? 'false' : 'true',
} }
}, },
@@ -84,9 +84,9 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
tag: 'query', tag: 'query',
attrs: { attrs: {
type: 'media', type: 'media',
index: message.key.id, index: message.key.id!,
owner: message.key.fromMe ? 'true' : 'false', owner: message.key.fromMe ? 'true' : 'false',
jid: message.key.remoteJid, jid: message.key.remoteJid!,
epoch: currentEpoch().toString() epoch: currentEpoch().toString()
} }
}, },
@@ -158,7 +158,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
{ {
// the key of the deleted message is updated // the key of the deleted message is updated
update: { message: null, key: message.key, messageStubType }, update: { message: null, key: message.key, messageStubType },
key key: key!
} }
]) ])
return return
@@ -167,7 +167,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
chatUpdate.ephemeralExpiration = protocolMessage.ephemeralExpiration chatUpdate.ephemeralExpiration = protocolMessage.ephemeralExpiration
if(isJidGroup(jid)) { if(isJidGroup(jid)) {
emitGroupUpdate({ ephemeralDuration: protocolMessage.ephemeralExpiration || null }) emitGroupUpdate({ ephemeralDuration: protocolMessage.ephemeralExpiration || 0 })
} }
break break
@@ -188,18 +188,18 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
switch (message.messageStubType) { switch (message.messageStubType) {
case WAMessageStubType.CHANGE_EPHEMERAL_SETTING: case WAMessageStubType.CHANGE_EPHEMERAL_SETTING:
chatUpdate.ephemeralSettingTimestamp = message.messageTimestamp chatUpdate.ephemeralSettingTimestamp = message.messageTimestamp
chatUpdate.ephemeralExpiration = +message.messageStubParameters[0] chatUpdate.ephemeralExpiration = +message.messageStubParameters![0]
if(isJidGroup(jid)) { if(isJidGroup(jid)) {
emitGroupUpdate({ ephemeralDuration: +message.messageStubParameters[0] || null }) emitGroupUpdate({ ephemeralDuration: +(message.messageStubParameters?.[0] || 0) })
} }
break break
case WAMessageStubType.GROUP_PARTICIPANT_LEAVE: case WAMessageStubType.GROUP_PARTICIPANT_LEAVE:
case WAMessageStubType.GROUP_PARTICIPANT_REMOVE: case WAMessageStubType.GROUP_PARTICIPANT_REMOVE:
participants = message.messageStubParameters.map (jidNormalizedUser) participants = message.messageStubParameters!.map (jidNormalizedUser)
emitParticipantsUpdate('remove') emitParticipantsUpdate('remove')
// mark the chat read only if you left the group // mark the chat read only if you left the group
if(participants.includes(user.id)) { if(participants.includes(user!.id)) {
chatUpdate.readOnly = true chatUpdate.readOnly = true
} }
@@ -207,24 +207,24 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
case WAMessageStubType.GROUP_PARTICIPANT_ADD: case WAMessageStubType.GROUP_PARTICIPANT_ADD:
case WAMessageStubType.GROUP_PARTICIPANT_INVITE: case WAMessageStubType.GROUP_PARTICIPANT_INVITE:
case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN: case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
participants = message.messageStubParameters.map (jidNormalizedUser) participants = message.messageStubParameters!.map (jidNormalizedUser)
if(participants.includes(user.id)) { if(participants.includes(user!.id)) {
chatUpdate.readOnly = null chatUpdate.readOnly = null
} }
emitParticipantsUpdate('add') emitParticipantsUpdate('add')
break break
case WAMessageStubType.GROUP_CHANGE_ANNOUNCE: case WAMessageStubType.GROUP_CHANGE_ANNOUNCE:
const announce = message.messageStubParameters[0] === 'on' const announce = message.messageStubParameters?.[0] === 'on'
emitGroupUpdate({ announce }) emitGroupUpdate({ announce })
break break
case WAMessageStubType.GROUP_CHANGE_RESTRICT: case WAMessageStubType.GROUP_CHANGE_RESTRICT:
const restrict = message.messageStubParameters[0] === 'on' const restrict = message.messageStubParameters?.[0] === 'on'
emitGroupUpdate({ restrict }) emitGroupUpdate({ restrict })
break break
case WAMessageStubType.GROUP_CHANGE_SUBJECT: case WAMessageStubType.GROUP_CHANGE_SUBJECT:
case WAMessageStubType.GROUP_CREATE: case WAMessageStubType.GROUP_CREATE:
chatUpdate.name = message.messageStubParameters[0] chatUpdate.name = message.messageStubParameters?.[0]
emitGroupUpdate({ subject: chatUpdate.name }) emitGroupUpdate({ subject: chatUpdate.name })
break break
} }
@@ -275,9 +275,9 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
} }
] ]
} }
const isMsgToMe = areJidsSameUser(message.key.remoteJid, state.legacy.user?.id || '') const isMsgToMe = areJidsSameUser(message.key.remoteJid!, state.legacy?.user?.id || '')
const flag = isMsgToMe ? WAFlag.acknowledge : WAFlag.ignore // acknowledge when sending message to oneself const flag = isMsgToMe ? WAFlag.acknowledge : WAFlag.ignore // acknowledge when sending message to oneself
const mID = message.key.id const mID = message.key.id!
const finalState = isMsgToMe ? WAMessageStatus.READ : WAMessageStatus.SERVER_ACK const finalState = isMsgToMe ? WAMessageStatus.READ : WAMessageStatus.SERVER_ACK
message.status = WAMessageStatus.PENDING message.status = WAMessageStatus.PENDING
@@ -498,7 +498,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
search: txt, search: txt,
count: count.toString(), count: count.toString(),
page: page.toString(), page: page.toString(),
jid: inJid jid: inJid!
} }
}, },
binaryTag: [24, WAFlag.ignore], binaryTag: [24, WAFlag.ignore],
@@ -515,7 +515,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
content: AnyMessageContent, content: AnyMessageContent,
options: MiscMessageGenerationOptions & { waitForAck?: boolean } = { waitForAck: true } options: MiscMessageGenerationOptions & { waitForAck?: boolean } = { waitForAck: true }
) => { ) => {
const userJid = state.legacy.user?.id const userJid = state.legacy?.user?.id
if( if(
typeof content === 'object' && typeof content === 'object' &&
'disappearingMessagesInChat' in content && 'disappearingMessagesInChat' in content &&
@@ -530,7 +530,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
await setQuery([ await setQuery([
{ {
tag: 'group', tag: 'group',
attrs: { id: tag, jid, type: 'prop', author: userJid }, attrs: { id: tag, jid, type: 'prop', author: userJid! },
content: [ content: [
{ tag: 'ephemeral', attrs: { value: value.toString() } } { tag: 'ephemeral', attrs: { value: value.toString() } }
] ]
@@ -542,7 +542,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
content, content,
{ {
logger, logger,
userJid: userJid, userJid: userJid!,
getUrlInfo: generateUrlInfo, getUrlInfo: generateUrlInfo,
upload: waUploadToServer, upload: waUploadToServer,
mediaCache: config.mediaCache, mediaCache: config.mediaCache,
@@ -550,7 +550,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
} }
) )
await relayMessage(msg, { waitForAck: options.waitForAck }) await relayMessage(msg, { waitForAck: !!options.waitForAck })
return msg return msg
} }
} }

View File

@@ -43,7 +43,7 @@ export const makeSocket = ({
let authInfo: { encKey: Buffer, macKey: Buffer } let authInfo: { encKey: Buffer, macKey: Buffer }
let keepAliveReq: NodeJS.Timeout let keepAliveReq: NodeJS.Timeout
let phoneCheckInterval: NodeJS.Timeout let phoneCheckInterval: NodeJS.Timeout | undefined
let phoneCheckListeners = 0 let phoneCheckListeners = 0
const phoneConnectionChanged = (value: boolean) => { const phoneConnectionChanged = (value: boolean) => {
@@ -267,10 +267,10 @@ export const makeSocket = ({
return result as any return result as any
} finally { } finally {
requiresPhoneConnection && clearPhoneCheckInterval() requiresPhoneConnection && clearPhoneCheckInterval()
cancelPhoneChecker && cancelPhoneChecker() cancelPhoneChecker! && cancelPhoneChecker!()
ws.off(`TAG:${tag}`, onRecv) ws.off(`TAG:${tag}`, onRecv!)
ws.off('ws-close', onErr) // if the socket closes, you'll never receive the message ws.off('ws-close', onErr!) // if the socket closes, you'll never receive the message
} }
})(), })(),
cancelToken: () => { cancelToken: () => {
@@ -290,7 +290,7 @@ export const makeSocket = ({
{ json, timeoutMs, expect200, tag, longTag, binaryTag, requiresPhoneConnection }: SocketQueryOptions { json, timeoutMs, expect200, tag, longTag, binaryTag, requiresPhoneConnection }: SocketQueryOptions
) => { ) => {
tag = tag || generateMessageTag(longTag) tag = tag || generateMessageTag(longTag)
const { promise, cancelToken } = waitForMessage(tag, requiresPhoneConnection, timeoutMs) const { promise, cancelToken } = waitForMessage(tag, !!requiresPhoneConnection, timeoutMs)
try { try {
await sendNode({ json, tag, binaryTag }) await sendNode({ json, tag, binaryTag })
} catch(error) { } catch(error) {

View File

@@ -13,7 +13,8 @@ export const makeBusinessSocket = (config: SocketConfig) => {
} = sock } = sock
const getCatalog = async(jid?: string, limit = 10) => { const getCatalog = async(jid?: string, limit = 10) => {
jid = jidNormalizedUser(jid || authState.creds.me?.id) jid = jid || authState.creds.me?.id
jid = jidNormalizedUser(jid!)
const result = await query({ const result = await query({
tag: 'iq', tag: 'iq',
attrs: { attrs: {
@@ -52,7 +53,8 @@ export const makeBusinessSocket = (config: SocketConfig) => {
} }
const getCollections = async(jid?: string, limit = 51) => { const getCollections = async(jid?: string, limit = 51) => {
jid = jidNormalizedUser(jid || authState.creds.me?.id) jid = jid || authState.creds.me?.id
jid = jidNormalizedUser(jid!)
const result = await query({ const result = await query({
tag: 'iq', tag: 'iq',
attrs: { attrs: {
@@ -165,7 +167,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
const productCatalogEditNode = getBinaryNodeChild(result, 'product_catalog_edit') const productCatalogEditNode = getBinaryNodeChild(result, 'product_catalog_edit')
const productNode = getBinaryNodeChild(productCatalogEditNode, 'product') const productNode = getBinaryNodeChild(productCatalogEditNode, 'product')
return parseProductNode(productNode) return parseProductNode(productNode!)
} }
const productCreate = async(create: ProductCreate) => { const productCreate = async(create: ProductCreate) => {
@@ -191,7 +193,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
const productCatalogAddNode = getBinaryNodeChild(result, 'product_catalog_add') const productCatalogAddNode = getBinaryNodeChild(result, 'product_catalog_add')
const productNode = getBinaryNodeChild(productCatalogAddNode, 'product') const productNode = getBinaryNodeChild(productCatalogAddNode, 'product')
return parseProductNode(productNode) return parseProductNode(productNode!)
} }
const productDelete = async(productIds: string[]) => { const productDelete = async(productIds: string[]) => {
@@ -225,7 +227,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
const productCatalogDelNode = getBinaryNodeChild(result, 'product_catalog_delete') const productCatalogDelNode = getBinaryNodeChild(result, 'product_catalog_delete')
return { return {
deleted: +productCatalogDelNode.attrs.deleted_count deleted: +(productCatalogDelNode?.attrs.deleted_count || 0)
} }
} }

View File

@@ -67,7 +67,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
{ tag: 'privacy', attrs: { } } { tag: 'privacy', attrs: { } }
] ]
}) })
privacySettings = reduceBinaryNodeToDictionary(content[0] as BinaryNode, 'category') privacySettings = reduceBinaryNodeToDictionary(content?.[0] as BinaryNode, 'category')
} }
return privacySettings return privacySettings
@@ -135,7 +135,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
return results.map(user => { return results.map(user => {
const contact = getBinaryNodeChild(user, 'contact') const contact = getBinaryNodeChild(user, 'contact')
return { exists: contact.attrs.type === 'in', jid: user.attrs.jid } return { exists: contact?.attrs.type === 'in', jid: user.attrs.jid }
}).filter(item => item.exists) }).filter(item => item.exists)
} }
@@ -147,8 +147,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
if(result) { if(result) {
const status = getBinaryNodeChild(result, 'status') const status = getBinaryNodeChild(result, 'status')
return { return {
status: status.content!.toString(), status: status?.content!.toString(),
setAt: new Date(+status.attrs.t * 1000) setAt: new Date(+(status?.attrs.t || 0) * 1000)
} }
} }
} }
@@ -253,18 +253,19 @@ export const makeChatsSocket = (config: SocketConfig) => {
const category = getBinaryNodeChild(getBinaryNodeChild(profiles, 'categories'), 'category') const category = getBinaryNodeChild(getBinaryNodeChild(profiles, 'categories'), 'category')
const business_hours = getBinaryNodeChild(profiles, 'business_hours') const business_hours = getBinaryNodeChild(profiles, 'business_hours')
const business_hours_config = business_hours && getBinaryNodeChildren(business_hours, 'business_hours_config') const business_hours_config = business_hours && getBinaryNodeChildren(business_hours, 'business_hours_config')
const websiteStr = website?.content?.toString()
return { return {
wid: profiles.attrs?.jid, wid: profiles.attrs?.jid,
address: address?.content.toString(), address: address?.content?.toString(),
description: description?.content.toString(), description: description?.content?.toString() || '',
website: [website?.content.toString()], website: websiteStr ? [websiteStr] : [],
email: email?.content.toString(), email: email?.content?.toString(),
category: category?.content.toString(), category: category?.content?.toString(),
business_hours: { business_hours: {
timezone: business_hours?.attrs?.timezone, timezone: business_hours?.attrs?.timezone,
business_config: business_hours_config?.map(({ attrs }) => attrs as unknown as WABusinessHoursConfig) business_config: business_hours_config?.map(({ attrs }) => attrs as unknown as WABusinessHoursConfig)
} }
} as unknown as WABusinessProfile }
} }
} }
@@ -296,7 +297,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
processSyncAction( processSyncAction(
mutation, mutation,
ev, ev,
authState.creds.me, authState.creds.me!,
recvChats ? { recvChats, accountSettings: authState.creds.accountSettings } : undefined, recvChats ? { recvChats, accountSettings: authState.creds.accountSettings } : undefined,
logger logger
) )
@@ -402,7 +403,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
} catch(error) { } catch(error) {
// if retry attempts overshoot // if retry attempts overshoot
// or key not found // or key not found
const isIrrecoverableError = attemptsMap[name] >= MAX_SYNC_ATTEMPTS || error.output?.statusCode === 404 const isIrrecoverableError = attemptsMap[name]! >= MAX_SYNC_ATTEMPTS || error.output?.statusCode === 404
logger.info({ name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`) logger.info({ name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`)
await authState.keys.set({ 'app-state-sync-version': { [name]: null } }) await authState.keys.set({ 'app-state-sync-version': { [name]: null } })
// increment number of retries // increment number of retries
@@ -468,7 +469,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
tag: 'chatstate', tag: 'chatstate',
attrs: { attrs: {
from: me!.id!, from: me!.id!,
to: toJid, to: toJid!,
}, },
content: [ content: [
{ {
@@ -492,7 +493,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
) )
const handlePresenceUpdate = ({ tag, attrs, content }: BinaryNode) => { const handlePresenceUpdate = ({ tag, attrs, content }: BinaryNode) => {
let presence: PresenceData let presence: PresenceData | undefined
const jid = attrs.from const jid = attrs.from
const participant = attrs.participant || attrs.from const participant = attrs.participant || attrs.from
if(tag === 'presence') { if(tag === 'presence') {
@@ -606,8 +607,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
const { onMutation } = newAppStateChunkHandler(undefined) const { onMutation } = newAppStateChunkHandler(undefined)
await decodePatches( await decodePatches(
name, name,
[{ ...encodeResult.patch, version: { version: encodeResult.state.version }, }], [{ ...encodeResult!.patch, version: { version: encodeResult!.state.version }, }],
initial, initial!,
getAppStateSyncKey, getAppStateSyncKey,
onMutation, onMutation,
undefined, undefined,
@@ -698,10 +699,10 @@ export const makeChatsSocket = (config: SocketConfig) => {
if(!!msg.pushName) { if(!!msg.pushName) {
let jid = msg.key.fromMe ? authState.creds.me!.id : (msg.key.participant || msg.key.remoteJid) let jid = msg.key.fromMe ? authState.creds.me!.id : (msg.key.participant || msg.key.remoteJid)
jid = jidNormalizedUser(jid) jid = jidNormalizedUser(jid!)
if(!msg.key.fromMe) { if(!msg.key.fromMe) {
ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }]) ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName! }])
} }
// update our pushname too // update our pushname too
@@ -724,7 +725,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
} }
) )
const isAnyHistoryMsg = isHistoryMsg(msg.message) const isAnyHistoryMsg = isHistoryMsg(msg.message!)
if(isAnyHistoryMsg) { if(isAnyHistoryMsg) {
// we only want to sync app state once we've all the history // we only want to sync app state once we've all the history
// restart the app state sync timeout // restart the app state sync timeout
@@ -741,7 +742,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
ws.on('CB:chatstate', handlePresenceUpdate) ws.on('CB:chatstate', handlePresenceUpdate)
ws.on('CB:ib,,dirty', async(node: BinaryNode) => { ws.on('CB:ib,,dirty', async(node: BinaryNode) => {
const { attrs } = getBinaryNodeChild(node, 'dirty') const { attrs } = getBinaryNodeChild(node, 'dirty')!
const type = attrs.type const type = attrs.type
switch (type) { switch (type) {
case 'account_sync': case 'account_sync':

View File

@@ -120,7 +120,9 @@ export const makeGroupsSocket = (config: SocketConfig) => {
...(description ? { id: generateMessageID() } : { delete: 'true' }), ...(description ? { id: generateMessageID() } : { delete: 'true' }),
...(prev ? { prev } : {}) ...(prev ? { prev } : {})
}, },
content: description ? [{ tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8') }] : null content: description ? [
{ tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8') }
] : undefined
} }
] ]
) )
@@ -128,17 +130,17 @@ export const makeGroupsSocket = (config: SocketConfig) => {
groupInviteCode: async(jid: string) => { groupInviteCode: async(jid: string) => {
const result = await groupQuery(jid, 'get', [{ tag: 'invite', attrs: {} }]) const result = await groupQuery(jid, 'get', [{ tag: 'invite', attrs: {} }])
const inviteNode = getBinaryNodeChild(result, 'invite') const inviteNode = getBinaryNodeChild(result, 'invite')
return inviteNode.attrs.code return inviteNode?.attrs.code
}, },
groupRevokeInvite: async(jid: string) => { groupRevokeInvite: async(jid: string) => {
const result = await groupQuery(jid, 'set', [{ tag: 'invite', attrs: {} }]) const result = await groupQuery(jid, 'set', [{ tag: 'invite', attrs: {} }])
const inviteNode = getBinaryNodeChild(result, 'invite') const inviteNode = getBinaryNodeChild(result, 'invite')
return inviteNode.attrs.code return inviteNode?.attrs.code
}, },
groupAcceptInvite: async(code: string) => { groupAcceptInvite: async(code: string) => {
const results = await groupQuery('@g.us', 'set', [{ tag: 'invite', attrs: { code } }]) const results = await groupQuery('@g.us', 'set', [{ tag: 'invite', attrs: { code } }])
const result = getBinaryNodeChild(results, 'group') const result = getBinaryNodeChild(results, 'group')
return result.attrs.jid return result?.attrs.jid
}, },
/** /**
* accept a GroupInviteMessage * accept a GroupInviteMessage
@@ -147,11 +149,11 @@ export const makeGroupsSocket = (config: SocketConfig) => {
*/ */
groupAcceptInviteV4: async(key: string | WAMessageKey, inviteMessage: proto.IGroupInviteMessage) => { groupAcceptInviteV4: async(key: string | WAMessageKey, inviteMessage: proto.IGroupInviteMessage) => {
key = typeof key === 'string' ? { remoteJid: key } : key key = typeof key === 'string' ? { remoteJid: key } : key
const results = await groupQuery(inviteMessage.groupJid, 'set', [{ const results = await groupQuery(inviteMessage.groupJid!, 'set', [{
tag: 'accept', tag: 'accept',
attrs: { attrs: {
code: inviteMessage.inviteCode, code: inviteMessage.inviteCode!,
expiration: inviteMessage.inviteExpiration.toString(), expiration: inviteMessage.inviteExpiration!.toString(),
admin: key.remoteJid! admin: key.remoteJid!
} }
}]) }])
@@ -254,7 +256,7 @@ export const makeGroupsSocket = (config: SocketConfig) => {
export const extractGroupMetadata = (result: BinaryNode) => { export const extractGroupMetadata = (result: BinaryNode) => {
const group = getBinaryNodeChild(result, 'group') const group = getBinaryNodeChild(result, 'group')!
const descChild = getBinaryNodeChild(group, 'description') const descChild = getBinaryNodeChild(group, 'description')
let desc: string | undefined let desc: string | undefined
let descId: string | undefined let descId: string | undefined

View File

@@ -76,7 +76,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const isGroup = !!node.attrs.participant const isGroup = !!node.attrs.participant
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
const deviceIdentity = proto.ADVSignedDeviceIdentity.encode(account).finish() const deviceIdentity = proto.ADVSignedDeviceIdentity.encode(account!).finish()
await authState.keys.transaction( await authState.keys.transaction(
async() => { async() => {
const { update, preKeys } = await getNextPreKeys(authState, 1) const { update, preKeys } = await getNextPreKeys(authState, 1)
@@ -147,7 +147,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const from = node.attrs.from const from = node.attrs.from
if(from === S_WHATSAPP_NET) { if(from === S_WHATSAPP_NET) {
const countChild = getBinaryNodeChild(node, 'count') const countChild = getBinaryNodeChild(node, 'count')
const count = +countChild.attrs.value const count = +countChild!.attrs.value
const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count') logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count')
@@ -270,22 +270,23 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const sendMessagesAgain = async(key: proto.IMessageKey, ids: string[]) => { const sendMessagesAgain = async(key: proto.IMessageKey, ids: string[]) => {
const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id }))) const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })))
const remoteJid = key.remoteJid!
const participant = key.participant || key.remoteJid const participant = key.participant || remoteJid
// if it's the primary jid sending the request // if it's the primary jid sending the request
// just re-send the message to everyone // just re-send the message to everyone
// prevents the first message decryption failure // prevents the first message decryption failure
const sendToAll = !jidDecode(participant).device const sendToAll = !jidDecode(participant)?.device
await assertSessions([participant], true) await assertSessions([participant], true)
if(isJidGroup(key.remoteJid)) { if(isJidGroup(remoteJid)) {
await authState.keys.set({ 'sender-key-memory': { [key.remoteJid]: null } }) await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } })
} }
logger.debug({ participant, sendToAll }, 'forced new session for retry recp') logger.debug({ participant, sendToAll }, 'forced new session for retry recp')
for(let i = 0; i < msgs.length;i++) { for(let i = 0; i < msgs.length;i++) {
if(msgs[i]) { const msg = msgs[i]
if(msg) {
updateSendMessageAgainCount(ids[i], participant) updateSendMessageAgainCount(ids[i], participant)
const msgRelayOpts: MessageRelayOptions = { messageId: ids[i] } const msgRelayOpts: MessageRelayOptions = { messageId: ids[i] }
@@ -295,7 +296,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
msgRelayOpts.participant = participant msgRelayOpts.participant = participant
} }
await relayMessage(key.remoteJid, msgs[i], msgRelayOpts) await relayMessage(key.remoteJid!, msg, msgRelayOpts)
} else { } else {
logger.debug({ jid: key.remoteJid, id: ids[i] }, 'recv retry request, but message not available') logger.debug({ jid: key.remoteJid, id: ids[i] }, 'recv retry request, but message not available')
} }
@@ -443,21 +444,21 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
} else if(msg.key.fromMe) { // message was sent by us from a different device } else if(msg.key.fromMe) { // message was sent by us from a different device
type = 'sender' type = 'sender'
// need to specially handle this case // need to specially handle this case
if(isJidUser(msg.key.remoteJid)) { if(isJidUser(msg.key.remoteJid!)) {
participant = author participant = author
} }
} else if(!sendActiveReceipts) { } else if(!sendActiveReceipts) {
type = 'inactive' type = 'inactive'
} }
await sendReceipt(msg.key.remoteJid!, participant, [msg.key.id!], type) await sendReceipt(msg.key.remoteJid!, participant!, [msg.key.id!], type)
// send ack for history message // send ack for history message
const isAnyHistoryMsg = isHistoryMsg(msg.message) const isAnyHistoryMsg = isHistoryMsg(msg.message!)
if(isAnyHistoryMsg) { if(isAnyHistoryMsg) {
const jid = jidEncode(jidDecode(msg.key.remoteJid!).user, 'c.us') const jid = jidNormalizedUser(msg.key.remoteJid!)
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync') await sendReceipt(jid, undefined, [msg.key.id!], 'hist_sync')
} }
} }
@@ -516,7 +517,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const key: WAMessageKey = { remoteJid: attrs.from, fromMe: true, id: attrs.id } const key: WAMessageKey = { remoteJid: attrs.from, fromMe: true, id: attrs.id }
const msg = await getMessage(key) const msg = await getMessage(key)
if(msg) { if(msg) {
await relayMessage(key.remoteJid, msg, { messageId: key.id, useUserDevicesCache: false }) await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
} else { } else {
logger.warn({ attrs }, 'could not send message again, as it was not found') logger.warn({ attrs }, 'could not send message again, as it was not found')
} }
@@ -539,7 +540,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
// called when all offline notifs are handled // called when all offline notifs are handled
ws.on('CB:ib,,offline', async(node: BinaryNode) => { ws.on('CB:ib,,offline', async(node: BinaryNode) => {
const child = getBinaryNodeChild(node, 'offline') const child = getBinaryNodeChild(node, 'offline')
const offlineNotifs = +child.attrs.count const offlineNotifs = +(child?.attrs.count || 0)
logger.info(`handled ${offlineNotifs} offline messages/notifications`) logger.info(`handled ${offlineNotifs} offline messages/notifications`)
await ev.flush() await ev.flush()

View File

@@ -48,8 +48,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map( hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(
item => item.attrs as any item => item.attrs as any
), ),
auth: mediaConnNode.attrs.auth, auth: mediaConnNode!.attrs.auth,
ttl: +mediaConnNode.attrs.ttl, ttl: +mediaConnNode!.attrs.ttl,
fetchDate: new Date() fetchDate: new Date()
} }
logger.debug('fetched media conn') logger.debug('fetched media conn')
@@ -78,7 +78,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
if(type === 'sender' && isJidUser(jid)) { if(type === 'sender' && isJidUser(jid)) {
node.attrs.recipient = jid node.attrs.recipient = jid
node.attrs.to = participant node.attrs.to = participant!
} else { } else {
node.attrs.to = jid node.attrs.to = jid
if(participant) { if(participant) {
@@ -134,10 +134,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const users: BinaryNode[] = [] const users: BinaryNode[] = []
jids = Array.from(new Set(jids)) jids = Array.from(new Set(jids))
for(let jid of jids) { for(let jid of jids) {
const user = jidDecode(jid).user const user = jidDecode(jid)?.user
jid = jidNormalizedUser(jid) jid = jidNormalizedUser(jid)
if(userDevicesCache.has(user) && useCache) { if(userDevicesCache.has(user!) && useCache) {
const devices: JidWithDevice[] = userDevicesCache.get(user) const devices = userDevicesCache.get<JidWithDevice[]>(user!)!
deviceResults.push(...devices) deviceResults.push(...devices)
logger.trace({ user }, 'using cache for devices') logger.trace({ user }, 'using cache for devices')
@@ -278,7 +278,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
let shouldIncludeDeviceIdentity = false let shouldIncludeDeviceIdentity = false
const { user, server } = jidDecode(jid) const { user, server } = jidDecode(jid)!
const isGroup = server === 'g.us' const isGroup = server === 'g.us'
msgId = msgId || generateMessageID() msgId = msgId || generateMessageID()
useUserDevicesCache = useUserDevicesCache !== false useUserDevicesCache = useUserDevicesCache !== false
@@ -299,7 +299,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
additionalAttributes = { ...additionalAttributes, device_fanout: 'false' } additionalAttributes = { ...additionalAttributes, device_fanout: 'false' }
} }
const { user, device } = jidDecode(participant) const { user, device } = jidDecode(participant)!
devices.push({ user, device }) devices.push({ user, device })
} }
@@ -333,7 +333,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
if(!participant) { if(!participant) {
const participantsList = groupData.participants.map(p => p.id) const participantsList = groupData.participants.map(p => p.id)
const additionalDevices = await getUSyncDevices(participantsList, useUserDevicesCache, false) const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
devices.push(...additionalDevices) devices.push(...additionalDevices)
} }
@@ -376,7 +376,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } }) await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
} else { } else {
const { user: meUser } = jidDecode(meId) const { user: meUser } = jidDecode(meId)!
const encodedMeMsg = encodeWAMessage({ const encodedMeMsg = encodeWAMessage({
deviceSentMessage: { deviceSentMessage: {
@@ -389,7 +389,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
devices.push({ user }) devices.push({ user })
devices.push({ user: meUser }) devices.push({ user: meUser })
const additionalDevices = await getUSyncDevices([ meId, jid ], useUserDevicesCache, true) const additionalDevices = await getUSyncDevices([ meId, jid ], !!useUserDevicesCache, true)
devices.push(...additionalDevices) devices.push(...additionalDevices)
} }
@@ -434,7 +434,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const stanza: BinaryNode = { const stanza: BinaryNode = {
tag: 'message', tag: 'message',
attrs: { attrs: {
id: msgId, id: msgId!,
type: 'text', type: 'text',
...(additionalAttributes || {}) ...(additionalAttributes || {})
}, },
@@ -461,7 +461,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
(stanza.content as BinaryNode[]).push({ (stanza.content as BinaryNode[]).push({
tag: 'device-identity', tag: 'device-identity',
attrs: { }, attrs: { },
content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account).finish() content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account!).finish()
}) })
logger.debug({ jid }, 'adding device identity') logger.debug({ jid }, 'adding device identity')
@@ -538,7 +538,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
error = result.error error = result.error
} else { } else {
try { try {
const media = decryptMediaRetryData(result.media!, mediaKey, result.key.id) const media = decryptMediaRetryData(result.media!, mediaKey, result.key.id!)
if(media.result !== proto.MediaRetryNotification.MediaRetryNotificationResultType.SUCCESS) { if(media.result !== proto.MediaRetryNotification.MediaRetryNotificationResultType.SUCCESS) {
const resultStr = proto.MediaRetryNotification.MediaRetryNotificationResultType[media.result] const resultStr = proto.MediaRetryNotification.MediaRetryNotificationResultType[media.result]
throw new Boom( throw new Boom(
@@ -612,7 +612,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
additionalAttributes.edit = '7' additionalAttributes.edit = '7'
} }
await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id!, cachedGroupMetadata: options.cachedGroupMetadata, additionalAttributes }) await relayMessage(jid, fullMsg.message!, { messageId: fullMsg.key.id!, cachedGroupMetadata: options.cachedGroupMetadata, additionalAttributes })
if(config.emitOwnEvents) { if(config.emitOwnEvents) {
process.nextTick(() => { process.nextTick(() => {
upsertMessage(fullMsg, 'append') upsertMessage(fullMsg, 'append')

View File

@@ -104,7 +104,7 @@ export const makeSocket = ({
}) })
if(sendMsg) { if(sendMsg) {
sendRawMessage(sendMsg).catch(onClose) sendRawMessage(sendMsg).catch(onClose!)
} }
return result return result
@@ -134,9 +134,9 @@ export const makeSocket = ({
) )
return result as any return result as any
} finally { } finally {
ws.off(`TAG:${msgId}`, onRecv) ws.off(`TAG:${msgId}`, onRecv!)
ws.off('close', onErr) // if the socket closes, you'll never receive the message ws.off('close', onErr!) // if the socket closes, you'll never receive the message
ws.off('error', onErr) ws.off('error', onErr!)
} }
} }
@@ -215,7 +215,7 @@ export const makeSocket = ({
] ]
}) })
const countChild = getBinaryNodeChild(result, 'count') const countChild = getBinaryNodeChild(result, 'count')
return +countChild.attrs.value return +countChild!.attrs.value
} }
/** generates and uploads a set of pre-keys to the server */ /** generates and uploads a set of pre-keys to the server */
@@ -525,7 +525,7 @@ export const makeSocket = ({
logger.info({ name }, 'updated pushName') logger.info({ name }, 'updated pushName')
sendNode({ sendNode({
tag: 'presence', tag: 'presence',
attrs: { name } attrs: { name: name! }
}) })
.catch(err => { .catch(err => {
logger.warn({ trace: err.stack }, 'error in sending presence update on name change') logger.warn({ trace: err.stack }, 'error in sending presence update on name change')

View File

@@ -18,7 +18,7 @@ export const waChatKey = (pin: boolean) => ({
compare: (k1: string, k2: string) => k2.localeCompare (k1) compare: (k1: string, k2: string) => k2.localeCompare (k1)
}) })
export const waMessageID = (m: WAMessage) => m.key.id export const waMessageID = (m: WAMessage) => m.key.id || ''
export type BaileysInMemoryStoreConfig = { export type BaileysInMemoryStoreConfig = {
chatKey?: Comparable<Chat, string> chatKey?: Comparable<Chat, string>
@@ -28,9 +28,9 @@ export type BaileysInMemoryStoreConfig = {
const makeMessagesDictionary = () => makeOrderedDictionary(waMessageID) const makeMessagesDictionary = () => makeOrderedDictionary(waMessageID)
export default ( export default (
{ logger, chatKey }: BaileysInMemoryStoreConfig { logger: _logger, chatKey }: BaileysInMemoryStoreConfig
) => { ) => {
logger = logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' }) const logger = _logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' })
chatKey = chatKey || waChatKey(true) chatKey = chatKey || waChatKey(true)
const KeyedDB = require('@adiwajshing/keyed-db').default as new (...args: any[]) => KeyedDB<Chat, string> const KeyedDB = require('@adiwajshing/keyed-db').default as new (...args: any[]) => KeyedDB<Chat, string>
@@ -118,9 +118,9 @@ export default (
ev.on('chats.update', updates => { ev.on('chats.update', updates => {
for(let update of updates) { for(let update of updates) {
const result = chats.update(update.id!, chat => { const result = chats.update(update.id!, chat => {
if(update.unreadCount > 0) { if(update.unreadCount! > 0) {
update = { ...update } update = { ...update }
update.unreadCount = chat.unreadCount + update.unreadCount update.unreadCount = (chat.unreadCount || 0) + update.unreadCount!
} }
Object.assign(chat, update) Object.assign(chat, update)
@@ -166,8 +166,8 @@ export default (
}) })
ev.on('messages.update', updates => { ev.on('messages.update', updates => {
for(const { update, key } of updates) { for(const { update, key } of updates) {
const list = assertMessageList(key.remoteJid) const list = assertMessageList(key.remoteJid!)
const result = list.updateAssign(key.id, update) const result = list.updateAssign(key.id!, update)
if(!result) { if(!result) {
logger.debug({ update }, 'got update for non-existent message') logger.debug({ update }, 'got update for non-existent message')
} }
@@ -178,7 +178,7 @@ export default (
const list = messages[item.jid] const list = messages[item.jid]
list?.clear() list?.clear()
} else { } else {
const jid = item.keys[0].remoteJid const jid = item.keys[0].remoteJid!
const list = messages[jid] const list = messages[jid]
if(list) { if(list) {
const idSet = new Set(item.keys.map(k => k.id)) const idSet = new Set(item.keys.map(k => k.id))
@@ -189,8 +189,9 @@ export default (
ev.on('groups.update', updates => { ev.on('groups.update', updates => {
for(const update of updates) { for(const update of updates) {
if(groupMetadata[update.id]) { const id = update.id!
Object.assign(groupMetadata[update.id!], update) if(groupMetadata[id]) {
Object.assign(groupMetadata[id], update)
} else { } else {
logger.debug({ update }, 'got update for non-existant group metadata') logger.debug({ update }, 'got update for non-existant group metadata')
} }
@@ -223,7 +224,7 @@ export default (
ev.on('message-receipt.update', updates => { ev.on('message-receipt.update', updates => {
for(const { key, receipt } of updates) { for(const { key, receipt } of updates) {
const obj = messages[key.remoteJid!] const obj = messages[key.remoteJid!]
const msg = obj?.get(key.id) const msg = obj?.get(key.id!)
if(msg) { if(msg) {
updateMessageWithReceipt(msg, receipt) updateMessageWithReceipt(msg, receipt)
} }
@@ -267,12 +268,12 @@ export default (
const mode = !cursor || 'before' in cursor ? 'before' : 'after' const mode = !cursor || 'before' in cursor ? 'before' : 'after'
const cursorKey = !!cursor ? ('before' in cursor ? cursor.before : cursor.after) : undefined const cursorKey = !!cursor ? ('before' in cursor ? cursor.before : cursor.after) : undefined
const cursorValue = cursorKey ? list.get(cursorKey.id) : undefined const cursorValue = cursorKey ? list.get(cursorKey.id!) : undefined
let messages: WAMessage[] let messages: WAMessage[]
if(list && mode === 'before' && (!cursorKey || cursorValue)) { if(list && mode === 'before' && (!cursorKey || cursorValue)) {
if(cursorValue) { if(cursorValue) {
const msgIdx = list.array.findIndex(m => m.key.id === cursorKey.id) const msgIdx = list.array.findIndex(m => m.key.id === cursorKey?.id)
messages = list.array.slice(0, msgIdx) messages = list.array.slice(0, msgIdx)
} else { } else {
messages = list.array messages = list.array
@@ -307,10 +308,10 @@ export default (
return message return message
}, },
mostRecentMessage: async(jid: string, sock: LegacyWASocket | undefined) => { mostRecentMessage: async(jid: string, sock: LegacyWASocket | undefined) => {
let message = messages[jid]?.array.slice(-1)[0] let message: WAMessage | undefined = messages[jid]?.array.slice(-1)[0]
if(!message) { if(!message) {
const [result] = await sock?.fetchMessagesFromWA(jid, 1, undefined) const items = await sock?.fetchMessagesFromWA(jid, 1, undefined)
message = result message = items?.[0]
} }
return message return message
@@ -329,24 +330,30 @@ export default (
}, },
fetchGroupMetadata: async(jid: string, sock: AnyWASocket | undefined) => { fetchGroupMetadata: async(jid: string, sock: AnyWASocket | undefined) => {
if(!groupMetadata[jid]) { if(!groupMetadata[jid]) {
groupMetadata[jid] = await sock?.groupMetadata(jid) const metadata = await sock?.groupMetadata(jid)
if(metadata) {
groupMetadata[jid] = metadata
}
} }
return groupMetadata[jid] return groupMetadata[jid]
}, },
fetchBroadcastListInfo: async(jid: string, sock: LegacyWASocket | undefined) => { fetchBroadcastListInfo: async(jid: string, sock: LegacyWASocket | undefined) => {
if(!groupMetadata[jid]) { if(!groupMetadata[jid]) {
groupMetadata[jid] = await sock?.getBroadcastListInfo(jid) const metadata = await sock?.getBroadcastListInfo(jid)
if(metadata) {
groupMetadata[jid] = metadata
}
} }
return groupMetadata[jid] return groupMetadata[jid]
}, },
fetchMessageReceipts: async({ remoteJid, id }: WAMessageKey, sock: LegacyWASocket | undefined) => { fetchMessageReceipts: async({ remoteJid, id }: WAMessageKey, sock: LegacyWASocket | undefined) => {
const list = messages[remoteJid] const list = messages[remoteJid!]
const msg = list?.get(id) const msg = list?.get(id!)
let receipts = msg.userReceipt let receipts = msg?.userReceipt
if(!receipts) { if(!receipts) {
receipts = await sock?.messageInfo(remoteJid, id) receipts = await sock?.messageInfo(remoteJid!, id!)
if(msg) { if(msg) {
msg.userReceipt = receipts msg.userReceipt = receipts
} }

View File

@@ -2,7 +2,7 @@ function makeOrderedDictionary<T>(idGetter: (item: T) => string) {
const array: T[] = [] const array: T[] = []
const dict: { [_: string]: T } = { } const dict: { [_: string]: T } = { }
const get = (id: string) => dict[id] const get = (id: string): T | undefined => dict[id]
const update = (item: T) => { const update = (item: T) => {
const id = idGetter(item) const id = idGetter(item)

View File

@@ -12,7 +12,7 @@ export interface GroupMetadata {
subjectOwner?: string subjectOwner?: string
/** group subject modification date */ /** group subject modification date */
subjectTime?: number subjectTime?: number
creation: number creation?: number
desc?: string desc?: string
descOwner?: string descOwner?: string
descId?: string descId?: string

View File

@@ -71,7 +71,8 @@ export type SocketQueryOptions = SocketSendMessageOptions & {
requiresPhoneConnection?: boolean requiresPhoneConnection?: boolean
} }
export type LegacySocketConfig = CommonSocketConfig<LegacyAuthenticationCreds> & { export type LegacySocketConfig = CommonSocketConfig & {
auth?: LegacyAuthenticationCreds
/** max time for the phone to respond to a connectivity test */ /** max time for the phone to respond to a connectivity test */
phoneResponseTimeMs: number phoneResponseTimeMs: number
/** max time for WA server to respond before error with 422 */ /** max time for WA server to respond before error with 422 */

View File

@@ -22,7 +22,7 @@ export type WAMediaUpload = Buffer | { url: URL | string } | { stream: Readable
/** Set of message types that are supported by the library */ /** Set of message types that are supported by the library */
export type MessageType = keyof proto.Message export type MessageType = keyof proto.Message
export type DownloadableMessage = { mediaKey?: Uint8Array, directPath?: string, url?: string } export type DownloadableMessage = { mediaKey?: Uint8Array | null, directPath?: string | null, url?: string | null }
export type MessageReceiptType = 'read' | 'read-self' | 'hist_sync' | 'peer_msg' | 'sender' | 'inactive' | undefined export type MessageReceiptType = 'read' | 'read-self' | 'hist_sync' | 'peer_msg' | 'sender' | 'inactive' | undefined
@@ -37,7 +37,7 @@ export interface WAUrlInfo {
'canonical-url': string 'canonical-url': string
'matched-text': string 'matched-text': string
title: string title: string
description: string description?: string
jpegThumbnail?: Buffer jpegThumbnail?: Buffer
} }
@@ -182,7 +182,7 @@ export type MediaGenerationOptions = {
mediaUploadTimeoutMs?: number mediaUploadTimeoutMs?: number
} }
export type MessageContentGenerationOptions = MediaGenerationOptions & { export type MessageContentGenerationOptions = MediaGenerationOptions & {
getUrlInfo?: (text: string) => Promise<WAUrlInfo> getUrlInfo?: (text: string) => Promise<WAUrlInfo | undefined>
} }
export type MessageGenerationOptions = MessageContentGenerationOptions & MessageGenerationOptionsFromContent export type MessageGenerationOptions = MessageContentGenerationOptions & MessageGenerationOptionsFromContent

View File

@@ -8,9 +8,7 @@ import { MediaConnInfo } from './Message'
export type WAVersion = [number, number, number] export type WAVersion = [number, number, number]
export type WABrowserDescription = [string, string, string] export type WABrowserDescription = [string, string, string]
export type CommonSocketConfig<T> = { export type CommonSocketConfig = {
/** provide an auth state object to maintain the auth state */
auth?: T
/** the WS url to connect to WA */ /** the WS url to connect to WA */
waWebSocketUrl: string | URL waWebSocketUrl: string | URL
/** Fails the connection if the socket times out in this interval */ /** Fails the connection if the socket times out in this interval */

View File

@@ -7,7 +7,7 @@ export type ConnectionState = {
connection: WAConnectionState connection: WAConnectionState
/** the error that caused the connection to close */ /** the error that caused the connection to close */
lastDisconnect?: { lastDisconnect?: {
error: Error error: Error | undefined
date: Date date: Date
} }
/** is this a new login */ /** is this a new login */

View File

@@ -17,7 +17,9 @@ import { CommonSocketConfig } from './Socket'
export type MessageRetryMap = { [msgId: string]: number } export type MessageRetryMap = { [msgId: string]: number }
export type SocketConfig = CommonSocketConfig<AuthenticationState> & { export type SocketConfig = CommonSocketConfig & {
/** provide an auth state object to maintain the auth state */
auth: AuthenticationState
/** By default true, should history messages be downloaded and processed */ /** By default true, should history messages be downloaded and processed */
downloadHistory: boolean downloadHistory: boolean
/** transaction capability options for SignalKeyStore */ /** transaction capability options for SignalKeyStore */
@@ -67,18 +69,16 @@ export type WABusinessHoursConfig = {
export type WABusinessProfile = { export type WABusinessProfile = {
description: string description: string
email: string email: string | undefined
business_hours: { business_hours: {
timezone?: string timezone?: string
config?: WABusinessHoursConfig[] config?: WABusinessHoursConfig[]
business_config?: WABusinessHoursConfig[] business_config?: WABusinessHoursConfig[]
} }
website: string[] website: string[]
categories: { category?: string
id: string
localized_display_name: string
}[]
wid?: string wid?: string
address?: string
} }

View File

@@ -36,8 +36,7 @@ export const addTransactionCapability = (state: SignalKeyStore, logger: Logger,
dbQueriesInTransaction += 1 dbQueriesInTransaction += 1
const result = await state.get(type, idsRequiringFetch) const result = await state.get(type, idsRequiringFetch)
transactionCache[type] = transactionCache[type] || { } transactionCache[type] = Object.assign(transactionCache[type] || { }, result)
Object.assign(transactionCache[type], result)
} }
} }

View File

@@ -14,8 +14,8 @@ export const parseCollectionsNode = (node: BinaryNode) => {
const collectionsNode = getBinaryNodeChild(node, 'collections') const collectionsNode = getBinaryNodeChild(node, 'collections')
const collections = getBinaryNodeChildren(collectionsNode, 'collection').map<CatalogCollection>( const collections = getBinaryNodeChildren(collectionsNode, 'collection').map<CatalogCollection>(
collectionNode => { collectionNode => {
const id = getBinaryNodeChildString(collectionNode, 'id') const id = getBinaryNodeChildString(collectionNode, 'id')!
const name = getBinaryNodeChildString(collectionNode, 'name') const name = getBinaryNodeChildString(collectionNode, 'name')!
const products = getBinaryNodeChildren(collectionNode, 'product').map(parseProductNode) const products = getBinaryNodeChildren(collectionNode, 'product').map(parseProductNode)
return { return {
@@ -36,14 +36,14 @@ export const parseOrderDetailsNode = (node: BinaryNode) => {
const orderNode = getBinaryNodeChild(node, 'order') const orderNode = getBinaryNodeChild(node, 'order')
const products = getBinaryNodeChildren(orderNode, 'product').map<OrderProduct>( const products = getBinaryNodeChildren(orderNode, 'product').map<OrderProduct>(
productNode => { productNode => {
const imageNode = getBinaryNodeChild(productNode, 'image') const imageNode = getBinaryNodeChild(productNode, 'image')!
return { return {
id: getBinaryNodeChildString(productNode, 'id'), id: getBinaryNodeChildString(productNode, 'id')!,
name: getBinaryNodeChildString(productNode, 'name'), name: getBinaryNodeChildString(productNode, 'name')!,
imageUrl: getBinaryNodeChildString(imageNode, 'url'), imageUrl: getBinaryNodeChildString(imageNode, 'url')!,
price: +getBinaryNodeChildString(productNode, 'price'), price: +getBinaryNodeChildString(productNode, 'price')!,
currency: getBinaryNodeChildString(productNode, 'currency'), currency: getBinaryNodeChildString(productNode, 'currency')!,
quantity: +getBinaryNodeChildString(productNode, 'quantity') quantity: +getBinaryNodeChildString(productNode, 'quantity')!
} }
} }
) )
@@ -52,8 +52,8 @@ export const parseOrderDetailsNode = (node: BinaryNode) => {
const orderDetails: OrderDetails = { const orderDetails: OrderDetails = {
price: { price: {
total: +getBinaryNodeChildString(priceNode, 'total'), total: +getBinaryNodeChildString(priceNode, 'total')!,
currency: getBinaryNodeChildString(priceNode, 'currency'), currency: getBinaryNodeChildString(priceNode, 'currency')!,
}, },
products products
} }
@@ -172,24 +172,24 @@ export const toProductNode = (productId: string | undefined, product: ProductCre
export const parseProductNode = (productNode: BinaryNode) => { export const parseProductNode = (productNode: BinaryNode) => {
const isHidden = productNode.attrs.is_hidden === 'true' const isHidden = productNode.attrs.is_hidden === 'true'
const id = getBinaryNodeChildString(productNode, 'id') const id = getBinaryNodeChildString(productNode, 'id')!
const mediaNode = getBinaryNodeChild(productNode, 'media') const mediaNode = getBinaryNodeChild(productNode, 'media')!
const statusInfoNode = getBinaryNodeChild(productNode, 'status_info') const statusInfoNode = getBinaryNodeChild(productNode, 'status_info')!
const product: Product = { const product: Product = {
id, id,
imageUrls: parseImageUrls(mediaNode), imageUrls: parseImageUrls(mediaNode),
reviewStatus: { reviewStatus: {
whatsapp: getBinaryNodeChildString(statusInfoNode, 'status'), whatsapp: getBinaryNodeChildString(statusInfoNode, 'status')!,
}, },
availability: 'in stock', availability: 'in stock',
name: getBinaryNodeChildString(productNode, 'name'), name: getBinaryNodeChildString(productNode, 'name')!,
retailerId: getBinaryNodeChildString(productNode, 'retailer_id'), retailerId: getBinaryNodeChildString(productNode, 'retailer_id'),
url: getBinaryNodeChildString(productNode, 'url'), url: getBinaryNodeChildString(productNode, 'url'),
description: getBinaryNodeChildString(productNode, 'description'), description: getBinaryNodeChildString(productNode, 'description')!,
price: +getBinaryNodeChildString(productNode, 'price'), price: +getBinaryNodeChildString(productNode, 'price')!,
currency: getBinaryNodeChildString(productNode, 'currency'), currency: getBinaryNodeChildString(productNode, 'currency')!,
isHidden, isHidden,
} }
@@ -246,15 +246,15 @@ export const uploadingNecessaryImages = async(images: WAMediaUpload[], waUploadT
const parseImageUrls = (mediaNode: BinaryNode) => { const parseImageUrls = (mediaNode: BinaryNode) => {
const imgNode = getBinaryNodeChild(mediaNode, 'image') const imgNode = getBinaryNodeChild(mediaNode, 'image')
return { return {
requested: getBinaryNodeChildString(imgNode, 'request_image_url'), requested: getBinaryNodeChildString(imgNode, 'request_image_url')!,
original: getBinaryNodeChildString(imgNode, 'original_image_url') original: getBinaryNodeChildString(imgNode, 'original_image_url')!
} }
} }
const parseStatusInfo = (mediaNode: BinaryNode): CatalogStatus => { const parseStatusInfo = (mediaNode: BinaryNode): CatalogStatus => {
const node = getBinaryNodeChild(mediaNode, 'status_info') const node = getBinaryNodeChild(mediaNode, 'status_info')
return { return {
status: getBinaryNodeChildString(node, 'status'), status: getBinaryNodeChildString(node, 'status')!,
canAppeal: getBinaryNodeChildString(node, 'can_appeal') === 'true', canAppeal: getBinaryNodeChildString(node, 'can_appeal') === 'true',
} }
} }

View File

@@ -220,7 +220,7 @@ export const decodeSyncdMutations = async(
const encContent = content.slice(0, -32) const encContent = content.slice(0, -32)
const ogValueMac = content.slice(-32) const ogValueMac = content.slice(-32)
if(validateMacs) { if(validateMacs) {
const contentHmac = generateMac(operation, encContent, record.keyId!.id!, key.valueMacKey) const contentHmac = generateMac(operation!, encContent, record.keyId!.id!, key.valueMacKey)
if(Buffer.compare(contentHmac, ogValueMac) !== 0) { if(Buffer.compare(contentHmac, ogValueMac) !== 0) {
throw new Boom('HMAC content verification failed') throw new Boom('HMAC content verification failed')
} }
@@ -231,7 +231,7 @@ export const decodeSyncdMutations = async(
if(validateMacs) { if(validateMacs) {
const hmac = hmacSign(syncAction.index, key.indexKey) const hmac = hmacSign(syncAction.index, key.indexKey)
if(Buffer.compare(hmac, record.index!.blob) !== 0) { if(Buffer.compare(hmac, record.index!.blob!) !== 0) {
throw new Boom('HMAC index verification failed') throw new Boom('HMAC index verification failed')
} }
} }
@@ -242,7 +242,7 @@ export const decodeSyncdMutations = async(
ltGenerator.mix({ ltGenerator.mix({
indexMac: record.index!.blob!, indexMac: record.index!.blob!,
valueMac: ogValueMac, valueMac: ogValueMac,
operation: operation operation: operation!
}) })
} }
@@ -258,13 +258,13 @@ export const decodeSyncdPatch = async(
validateMacs: boolean validateMacs: boolean
) => { ) => {
if(validateMacs) { if(validateMacs) {
const base64Key = Buffer.from(msg.keyId!.id).toString('base64') const base64Key = Buffer.from(msg.keyId!.id!).toString('base64')
const mainKeyObj = await getAppStateSyncKey(base64Key) const mainKeyObj = await getAppStateSyncKey(base64Key)
const mainKey = mutationKeys(mainKeyObj.keyData!) const mainKey = mutationKeys(mainKeyObj.keyData!)
const mutationmacs = msg.mutations!.map(mutation => mutation.record!.value!.blob!.slice(-32)) const mutationmacs = msg.mutations!.map(mutation => mutation.record!.value!.blob!.slice(-32))
const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, toNumber(msg.version!.version), name, mainKey.patchMacKey) const patchMac = generatePatchMac(msg.snapshotMac!, mutationmacs, toNumber(msg.version!.version!), name, mainKey.patchMacKey)
if(Buffer.compare(patchMac, msg.patchMac) !== 0) { if(Buffer.compare(patchMac, msg.patchMac!) !== 0) {
throw new Boom('Invalid patch mac') throw new Boom('Invalid patch mac')
} }
} }
@@ -418,10 +418,10 @@ export const decodePatches = async(
logger?.trace({ name, version }, 'downloading external patch') logger?.trace({ name, version }, 'downloading external patch')
const ref = await downloadExternalPatch(syncd.externalMutations) const ref = await downloadExternalPatch(syncd.externalMutations)
logger?.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch') logger?.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch')
syncd.mutations.push(...ref.mutations) syncd.mutations?.push(...ref.mutations)
} }
const patchVersion = toNumber(version.version!) const patchVersion = toNumber(version!.version!)
newState.version = patchVersion newState.version = patchVersion
const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber
@@ -439,7 +439,7 @@ export const decodePatches = async(
const result = mutationKeys(keyEnc.keyData!) const result = mutationKeys(keyEnc.keyData!)
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey) const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
if(Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) { if(Buffer.compare(snapshotMac!, computedSnapshotMac) !== 0) {
throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`) throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`)
} }
} }
@@ -482,7 +482,6 @@ export const chatModificationToAppPatch = (
} }
if(m.key.participant) { if(m.key.participant) {
m.key = { ...m.key }
m.key.participant = jidNormalizedUser(m.key.participant) m.key.participant = jidNormalizedUser(m.key.participant)
} }
@@ -627,7 +626,7 @@ export const processSyncAction = (
if( if(
isValidPatchBasedOnMessageRange(id, archiveAction.messageRange) isValidPatchBasedOnMessageRange(id, archiveAction.messageRange)
|| !isInitialSync || !isInitialSync
|| !accountSettings.unarchiveChats || !accountSettings?.unarchiveChats
) { ) {
// basically we don't need to fire an "archive" update if the chat is being marked unarchvied // basically we don't need to fire an "archive" update if the chat is being marked unarchvied
// this only applies for the initial sync // this only applies for the initial sync
@@ -661,19 +660,21 @@ export const processSyncAction = (
} }
] }) ] })
} else if(action?.contactAction) { } else if(action?.contactAction) {
ev.emit('contacts.upsert', [{ id, name: action.contactAction!.fullName }]) ev.emit('contacts.upsert', [{ id, name: action.contactAction!.fullName! }])
} else if(action?.pushNameSetting) { } else if(action?.pushNameSetting) {
if(me?.name !== action?.pushNameSetting) { if(me?.name !== action?.pushNameSetting) {
ev.emit('creds.update', { me: { ...me, name: action?.pushNameSetting?.name! } }) ev.emit('creds.update', { me: { ...me, name: action?.pushNameSetting?.name! } })
} }
} else if(action?.pinAction) { } else if(action?.pinAction) {
ev.emit('chats.update', [{ id, pin: action.pinAction?.pinned ? toNumber(action.timestamp) : null }]) ev.emit('chats.update', [{ id, pin: action.pinAction?.pinned ? toNumber(action.timestamp!) : null }])
} else if(action?.unarchiveChatsSetting) { } else if(action?.unarchiveChatsSetting) {
const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats
ev.emit('creds.update', { accountSettings: { unarchiveChats } }) ev.emit('creds.update', { accountSettings: { unarchiveChats } })
logger.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`) logger?.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`)
accountSettings.unarchiveChats = unarchiveChats if(accountSettings) {
accountSettings.unarchiveChats = unarchiveChats
}
} else if(action?.starAction) { } else if(action?.starAction) {
ev.emit('messages.update', [ ev.emit('messages.update', [
{ {
@@ -689,12 +690,12 @@ export const processSyncAction = (
ev.emit('chats.delete', [id]) ev.emit('chats.delete', [id])
} }
} else { } else {
logger.warn({ syncAction, id }, 'unprocessable update') logger?.warn({ syncAction, id }, 'unprocessable update')
} }
function isValidPatchBasedOnMessageRange(id: string, msgRange: proto.ISyncActionMessageRange) { function isValidPatchBasedOnMessageRange(id: string, msgRange: proto.ISyncActionMessageRange | null | undefined) {
const chat = recvChats?.[id] const chat = recvChats?.[id]
const lastMsgTimestamp = msgRange.lastMessageTimestamp || msgRange.lastSystemMessageTimestamp || 0 const lastMsgTimestamp = msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0
const chatLastMsgTimestamp = chat?.lastMsgRecvTimestamp || 0 const chatLastMsgTimestamp = chat?.lastMsgRecvTimestamp || 0
return lastMsgTimestamp >= chatLastMsgTimestamp return lastMsgTimestamp >= chatLastMsgTimestamp
} }

View File

@@ -56,6 +56,8 @@ export const decodeMessageStanza = (stanza: BinaryNode, auth: AuthenticationStat
chatId = from chatId = from
author = participant author = participant
} else {
throw new Boom('Unknown message type', { data: stanza })
} }
const sender = msgType === 'chat' ? author : chatId const sender = msgType === 'chat' ? author : chatId
@@ -117,6 +119,8 @@ export const decodeMessageStanza = (stanza: BinaryNode, auth: AuthenticationStat
const user = isJidUser(sender) ? sender : author const user = isJidUser(sender) ? sender : author
msgBuffer = await decryptSignalProto(user, e2eType, content as Buffer, auth) msgBuffer = await decryptSignalProto(user, e2eType, content as Buffer, auth)
break break
default:
throw new Error(`Unknown e2e type: ${e2eType}`)
} }
let msg: proto.IMessage = proto.Message.decode(unpadRandomMax16(msgBuffer)) let msg: proto.IMessage = proto.Message.decode(unpadRandomMax16(msgBuffer))

View File

@@ -96,6 +96,8 @@ export const makeEventBuffer = (logger: Logger): BaileysBufferableEventEmitter =
isBuffering = true isBuffering = true
return true return true
} }
return false
}, },
async flush() { async flush() {
if(!isBuffering) { if(!isBuffering) {
@@ -209,12 +211,13 @@ function append<E extends BufferableEvent>(
case 'contacts.update': case 'contacts.update':
const contactUpdates = eventData as BaileysEventMap<any>['contacts.update'] const contactUpdates = eventData as BaileysEventMap<any>['contacts.update']
for(const update of contactUpdates) { for(const update of contactUpdates) {
const id = update.id!
const upsert = data.contactUpserts[update.id!] const upsert = data.contactUpserts[update.id!]
if(upsert) { if(upsert) {
Object.assign(upsert, update) Object.assign(upsert, update)
} else { } else {
const contactUpdate = data.contactUpdates[update.id] || { } const contactUpdate = data.contactUpdates[id] || { }
data.contactUpdates[update.id] = Object.assign(contactUpdate, update) data.contactUpdates[id] = Object.assign(contactUpdate, update)
} }
} }
@@ -312,8 +315,9 @@ function append<E extends BufferableEvent>(
case 'groups.update': case 'groups.update':
const groupUpdates = eventData as BaileysEventMap<any>['groups.update'] const groupUpdates = eventData as BaileysEventMap<any>['groups.update']
for(const update of groupUpdates) { for(const update of groupUpdates) {
const groupUpdate = data.groupUpdates[update.id] || { } const id = update.id!
data.groupUpdates[update.id] = Object.assign(groupUpdate, update) const groupUpdate = data.groupUpdates[id] || { }
data.groupUpdates[id] = Object.assign(groupUpdate, update)
} }
break break
@@ -324,12 +328,12 @@ function append<E extends BufferableEvent>(
function decrementChatReadCounterIfMsgDidUnread(message: WAMessage) { function decrementChatReadCounterIfMsgDidUnread(message: WAMessage) {
// decrement chat unread counter // decrement chat unread counter
// if the message has already been marked read by us // if the message has already been marked read by us
const chatId = message.key.remoteJid const chatId = message.key.remoteJid!
const chat = data.chatUpdates[chatId] || data.chatUpserts[chatId] const chat = data.chatUpdates[chatId] || data.chatUpserts[chatId]
if( if(
isRealMessage(message) isRealMessage(message)
&& shouldIncrementChatUnread(message) && shouldIncrementChatUnread(message)
&& typeof chat?.unreadCount !== 'undefined' && typeof chat?.unreadCount === 'number'
&& chat.unreadCount > 0 && chat.unreadCount > 0
) { ) {
logger.debug({ chatId: chat.id }, 'decrementing chat counter') logger.debug({ chatId: chat.id }, 'decrementing chat counter')
@@ -413,16 +417,16 @@ function consolidateEvents(data: BufferedEventData) {
function concatChats<C extends Partial<Chat>>(a: C, b: C) { function concatChats<C extends Partial<Chat>>(a: C, b: C) {
if(b.unreadCount === null) { if(b.unreadCount === null) {
// neutralize unread counter // neutralize unread counter
if(a.unreadCount < 0) { if(a.unreadCount! < 0) {
a.unreadCount = undefined a.unreadCount = undefined
b.unreadCount = undefined b.unreadCount = undefined
} }
} }
if(typeof a.unreadCount !== 'undefined' && typeof b.unreadCount !== 'undefined') { if(typeof a.unreadCount === 'number' && typeof b.unreadCount === 'number') {
b = { ...b } b = { ...b }
if(b.unreadCount >= 0) { if(b.unreadCount! >= 0) {
b.unreadCount = Math.max(b.unreadCount, 0) + Math.max(a.unreadCount, 0) b.unreadCount = Math.max(b.unreadCount!, 0) + Math.max(a.unreadCount, 0)
} }
} }

View File

@@ -86,40 +86,21 @@ export const encodeBigEndian = (e: number, t = 4) => {
return a return a
} }
export const toNumber = (t: Long | number): number => ((typeof t === 'object' && t) ? ('toNumber' in t ? t.toNumber() : (t as any).low) : t) export const toNumber = (t: Long | number | null | undefined): number => ((typeof t === 'object' && t) ? ('toNumber' in t ? t.toNumber() : (t as any).low) : t)
export function shallowChanges <T>(old: T, current: T, { lookForDeletedKeys }: {lookForDeletedKeys: boolean}): Partial<T> {
const changes: Partial<T> = {}
for(const key in current) {
if(old[key] !== current[key]) {
changes[key] = current[key] || null
}
}
if(lookForDeletedKeys) {
for(const key in old) {
if(!changes[key] && old[key] !== current[key]) {
changes[key] = current[key] || null
}
}
}
return changes
}
/** unix timestamp of a date in seconds */ /** unix timestamp of a date in seconds */
export const unixTimestampSeconds = (date: Date = new Date()) => Math.floor(date.getTime() / 1000) export const unixTimestampSeconds = (date: Date = new Date()) => Math.floor(date.getTime() / 1000)
export type DebouncedTimeout = ReturnType<typeof debouncedTimeout> export type DebouncedTimeout = ReturnType<typeof debouncedTimeout>
export const debouncedTimeout = (intervalMs: number = 1000, task: () => void = undefined) => { export const debouncedTimeout = (intervalMs: number = 1000, task?: () => void) => {
let timeout: NodeJS.Timeout let timeout: NodeJS.Timeout | undefined
return { return {
start: (newIntervalMs?: number, newTask?: () => void) => { start: (newIntervalMs?: number, newTask?: () => void) => {
task = newTask || task task = newTask || task
intervalMs = newIntervalMs || intervalMs intervalMs = newIntervalMs || intervalMs
timeout && clearTimeout(timeout) timeout && clearTimeout(timeout)
timeout = setTimeout(task, intervalMs) timeout = setTimeout(() => task?.(), intervalMs)
}, },
cancel: () => { cancel: () => {
timeout && clearTimeout(timeout) timeout && clearTimeout(timeout)
@@ -155,7 +136,7 @@ export const delayCancellable = (ms: number) => {
return { delay, cancel } return { delay, cancel }
} }
export async function promiseTimeout<T>(ms: number, promise: (resolve: (v?: T)=>void, reject: (error) => void) => void) { export async function promiseTimeout<T>(ms: number | undefined, promise: (resolve: (v?: T)=>void, reject: (error) => void) => void) {
if(!ms) { if(!ms) {
return new Promise (promise) return new Promise (promise)
} }
@@ -185,7 +166,7 @@ export async function promiseTimeout<T>(ms: number, promise: (resolve: (v?: T)=>
export const generateMessageID = () => 'BAE5' + randomBytes(6).toString('hex').toUpperCase() export const generateMessageID = () => 'BAE5' + randomBytes(6).toString('hex').toUpperCase()
export function bindWaitForEvent<T extends keyof BaileysEventMap<any>>(ev: CommonBaileysEventEmitter<any>, event: T) { export function bindWaitForEvent<T extends keyof BaileysEventMap<any>>(ev: CommonBaileysEventEmitter<any>, event: T) {
return async(check: (u: BaileysEventMap<any>[T]) => boolean, timeoutMs?: number) => { return async(check: (u: BaileysEventMap<any>[T]) => boolean | undefined, timeoutMs?: number) => {
let listener: (item: BaileysEventMap<any>[T]) => void let listener: (item: BaileysEventMap<any>[T]) => void
let closeListener: any let closeListener: any
await ( await (
@@ -291,7 +272,7 @@ const STATUS_MAP: { [_: string]: proto.WebMessageInfo.WebMessageInfoStatus } = {
* @param type type from receipt * @param type type from receipt
*/ */
export const getStatusFromReceiptType = (type: string | undefined) => { export const getStatusFromReceiptType = (type: string | undefined) => {
const status = STATUS_MAP[type] const status = STATUS_MAP[type!]
if(typeof type === 'undefined') { if(typeof type === 'undefined') {
return proto.WebMessageInfo.WebMessageInfoStatus.DELIVERY_ACK return proto.WebMessageInfo.WebMessageInfoStatus.DELIVERY_ACK
} }

View File

@@ -35,7 +35,7 @@ export const processHistoryMessage = (
switch (item.syncType) { switch (item.syncType) {
case proto.HistorySync.HistorySyncHistorySyncType.INITIAL_BOOTSTRAP: case proto.HistorySync.HistorySyncHistorySyncType.INITIAL_BOOTSTRAP:
case proto.HistorySync.HistorySyncHistorySyncType.RECENT: case proto.HistorySync.HistorySyncHistorySyncType.RECENT:
for(const chat of item.conversations) { for(const chat of item.conversations!) {
const contactId = `c:${chat.id}` const contactId = `c:${chat.id}`
if(chat.name && !historyCache.has(contactId)) { if(chat.name && !historyCache.has(contactId)) {
contacts.push({ id: chat.id, name: chat.name }) contacts.push({ id: chat.id, name: chat.name })
@@ -43,15 +43,16 @@ export const processHistoryMessage = (
} }
const msgs = chat.messages || [] const msgs = chat.messages || []
for(const { message } of msgs) { for(const item of msgs) {
const message = item.message!
const uqId = `${message.key.remoteJid}:${message.key.id}` const uqId = `${message.key.remoteJid}:${message.key.id}`
if(!historyCache.has(uqId)) { if(!historyCache.has(uqId)) {
messages.push(message) messages.push(message)
const curItem = recvChats[message.key.remoteJid] const curItem = recvChats[message.key.remoteJid!]
const timestamp = toNumber(message.messageTimestamp) const timestamp = toNumber(message.messageTimestamp)
if(!message.key.fromMe && (!curItem || timestamp > curItem.lastMsgRecvTimestamp)) { if(!message.key.fromMe && (!curItem || timestamp > curItem.lastMsgRecvTimestamp)) {
recvChats[message.key.remoteJid] = { lastMsgRecvTimestamp: timestamp } recvChats[message.key.remoteJid!] = { lastMsgRecvTimestamp: timestamp }
// keep only the most recent message in the chat array // keep only the most recent message in the chat array
chat.messages = [{ message }] chat.messages = [{ message }]
} }
@@ -72,10 +73,10 @@ export const processHistoryMessage = (
break break
case proto.HistorySync.HistorySyncHistorySyncType.PUSH_NAME: case proto.HistorySync.HistorySyncHistorySyncType.PUSH_NAME:
for(const c of item.pushnames) { for(const c of item.pushnames!) {
const contactId = `c:${c.id}` const contactId = `c:${c.id}`
if(!historyCache.has(contactId)) { if(!historyCache.has(contactId)) {
contacts.push({ notify: c.pushname, id: c.id }) contacts.push({ notify: c.pushname!, id: c.id! })
historyCache.add(contactId) historyCache.add(contactId)
} }
} }

View File

@@ -29,7 +29,7 @@ export const decodeWAMessage = (
// If a query was done, the server will respond with the same message tag we sent the query with // If a query was done, the server will respond with the same message tag we sent the query with
const messageTag: string = message.slice(0, commaIndex).toString() const messageTag: string = message.slice(0, commaIndex).toString()
let json: any let json: any
let tags: WATag let tags: WATag | undefined
if(data.length) { if(data.length) {
const possiblyEnc = (data.length > 32 && data.length % 16 === 0) const possiblyEnc = (data.length > 32 && data.length % 16 === 0)
if(typeof data === 'string' || !possiblyEnc) { if(typeof data === 'string' || !possiblyEnc) {

View File

@@ -62,7 +62,11 @@ export const hkdfInfoKey = (type: MediaType) => {
} }
/** generates all the keys required to encrypt/decrypt & sign a media message */ /** generates all the keys required to encrypt/decrypt & sign a media message */
export function getMediaKeys(buffer: Uint8Array | string, mediaType: MediaType): MediaDecryptionKeyInfo { export function getMediaKeys(buffer: Uint8Array | string | null | undefined, mediaType: MediaType): MediaDecryptionKeyInfo {
if(!buffer) {
throw new Boom('Cannot derive from empty media key')
}
if(typeof buffer === 'string') { if(typeof buffer === 'string') {
buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64') buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64')
} }
@@ -163,13 +167,13 @@ export async function getAudioDuration(buffer: Buffer | string | Readable) {
const musicMetadata = await import('music-metadata') const musicMetadata = await import('music-metadata')
let metadata: IAudioMetadata let metadata: IAudioMetadata
if(Buffer.isBuffer(buffer)) { if(Buffer.isBuffer(buffer)) {
metadata = await musicMetadata.parseBuffer(buffer, null, { duration: true }) metadata = await musicMetadata.parseBuffer(buffer, undefined, { duration: true })
} else if(typeof buffer === 'string') { } else if(typeof buffer === 'string') {
const rStream = createReadStream(buffer) const rStream = createReadStream(buffer)
metadata = await musicMetadata.parseStream(rStream, null, { duration: true }) metadata = await musicMetadata.parseStream(rStream, undefined, { duration: true })
rStream.close() rStream.close()
} else { } else {
metadata = await musicMetadata.parseStream(buffer, null, { duration: true }) metadata = await musicMetadata.parseStream(buffer, undefined, { duration: true })
} }
return metadata.format.duration return metadata.format.duration
@@ -216,7 +220,7 @@ export async function generateThumbnail(
logger?: Logger logger?: Logger
} }
) { ) {
let thumbnail: string let thumbnail: string | undefined
if(mediaType === 'image') { if(mediaType === 'image') {
const buff = await extractImageThumb(file) const buff = await extractImageThumb(file)
thumbnail = buff.toString('base64') thumbnail = buff.toString('base64')
@@ -259,8 +263,8 @@ export const encryptedStream = async(
// const encWriteStream = createWriteStream(encBodyPath) // const encWriteStream = createWriteStream(encBodyPath)
const encWriteStream = new Readable({ read: () => {} }) const encWriteStream = new Readable({ read: () => {} })
let bodyPath: string let bodyPath: string | undefined
let writeStream: WriteStream let writeStream: WriteStream | undefined
let didSaveToTmpPath = false let didSaveToTmpPath = false
if(type === 'file') { if(type === 'file') {
bodyPath = (media as any).url bodyPath = (media as any).url
@@ -272,7 +276,7 @@ export const encryptedStream = async(
let fileLength = 0 let fileLength = 0
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv) const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
let hmac = Crypto.createHmac('sha256', macKey).update(iv) let hmac = Crypto.createHmac('sha256', macKey!).update(iv)
let sha256Plain = Crypto.createHash('sha256') let sha256Plain = Crypto.createHash('sha256')
let sha256Enc = Crypto.createHash('sha256') let sha256Enc = Crypto.createHash('sha256')
@@ -323,7 +327,7 @@ export const encryptedStream = async(
} }
} catch(error) { } catch(error) {
encWriteStream.destroy(error) encWriteStream.destroy(error)
writeStream.destroy(error) writeStream?.destroy(error)
aes.destroy(error) aes.destroy(error)
hmac.destroy(error) hmac.destroy(error)
sha256Plain.destroy(error) sha256Plain.destroy(error)
@@ -353,7 +357,7 @@ export const downloadContentFromMessage = (
type: MediaType, type: MediaType,
opts: MediaDownloadOptions = { } opts: MediaDownloadOptions = { }
) => { ) => {
const downloadUrl = url || getUrlFromDirectPath(directPath) const downloadUrl = url || getUrlFromDirectPath(directPath!)
const keys = getMediaKeys(mediaKey, type) const keys = getMediaKeys(mediaKey, type)
return downloadEncryptedContent(downloadUrl, keys, opts) return downloadEncryptedContent(downloadUrl, keys, opts)
@@ -410,8 +414,8 @@ export const downloadEncryptedContent = async(
const pushBytes = (bytes: Buffer, push: (bytes: Buffer) => void) => { const pushBytes = (bytes: Buffer, push: (bytes: Buffer) => void) => {
if(startByte || endByte) { if(startByte || endByte) {
const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0) const start = bytesFetched >= startByte! ? undefined : Math.max(startByte! - bytesFetched, 0)
const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0) const end = bytesFetched + bytes.length < endByte! ? undefined : Math.max(endByte! - bytesFetched, 0)
push(bytes.slice(start, end)) push(bytes.slice(start, end))
@@ -486,13 +490,13 @@ export function extensionForMediaMessage(message: WAMessageContent) {
return extension return extension
} }
export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: CommonSocketConfig<any>, refreshMediaConn: (force: boolean) => Promise<MediaConnInfo>): WAMediaUploadFunction => { export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: CommonSocketConfig, refreshMediaConn: (force: boolean) => Promise<MediaConnInfo>): WAMediaUploadFunction => {
return async(stream, { mediaType, fileEncSha256B64, timeoutMs }) => { return async(stream, { mediaType, fileEncSha256B64, timeoutMs }) => {
const { default: axios } = await import('axios') const { default: axios } = await import('axios')
// send a query JSON to obtain the url & auth token to upload our media // send a query JSON to obtain the url & auth token to upload our media
let uploadInfo = await refreshMediaConn(false) let uploadInfo = await refreshMediaConn(false)
let urls: { mediaUrl: string, directPath: string } let urls: { mediaUrl: string, directPath: string } | undefined
const hosts = [ ...customUploadHosts, ...uploadInfo.hosts ] const hosts = [ ...customUploadHosts, ...uploadInfo.hosts ]
const chunks: Buffer[] = [] const chunks: Buffer[] = []
@@ -500,7 +504,7 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: C
chunks.push(chunk) chunks.push(chunk)
} }
let reqBody = Buffer.concat(chunks) const reqBody = Buffer.concat(chunks)
for(const { hostname, maxContentLengthBytes } of hosts) { for(const { hostname, maxContentLengthBytes } of hosts) {
logger.debug(`uploading to "${hostname}"`) logger.debug(`uploading to "${hostname}"`)
@@ -550,9 +554,6 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: C
} }
} }
// clear buffer just to be sure we're releasing the memory
reqBody = undefined
if(!urls) { if(!urls) {
throw new Boom( throw new Boom(
'Media upload failed on all hosts', 'Media upload failed on all hosts',
@@ -583,12 +584,12 @@ export const encryptMediaRetryRequest = (
const iv = Crypto.randomBytes(12) const iv = Crypto.randomBytes(12)
const retryKey = getMediaRetryKey(mediaKey) const retryKey = getMediaRetryKey(mediaKey)
const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id)) const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id!))
const req: BinaryNode = { const req: BinaryNode = {
tag: 'receipt', tag: 'receipt',
attrs: { attrs: {
id: key.id, id: key.id!,
to: jidNormalizedUser(meId), to: jidNormalizedUser(meId),
type: 'server-error' type: 'server-error'
}, },
@@ -607,8 +608,9 @@ export const encryptMediaRetryRequest = (
{ {
tag: 'rmr', tag: 'rmr',
attrs: { attrs: {
jid: key.remoteJid, jid: key.remoteJid!,
from_me: (!!key.fromMe).toString(), from_me: (!!key.fromMe).toString(),
// @ts-ignore
participant: key.participant || undefined participant: key.participant || undefined
} }
} }
@@ -619,7 +621,7 @@ export const encryptMediaRetryRequest = (
} }
export const decodeMediaRetryNode = (node: BinaryNode) => { export const decodeMediaRetryNode = (node: BinaryNode) => {
const rmrNode = getBinaryNodeChild(node, 'rmr') const rmrNode = getBinaryNodeChild(node, 'rmr')!
const event: BaileysEventMap<any>['messages.media-update'][number] = { const event: BaileysEventMap<any>['messages.media-update'][number] = {
key: { key: {

View File

@@ -75,13 +75,17 @@ export const prepareWAMessageMedia = async(
) => { ) => {
const logger = options.logger const logger = options.logger
let mediaType: typeof MEDIA_KEYS[number] let mediaType: typeof MEDIA_KEYS[number] | undefined
for(const key of MEDIA_KEYS) { for(const key of MEDIA_KEYS) {
if(key in message) { if(key in message) {
mediaType = key mediaType = key
} }
} }
if(!mediaType) {
throw new Boom('Invalid media type', { statusCode: 400 })
}
const uploadData: MediaUploadData = { const uploadData: MediaUploadData = {
...message, ...message,
media: message[mediaType] media: message[mediaType]
@@ -106,15 +110,14 @@ export const prepareWAMessageMedia = async(
// check for cache hit // check for cache hit
if(cacheableKey) { if(cacheableKey) {
const mediaBuff: Buffer = options.mediaCache!.get(cacheableKey) const mediaBuff = options.mediaCache!.get<Buffer>(cacheableKey)
if(mediaBuff) { if(mediaBuff) {
logger?.debug({ cacheableKey }, 'got media cache hit') logger?.debug({ cacheableKey }, 'got media cache hit')
const obj = WAProto.Message.decode(mediaBuff) const obj = WAProto.Message.decode(mediaBuff)
const key = `${mediaType}Message` const key = `${mediaType}Message`
delete uploadData.media Object.assign(obj[key], { ...uploadData, media: undefined })
Object.assign(obj[key], { ...uploadData })
return obj return obj
} }
@@ -153,12 +156,12 @@ export const prepareWAMessageMedia = async(
(async() => { (async() => {
try { try {
if(requiresThumbnailComputation) { if(requiresThumbnailComputation) {
uploadData.jpegThumbnail = await generateThumbnail(bodyPath, mediaType as any, options) uploadData.jpegThumbnail = await generateThumbnail(bodyPath!, mediaType as any, options)
logger?.debug('generated thumbnail') logger?.debug('generated thumbnail')
} }
if(requiresDurationComputation) { if(requiresDurationComputation) {
uploadData.seconds = await getAudioDuration(bodyPath) uploadData.seconds = await getAudioDuration(bodyPath!)
logger?.debug('computed audio duration') logger?.debug('computed audio duration')
} }
} catch(error) { } catch(error) {
@@ -177,8 +180,6 @@ export const prepareWAMessageMedia = async(
} }
) )
delete uploadData.media
const obj = WAProto.Message.fromObject({ const obj = WAProto.Message.fromObject({
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject( [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject(
{ {
@@ -189,13 +190,14 @@ export const prepareWAMessageMedia = async(
fileSha256, fileSha256,
fileLength, fileLength,
mediaKeyTimestamp: unixTimestampSeconds(), mediaKeyTimestamp: unixTimestampSeconds(),
...uploadData ...uploadData,
media: undefined
} }
) )
}) })
if(cacheableKey) { if(cacheableKey) {
logger.debug({ cacheableKey }, 'set cache') logger?.debug({ cacheableKey }, 'set cache')
options.mediaCache!.set(cacheableKey, WAProto.Message.encode(obj).finish()) options.mediaCache!.set(cacheableKey, WAProto.Message.encode(obj).finish())
} }
@@ -232,8 +234,8 @@ export const generateForwardMessageContent = (
} }
// hacky copy // hacky copy
content = normalizeMessageContent(message.message) content = normalizeMessageContent(content)
content = proto.Message.decode(proto.Message.encode(content).finish()) content = proto.Message.decode(proto.Message.encode(content!).finish())
let key = Object.keys(content)[0] as MessageType let key = Object.keys(content)[0] as MessageType
@@ -428,8 +430,8 @@ export const generateWAMessageFromContent = (
if(quoted) { if(quoted) {
const participant = quoted.key.fromMe ? userJid : (quoted.participant || quoted.key.participant || quoted.key.remoteJid) const participant = quoted.key.fromMe ? userJid : (quoted.participant || quoted.key.participant || quoted.key.remoteJid)
let quotedMsg = normalizeMessageContent(quoted.message) let quotedMsg = normalizeMessageContent(quoted.message)!
const msgType = getContentType(quotedMsg) const msgType = getContentType(quotedMsg)!
// strip any redundant properties // strip any redundant properties
quotedMsg = proto.Message.fromObject({ [msgType]: quotedMsg[msgType] }) quotedMsg = proto.Message.fromObject({ [msgType]: quotedMsg[msgType] })
@@ -439,7 +441,7 @@ export const generateWAMessageFromContent = (
} }
const contextInfo: proto.IContextInfo = message[key].contextInfo || { } const contextInfo: proto.IContextInfo = message[key].contextInfo || { }
contextInfo.participant = jidNormalizedUser(participant) contextInfo.participant = jidNormalizedUser(participant!)
contextInfo.stanzaId = quoted.key.id contextInfo.stanzaId = quoted.key.id
contextInfo.quotedMessage = quotedMsg contextInfo.quotedMessage = quotedMsg
@@ -521,7 +523,7 @@ export const getContentType = (content: WAProto.IMessage | undefined) => {
* @param content * @param content
* @returns * @returns
*/ */
export const normalizeMessageContent = (content: WAMessageContent | undefined): WAMessageContent => { export const normalizeMessageContent = (content: WAMessageContent | null | undefined): WAMessageContent | undefined => {
content = content?.ephemeralMessage?.message?.viewOnceMessage?.message || content = content?.ephemeralMessage?.message?.viewOnceMessage?.message ||
content?.ephemeralMessage?.message || content?.ephemeralMessage?.message ||
content?.viewOnceMessage?.message || content?.viewOnceMessage?.message ||
@@ -614,13 +616,13 @@ export const aggregateMessageKeysNotFromMe = (keys: proto.IMessageKey[]) => {
const uqKey = `${remoteJid}:${participant || ''}` const uqKey = `${remoteJid}:${participant || ''}`
if(!keyMap[uqKey]) { if(!keyMap[uqKey]) {
keyMap[uqKey] = { keyMap[uqKey] = {
jid: remoteJid, jid: remoteJid!,
participant, participant: participant!,
messageIds: [] messageIds: []
} }
} }
keyMap[uqKey].messageIds.push(id) keyMap[uqKey].messageIds.push(id!)
} }
} }
@@ -650,7 +652,7 @@ export const downloadMediaMessage = async(
if(ctx) { if(ctx) {
if(axios.isAxiosError(error)) { if(axios.isAxiosError(error)) {
// check if the message requires a reupload // check if the message requires a reupload
if(REUPLOAD_REQUIRED_STATUS.includes(error.response?.status)) { if(REUPLOAD_REQUIRED_STATUS.includes(error.response?.status!)) {
ctx.logger.info({ key: message.key }, 'sending reupload media request...') ctx.logger.info({ key: message.key }, 'sending reupload media request...')
// request reupload // request reupload
message = await ctx.reuploadRequest(message) message = await ctx.reuploadRequest(message)
@@ -670,9 +672,9 @@ export const downloadMediaMessage = async(
} }
const contentType = getContentType(mContent) const contentType = getContentType(mContent)
const mediaType = contentType.replace('Message', '') as MediaType const mediaType = contentType?.replace('Message', '') as MediaType
const media = mContent[contentType] const media = mContent[contentType!]
if(typeof media !== 'object' || !('url' in media)) { if(!media || typeof media !== 'object' || !('url' in media)) {
throw new Boom(`"${contentType}" message is not a media message`) throw new Boom(`"${contentType}" message is not a media message`)
} }
@@ -691,7 +693,7 @@ export const downloadMediaMessage = async(
} }
/** Checks whether the given message is a media message; if it is returns the inner content */ /** Checks whether the given message is a media message; if it is returns the inner content */
export const assertMediaContent = (content: proto.IMessage) => { export const assertMediaContent = (content: proto.IMessage | null | undefined) => {
content = extractMessageContent(content) content = extractMessageContent(content)
const mediaContent = content?.documentMessage const mediaContent = content?.documentMessage
|| content?.imageMessage || content?.imageMessage

View File

@@ -97,7 +97,7 @@ export const makeNoiseHandler = (
finishInit, finishInit,
processHandshake: ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => { processHandshake: ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => {
authenticate(serverHello!.ephemeral!) authenticate(serverHello!.ephemeral!)
mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral!)) mixIntoKey(Curve.sharedKey(privateKey, serverHello!.ephemeral!))
const decStaticContent = decrypt(serverHello!.static!) const decStaticContent = decrypt(serverHello!.static!)
mixIntoKey(Curve.sharedKey(privateKey, decStaticContent)) mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))

View File

@@ -42,7 +42,7 @@ export const isRealMessage = (message: proto.IWebMessageInfo) => {
const normalizedContent = normalizeMessageContent(message.message) const normalizedContent = normalizeMessageContent(message.message)
return ( return (
!!normalizedContent !!normalizedContent
|| MSG_MISSED_CALL_TYPES.has(message.messageStubType) || MSG_MISSED_CALL_TYPES.has(message.messageStubType!)
) )
&& !normalizedContent?.protocolMessage && !normalizedContent?.protocolMessage
&& !normalizedContent?.reactionMessage && !normalizedContent?.reactionMessage
@@ -59,7 +59,7 @@ const processMessage = async(
const meId = creds.me!.id const meId = creds.me!.id
const { accountSettings } = creds const { accountSettings } = creds
const chat: Partial<Chat> = { id: jidNormalizedUser(message.key.remoteJid) } const chat: Partial<Chat> = { id: jidNormalizedUser(message.key.remoteJid!) }
if(isRealMessage(message)) { if(isRealMessage(message)) {
chat.conversationTimestamp = toNumber(message.messageTimestamp) chat.conversationTimestamp = toNumber(message.messageTimestamp)
@@ -79,7 +79,7 @@ const processMessage = async(
if(protocolMsg) { if(protocolMsg) {
switch (protocolMsg.type) { switch (protocolMsg.type) {
case proto.ProtocolMessage.ProtocolMessageType.HISTORY_SYNC_NOTIFICATION: case proto.ProtocolMessage.ProtocolMessageType.HISTORY_SYNC_NOTIFICATION:
const histNotification = protocolMsg!.historySyncNotification const histNotification = protocolMsg!.historySyncNotification!
logger?.info({ histNotification, id: message.key.id }, 'got history notification') logger?.info({ histNotification, id: message.key.id }, 'got history notification')
@@ -117,10 +117,10 @@ const processMessage = async(
await keyStore.transaction( await keyStore.transaction(
async() => { async() => {
for(const { keyData, keyId } of keys) { for(const { keyData, keyId } of keys) {
const strKeyId = Buffer.from(keyId.keyId!).toString('base64') const strKeyId = Buffer.from(keyId!.keyId!).toString('base64')
logger?.info({ strKeyId }, 'injecting new app state sync key') logger?.info({ strKeyId }, 'injecting new app state sync key')
await keyStore.set({ 'app-state-sync-key': { [strKeyId]: keyData } }) await keyStore.set({ 'app-state-sync-key': { [strKeyId]: keyData! } })
newAppStateSyncKeyId = strKeyId newAppStateSyncKeyId = strKeyId
} }
@@ -176,7 +176,7 @@ const processMessage = async(
switch (message.messageStubType) { switch (message.messageStubType) {
case WAMessageStubType.GROUP_PARTICIPANT_LEAVE: case WAMessageStubType.GROUP_PARTICIPANT_LEAVE:
case WAMessageStubType.GROUP_PARTICIPANT_REMOVE: case WAMessageStubType.GROUP_PARTICIPANT_REMOVE:
participants = message.messageStubParameters participants = message.messageStubParameters || []
emitParticipantsUpdate('remove') emitParticipantsUpdate('remove')
// mark the chat read only if you left the group // mark the chat read only if you left the group
if(participantsIncludesMe()) { if(participantsIncludesMe()) {
@@ -187,7 +187,7 @@ const processMessage = async(
case WAMessageStubType.GROUP_PARTICIPANT_ADD: case WAMessageStubType.GROUP_PARTICIPANT_ADD:
case WAMessageStubType.GROUP_PARTICIPANT_INVITE: case WAMessageStubType.GROUP_PARTICIPANT_INVITE:
case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN: case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
participants = message.messageStubParameters participants = message.messageStubParameters || []
if(participantsIncludesMe()) { if(participantsIncludesMe()) {
chat.readOnly = false chat.readOnly = false
} }
@@ -195,23 +195,23 @@ const processMessage = async(
emitParticipantsUpdate('add') emitParticipantsUpdate('add')
break break
case WAMessageStubType.GROUP_PARTICIPANT_DEMOTE: case WAMessageStubType.GROUP_PARTICIPANT_DEMOTE:
participants = message.messageStubParameters participants = message.messageStubParameters || []
emitParticipantsUpdate('demote') emitParticipantsUpdate('demote')
break break
case WAMessageStubType.GROUP_PARTICIPANT_PROMOTE: case WAMessageStubType.GROUP_PARTICIPANT_PROMOTE:
participants = message.messageStubParameters participants = message.messageStubParameters || []
emitParticipantsUpdate('promote') emitParticipantsUpdate('promote')
break break
case WAMessageStubType.GROUP_CHANGE_ANNOUNCE: case WAMessageStubType.GROUP_CHANGE_ANNOUNCE:
const announceValue = message.messageStubParameters[0] const announceValue = message.messageStubParameters?.[0]
emitGroupUpdate({ announce: announceValue === 'true' || announceValue === 'on' }) emitGroupUpdate({ announce: announceValue === 'true' || announceValue === 'on' })
break break
case WAMessageStubType.GROUP_CHANGE_RESTRICT: case WAMessageStubType.GROUP_CHANGE_RESTRICT:
const restrictValue = message.messageStubParameters[0] const restrictValue = message.messageStubParameters?.[0]
emitGroupUpdate({ restrict: restrictValue === 'true' || restrictValue === 'on' }) emitGroupUpdate({ restrict: restrictValue === 'true' || restrictValue === 'on' })
break break
case WAMessageStubType.GROUP_CHANGE_SUBJECT: case WAMessageStubType.GROUP_CHANGE_SUBJECT:
const name = message.messageStubParameters[0] const name = message.messageStubParameters?.[0]
chat.name = name chat.name = name
emitGroupUpdate({ subject: name }) emitGroupUpdate({ subject: name })
break break

View File

@@ -142,7 +142,7 @@ export const processSenderKeyMessage = async(
auth: SignalAuthState auth: SignalAuthState
) => { ) => {
const builder = new GroupSessionBuilder(signalStorage(auth)) const builder = new GroupSessionBuilder(signalStorage(auth))
const senderName = jidToSignalSenderKeyName(item.groupId, authorJid) const senderName = jidToSignalSenderKeyName(item.groupId!, authorJid)
const senderMsg = new SenderKeyDistributionMessage(null, null, null, null, item.axolotlSenderKeyDistributionMessage) const senderMsg = new SenderKeyDistributionMessage(null, null, null, null, item.axolotlSenderKeyDistributionMessage)
const { [senderName]: senderKey } = await auth.keys.get('sender-key', [senderName]) const { [senderName]: senderKey } = await auth.keys.get('sender-key', [senderName])
@@ -203,9 +203,7 @@ export const parseAndInjectE2ESessions = async(node: BinaryNode, auth: SignalAut
const extractKey = (key: BinaryNode) => ( const extractKey = (key: BinaryNode) => (
key ? ({ key ? ({
keyId: getBinaryNodeChildUInt(key, 'id', 3), keyId: getBinaryNodeChildUInt(key, 'id', 3),
publicKey: generateSignalPubKey( publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')!),
getBinaryNodeChildBuffer(key, 'value')
),
signature: getBinaryNodeChildBuffer(key, 'signature'), signature: getBinaryNodeChildBuffer(key, 'signature'),
}) : undefined }) : undefined
) )
@@ -217,9 +215,9 @@ export const parseAndInjectE2ESessions = async(node: BinaryNode, auth: SignalAut
await Promise.all( await Promise.all(
nodes.map( nodes.map(
async node => { async node => {
const signedKey = getBinaryNodeChild(node, 'skey') const signedKey = getBinaryNodeChild(node, 'skey')!
const key = getBinaryNodeChild(node, 'key') const key = getBinaryNodeChild(node, 'key')!
const identity = getBinaryNodeChildBuffer(node, 'identity') const identity = getBinaryNodeChildBuffer(node, 'identity')!
const jid = node.attrs.jid const jid = node.attrs.jid
const registrationId = getBinaryNodeChildUInt(node, 'registration', 4) const registrationId = getBinaryNodeChildUInt(node, 'registration', 4)
@@ -237,13 +235,13 @@ export const parseAndInjectE2ESessions = async(node: BinaryNode, auth: SignalAut
} }
export const extractDeviceJids = (result: BinaryNode, myJid: string, excludeZeroDevices: boolean) => { export const extractDeviceJids = (result: BinaryNode, myJid: string, excludeZeroDevices: boolean) => {
const { user: myUser, device: myDevice } = jidDecode(myJid) const { user: myUser, device: myDevice } = jidDecode(myJid)!
const extracted: JidWithDevice[] = [] const extracted: JidWithDevice[] = []
for(const node of result.content as BinaryNode[]) { for(const node of result.content as BinaryNode[]) {
const list = getBinaryNodeChild(node, 'list')?.content const list = getBinaryNodeChild(node, 'list')?.content
if(list && Array.isArray(list)) { if(list && Array.isArray(list)) {
for(const item of list) { for(const item of list) {
const { user } = jidDecode(item.attrs.jid) const { user } = jidDecode(item.attrs.jid)!
const devicesNode = getBinaryNodeChild(item, 'devices') const devicesNode = getBinaryNodeChild(item, 'devices')
const deviceListNode = getBinaryNodeChild(devicesNode, 'device-list') const deviceListNode = getBinaryNodeChild(devicesNode, 'device-list')
if(Array.isArray(deviceListNode?.content)) { if(Array.isArray(deviceListNode?.content)) {

View File

@@ -15,12 +15,12 @@ import { BufferJSON } from './generics'
export const useMultiFileAuthState = async(folder: string): Promise<{ state: AuthenticationState, saveCreds: () => Promise<void> }> => { export const useMultiFileAuthState = async(folder: string): Promise<{ state: AuthenticationState, saveCreds: () => Promise<void> }> => {
const writeData = (data: any, file: string) => { const writeData = (data: any, file: string) => {
return writeFile(join(folder, fixFileName(file)), JSON.stringify(data, BufferJSON.replacer)) return writeFile(join(folder, fixFileName(file)!), JSON.stringify(data, BufferJSON.replacer))
} }
const readData = async(file: string) => { const readData = async(file: string) => {
try { try {
const data = await readFile(join(folder, fixFileName(file)), { encoding: 'utf-8' }) const data = await readFile(join(folder, fixFileName(file)!), { encoding: 'utf-8' })
return JSON.parse(data, BufferJSON.reviver) return JSON.parse(data, BufferJSON.reviver)
} catch(error) { } catch(error) {
return null return null
@@ -29,7 +29,7 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut
const removeData = async(file: string) => { const removeData = async(file: string) => {
try { try {
await unlink(fixFileName(file)) await unlink(fixFileName(file)!)
} catch{ } catch{
} }

View File

@@ -45,7 +45,7 @@ const getClientPayload = (config: ClientPayloadConfig): proto.IClientPayload =>
} }
export const generateLoginNode = (userJid: string, config: ClientPayloadConfig): proto.IClientPayload => { export const generateLoginNode = (userJid: string, config: ClientPayloadConfig): proto.IClientPayload => {
const { user, device } = jidDecode(userJid) const { user, device } = jidDecode(userJid)!
const payload: proto.IClientPayload = { const payload: proto.IClientPayload = {
...getClientPayload(config), ...getClientPayload(config),
passive: true, passive: true,
@@ -137,7 +137,7 @@ export const configureSuccessfulPairing = (
const deviceMsg = Buffer.concat([ Buffer.from([6, 1]), deviceDetails, signedIdentityKey.public, accountSignatureKey ]) const deviceMsg = Buffer.concat([ Buffer.from([6, 1]), deviceDetails, signedIdentityKey.public, accountSignatureKey ])
account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg) account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg)
// do not provide the "accountSignatureKey" back // do not provide the "accountSignatureKey" back
account.accountSignatureKey = null account.accountSignatureKey = Buffer.alloc(0)
const identity = createSignalIdentity(jid, accountSignatureKey) const identity = createSignalIdentity(jid, accountSignatureKey)
const accountEnc = proto.ADVSignedDeviceIdentity.encode(account).finish() const accountEnc = proto.ADVSignedDeviceIdentity.encode(account).finish()

View File

@@ -201,5 +201,5 @@ export const SINGLE_BYTE_TOKENS = [
export const TOKEN_MAP: { [token: string]: { dict?: number, index: number } } = { } export const TOKEN_MAP: { [token: string]: { dict?: number, index: number } } = { }
for(let i = 0;i < SINGLE_BYTE_TOKENS.length;i++) { for(let i = 0;i < SINGLE_BYTE_TOKENS.length;i++) {
TOKEN_MAP[SINGLE_BYTE_TOKENS[i]] = { index: i } TOKEN_MAP[SINGLE_BYTE_TOKENS[i]!] = { index: i }
} }

File diff suppressed because one or more lines are too long

View File

@@ -153,7 +153,7 @@ export const decodeDecompressedBinaryNode = (
const readString = (tag: number): string => { const readString = (tag: number): string => {
if(tag >= 1 && tag < SINGLE_BYTE_TOKENS.length) { if(tag >= 1 && tag < SINGLE_BYTE_TOKENS.length) {
return SINGLE_BYTE_TOKENS[tag] return SINGLE_BYTE_TOKENS[tag] || ''
} }
switch (tag) { switch (tag) {
@@ -163,7 +163,7 @@ export const decodeDecompressedBinaryNode = (
case TAGS.DICTIONARY_3: case TAGS.DICTIONARY_3:
return getTokenDouble(tag - TAGS.DICTIONARY_0, readByte()) return getTokenDouble(tag - TAGS.DICTIONARY_0, readByte())
case TAGS.LIST_EMPTY: case TAGS.LIST_EMPTY:
return null return ''
case TAGS.BINARY_8: case TAGS.BINARY_8:
return readStringFromChars(readByte()) return readStringFromChars(readByte())
case TAGS.BINARY_20: case TAGS.BINARY_20:

View File

@@ -1,6 +1,6 @@
import * as constants from './constants' import * as constants from './constants'
import { jidDecode } from './jid-utils' import { FullJid, jidDecode } from './jid-utils'
import type { BinaryNode, BinaryNodeCodingOptions } from './types' import type { BinaryNode, BinaryNodeCodingOptions } from './types'
export const encodeBinaryNode = ( export const encodeBinaryNode = (
@@ -52,7 +52,7 @@ export const encodeBinaryNode = (
pushBytes(bytes) pushBytes(bytes)
} }
const writeJid = ({ agent, device, user, server }: ReturnType<typeof jidDecode>) => { const writeJid = ({ agent, device, user, server }: FullJid) => {
if(typeof agent !== 'undefined' || typeof device !== 'undefined') { if(typeof agent !== 'undefined' || typeof device !== 'undefined') {
pushByte(TAGS.AD_JID) pushByte(TAGS.AD_JID)
pushByte(agent || 0) pushByte(agent || 0)

View File

@@ -4,9 +4,9 @@ import { BinaryNode } from './types'
// some extra useful utilities // some extra useful utilities
export const getBinaryNodeChildren = ({ content }: BinaryNode, childTag: string) => { export const getBinaryNodeChildren = (node: BinaryNode | undefined, childTag: string) => {
if(Array.isArray(content)) { if(Array.isArray(node?.content)) {
return content.filter(item => item.tag === childTag) return node!.content.filter(item => item.tag === childTag)
} }
return [] return []
@@ -20,20 +20,20 @@ export const getAllBinaryNodeChildren = ({ content }: BinaryNode) => {
return [] return []
} }
export const getBinaryNodeChild = ({ content }: BinaryNode, childTag: string) => { export const getBinaryNodeChild = (node: BinaryNode | undefined, childTag: string) => {
if(Array.isArray(content)) { if(Array.isArray(node?.content)) {
return content.find(item => item.tag === childTag) return node?.content.find(item => item.tag === childTag)
} }
} }
export const getBinaryNodeChildBuffer = (node: BinaryNode, childTag: string) => { export const getBinaryNodeChildBuffer = (node: BinaryNode | undefined, childTag: string) => {
const child = getBinaryNodeChild(node, childTag)?.content const child = getBinaryNodeChild(node, childTag)?.content
if(Buffer.isBuffer(child) || child instanceof Uint8Array) { if(Buffer.isBuffer(child) || child instanceof Uint8Array) {
return child return child
} }
} }
export const getBinaryNodeChildString = (node: BinaryNode, childTag: string) => { export const getBinaryNodeChildString = (node: BinaryNode | undefined, childTag: string) => {
const child = getBinaryNodeChild(node, childTag)?.content const child = getBinaryNodeChild(node, childTag)?.content
if(Buffer.isBuffer(child) || child instanceof Uint8Array) { if(Buffer.isBuffer(child) || child instanceof Uint8Array) {
return Buffer.from(child).toString('utf-8') return Buffer.from(child).toString('utf-8')

View File

@@ -11,18 +11,23 @@ export type JidWithDevice = {
device?: number device?: number
} }
export type FullJid = JidWithDevice & {
server: JidServer | string
agent?: number
}
export const jidEncode = (user: string | number | null, server: JidServer, device?: number, agent?: number) => { export const jidEncode = (user: string | number | null, server: JidServer, device?: number, agent?: number) => {
return `${user || ''}${!!agent ? `_${agent}` : ''}${!!device ? `:${device}` : ''}@${server}` return `${user || ''}${!!agent ? `_${agent}` : ''}${!!device ? `:${device}` : ''}@${server}`
} }
export const jidDecode = (jid: string) => { export const jidDecode = (jid: string | undefined): FullJid | undefined => {
const sepIdx = typeof jid === 'string' ? jid.indexOf('@') : -1 const sepIdx = typeof jid === 'string' ? jid.indexOf('@') : -1
if(sepIdx < 0) { if(sepIdx < 0) {
return undefined return undefined
} }
const server = jid.slice(sepIdx + 1) const server = jid!.slice(sepIdx + 1)
const userCombined = jid.slice(0, sepIdx) const userCombined = jid!.slice(0, sepIdx)
const [userAgent, device] = userCombined.split(':') const [userAgent, device] = userCombined.split(':')
const [user, agent] = userAgent.split('_') const [user, agent] = userAgent.split('_')
@@ -36,7 +41,7 @@ export const jidDecode = (jid: string) => {
} }
/** is the jid a user */ /** is the jid a user */
export const areJidsSameUser = (jid1: string, jid2: string) => ( export const areJidsSameUser = (jid1: string | undefined, jid2: string | undefined) => (
jidDecode(jid1)?.user === jidDecode(jid2)?.user jidDecode(jid1)?.user === jidDecode(jid2)?.user
) )
/** is the jid a user */ /** is the jid a user */
@@ -49,6 +54,6 @@ export const isJidGroup = (jid: string) => (jid?.endsWith('@g.us'))
export const isJidStatusBroadcast = (jid: string) => jid === 'status@broadcast' export const isJidStatusBroadcast = (jid: string) => jid === 'status@broadcast'
export const jidNormalizedUser = (jid: string) => { export const jidNormalizedUser = (jid: string) => {
const { user, server } = jidDecode(jid) const { user, server } = jidDecode(jid)!
return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : server as JidServer) return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : server as JidServer)
} }

View File

@@ -7,6 +7,7 @@
"checkJs": false, "checkJs": false,
"outDir": "lib", "outDir": "lib",
"strict": false, "strict": false,
"strictNullChecks": true,
"skipLibCheck": true, "skipLibCheck": true,
"noImplicitThis": true, "noImplicitThis": true,
"esModuleInterop": true, "esModuleInterop": true,