mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
chore: format everything
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
lib
|
lib
|
||||||
coverage
|
coverage
|
||||||
*.lock
|
*.lock
|
||||||
|
*.json
|
||||||
src/WABinary/index.ts
|
src/WABinary/index.ts
|
||||||
WAProto
|
WAProto
|
||||||
WASignalGroup
|
WASignalGroup
|
||||||
|
|||||||
@@ -30,8 +30,9 @@
|
|||||||
"changelog:update": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
"changelog:update": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
||||||
"example": "node --inspect -r ts-node/register Example/example.ts",
|
"example": "node --inspect -r ts-node/register Example/example.ts",
|
||||||
"gen:protobuf": "sh WAProto/GenerateStatics.sh",
|
"gen:protobuf": "sh WAProto/GenerateStatics.sh",
|
||||||
|
"format": "prettier --write \"src/**/*.{ts,js,json,md}\"",
|
||||||
"lint": "eslint src --ext .js,.ts",
|
"lint": "eslint src --ext .js,.ts",
|
||||||
"lint:fix": "yarn lint --fix",
|
"lint:fix": "yarn format && yarn lint --fix",
|
||||||
"prepack": "tsc",
|
"prepack": "tsc",
|
||||||
"prepare": "tsc",
|
"prepare": "tsc",
|
||||||
"preinstall": "node ./engine-requirements.js",
|
"preinstall": "node ./engine-requirements.js",
|
||||||
|
|||||||
@@ -17,14 +17,12 @@ export const WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60
|
|||||||
export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0'
|
export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0'
|
||||||
export const DICT_VERSION = 2
|
export const DICT_VERSION = 2
|
||||||
export const KEY_BUNDLE_TYPE = Buffer.from([5])
|
export const KEY_BUNDLE_TYPE = Buffer.from([5])
|
||||||
export const NOISE_WA_HEADER = Buffer.from(
|
export const NOISE_WA_HEADER = Buffer.from([87, 65, 6, DICT_VERSION]) // last is "DICT_VERSION"
|
||||||
[ 87, 65, 6, DICT_VERSION ]
|
|
||||||
) // last is "DICT_VERSION"
|
|
||||||
/** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
|
/** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
|
||||||
export const URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g
|
export const URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g
|
||||||
|
|
||||||
export const WA_CERT_DETAILS = {
|
export const WA_CERT_DETAILS = {
|
||||||
SERIAL: 0,
|
SERIAL: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PROCESSABLE_HISTORY_TYPES = [
|
export const PROCESSABLE_HISTORY_TYPES = [
|
||||||
@@ -32,7 +30,7 @@ export const PROCESSABLE_HISTORY_TYPES = [
|
|||||||
proto.Message.HistorySyncNotification.HistorySyncType.PUSH_NAME,
|
proto.Message.HistorySyncNotification.HistorySyncType.PUSH_NAME,
|
||||||
proto.Message.HistorySyncNotification.HistorySyncType.RECENT,
|
proto.Message.HistorySyncNotification.HistorySyncType.RECENT,
|
||||||
proto.Message.HistorySyncNotification.HistorySyncType.FULL,
|
proto.Message.HistorySyncNotification.HistorySyncType.FULL,
|
||||||
proto.Message.HistorySyncNotification.HistorySyncType.ON_DEMAND,
|
proto.Message.HistorySyncNotification.HistorySyncType.ON_DEMAND
|
||||||
]
|
]
|
||||||
|
|
||||||
export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
|
export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
|
||||||
@@ -60,7 +58,7 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
|
|||||||
options: {},
|
options: {},
|
||||||
appStateMacVerification: {
|
appStateMacVerification: {
|
||||||
patch: false,
|
patch: false,
|
||||||
snapshot: false,
|
snapshot: false
|
||||||
},
|
},
|
||||||
countryCode: 'US',
|
countryCode: 'US',
|
||||||
getMessage: async () => undefined,
|
getMessage: async () => undefined,
|
||||||
@@ -77,19 +75,19 @@ export const MEDIA_PATH_MAP: { [T in MediaType]?: string } = {
|
|||||||
'thumbnail-link': '/mms/image',
|
'thumbnail-link': '/mms/image',
|
||||||
'product-catalog-image': '/product/image',
|
'product-catalog-image': '/product/image',
|
||||||
'md-app-state': '',
|
'md-app-state': '',
|
||||||
'md-msg-hist': '/mms/md-app-state',
|
'md-msg-hist': '/mms/md-app-state'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MEDIA_HKDF_KEY_MAPPING = {
|
export const MEDIA_HKDF_KEY_MAPPING = {
|
||||||
'audio': 'Audio',
|
audio: 'Audio',
|
||||||
'document': 'Document',
|
document: 'Document',
|
||||||
'gif': 'Video',
|
gif: 'Video',
|
||||||
'image': 'Image',
|
image: 'Image',
|
||||||
'ppic': '',
|
ppic: '',
|
||||||
'product': 'Image',
|
product: 'Image',
|
||||||
'ptt': 'Audio',
|
ptt: 'Audio',
|
||||||
'sticker': 'Image',
|
sticker: 'Image',
|
||||||
'video': 'Video',
|
video: 'Video',
|
||||||
'thumbnail-document': 'Document Thumbnail',
|
'thumbnail-document': 'Document Thumbnail',
|
||||||
'thumbnail-image': 'Image Thumbnail',
|
'thumbnail-image': 'Image Thumbnail',
|
||||||
'thumbnail-video': 'Video Thumbnail',
|
'thumbnail-video': 'Video Thumbnail',
|
||||||
@@ -98,7 +96,7 @@ export const MEDIA_HKDF_KEY_MAPPING = {
|
|||||||
'md-app-state': 'App State',
|
'md-app-state': 'App State',
|
||||||
'product-catalog-image': '',
|
'product-catalog-image': '',
|
||||||
'payment-bg-image': 'Payment Background',
|
'payment-bg-image': 'Payment Background',
|
||||||
'ptv': 'Video'
|
ptv: 'Video'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP) as MediaType[]
|
export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP) as MediaType[]
|
||||||
@@ -111,5 +109,5 @@ export const DEFAULT_CACHE_TTLS = {
|
|||||||
SIGNAL_STORE: 5 * 60, // 5 minutes
|
SIGNAL_STORE: 5 * 60, // 5 minutes
|
||||||
MSG_RETRY: 60 * 60, // 1 hour
|
MSG_RETRY: 60 * 60, // 1 hour
|
||||||
CALL_OFFER: 5 * 60, // 5 minutes
|
CALL_OFFER: 5 * 60, // 5 minutes
|
||||||
USER_DEVICES: 5 * 60, // 5 minutes
|
USER_DEVICES: 5 * 60 // 5 minutes
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import * as libsignal from 'libsignal'
|
import * as libsignal from 'libsignal'
|
||||||
import { GroupCipher, GroupSessionBuilder, SenderKeyDistributionMessage, SenderKeyName, SenderKeyRecord } from '../../WASignalGroup'
|
import {
|
||||||
|
GroupCipher,
|
||||||
|
GroupSessionBuilder,
|
||||||
|
SenderKeyDistributionMessage,
|
||||||
|
SenderKeyName,
|
||||||
|
SenderKeyRecord
|
||||||
|
} from '../../WASignalGroup'
|
||||||
import { SignalAuthState } from '../Types'
|
import { SignalAuthState } from '../Types'
|
||||||
import { SignalRepository } from '../Types/Signal'
|
import { SignalRepository } from '../Types/Signal'
|
||||||
import { generateSignalPubKey } from '../Utils'
|
import { generateSignalPubKey } from '../Utils'
|
||||||
@@ -18,7 +24,13 @@ export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository
|
|||||||
const builder = new GroupSessionBuilder(storage)
|
const builder = new GroupSessionBuilder(storage)
|
||||||
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])
|
||||||
if (!senderKey) {
|
if (!senderKey) {
|
||||||
await storage.storeSenderKey(senderName, new SenderKeyRecord())
|
await storage.storeSenderKey(senderName, new SenderKeyRecord())
|
||||||
@@ -64,7 +76,7 @@ export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
ciphertext,
|
ciphertext,
|
||||||
senderKeyDistributionMessage: senderKeyDistributionMessage.serialize(),
|
senderKeyDistributionMessage: senderKeyDistributionMessage.serialize()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async injectE2ESession({ jid, session }) {
|
async injectE2ESession({ jid, session }) {
|
||||||
@@ -73,7 +85,7 @@ export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository
|
|||||||
},
|
},
|
||||||
jidToSignalProtocolAddress(jid) {
|
jidToSignalProtocolAddress(jid) {
|
||||||
return jidToSignalProtocolAddress(jid).toString()
|
return jidToSignalProtocolAddress(jid).toString()
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +107,7 @@ function signalStorage({ creds, keys }: SignalAuthState) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
storeSession: async (id, session) => {
|
storeSession: async (id, session) => {
|
||||||
await keys.set({ 'session': { [id]: session.serialize() } })
|
await keys.set({ session: { [id]: session.serialize() } })
|
||||||
},
|
},
|
||||||
isTrustedIdentity: () => {
|
isTrustedIdentity: () => {
|
||||||
return true
|
return true
|
||||||
@@ -127,14 +139,12 @@ function signalStorage({ creds, keys }: SignalAuthState) {
|
|||||||
storeSenderKey: async (keyId, key) => {
|
storeSenderKey: async (keyId, key) => {
|
||||||
await keys.set({ 'sender-key': { [keyId]: key.serialize() } })
|
await keys.set({ 'sender-key': { [keyId]: key.serialize() } })
|
||||||
},
|
},
|
||||||
getOurRegistrationId: () => (
|
getOurRegistrationId: () => creds.registrationId,
|
||||||
creds.registrationId
|
|
||||||
),
|
|
||||||
getOurIdentity: () => {
|
getOurIdentity: () => {
|
||||||
const { signedIdentityKey } = creds
|
const { signedIdentityKey } = creds
|
||||||
return {
|
return {
|
||||||
privKey: Buffer.from(signedIdentityKey.private),
|
privKey: Buffer.from(signedIdentityKey.private),
|
||||||
pubKey: generateSignalPubKey(signedIdentityKey.public),
|
pubKey: generateSignalPubKey(signedIdentityKey.public)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,15 @@ export abstract class AbstractSocketClient extends EventEmitter {
|
|||||||
abstract get isClosing(): boolean
|
abstract get isClosing(): boolean
|
||||||
abstract get isConnecting(): boolean
|
abstract get isConnecting(): boolean
|
||||||
|
|
||||||
constructor(public url: URL, public config: SocketConfig) {
|
constructor(
|
||||||
|
public url: URL,
|
||||||
|
public config: SocketConfig
|
||||||
|
) {
|
||||||
super()
|
super()
|
||||||
this.setMaxListeners(0)
|
this.setMaxListeners(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract connect(): Promise<void>
|
abstract connect(): Promise<void>
|
||||||
abstract close(): Promise<void>
|
abstract close(): Promise<void>
|
||||||
abstract send(str: Uint8Array | string, cb?: (err?: Error) => void): boolean;
|
abstract send(str: Uint8Array | string, cb?: (err?: Error) => void): boolean
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ import { DEFAULT_ORIGIN } from '../../Defaults'
|
|||||||
import { AbstractSocketClient } from './types'
|
import { AbstractSocketClient } from './types'
|
||||||
|
|
||||||
export class WebSocketClient extends AbstractSocketClient {
|
export class WebSocketClient extends AbstractSocketClient {
|
||||||
|
|
||||||
protected socket: WebSocket | null = null
|
protected socket: WebSocket | null = null
|
||||||
|
|
||||||
get isOpen(): boolean {
|
get isOpen(): boolean {
|
||||||
@@ -29,7 +28,7 @@ export class WebSocketClient extends AbstractSocketClient {
|
|||||||
headers: this.config.options?.headers as {},
|
headers: this.config.options?.headers as {},
|
||||||
handshakeTimeout: this.config.connectTimeoutMs,
|
handshakeTimeout: this.config.connectTimeoutMs,
|
||||||
timeout: this.config.connectTimeoutMs,
|
timeout: this.config.connectTimeoutMs,
|
||||||
agent: this.config.agent,
|
agent: this.config.agent
|
||||||
})
|
})
|
||||||
|
|
||||||
this.socket.setMaxListeners(0)
|
this.socket.setMaxListeners(0)
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import { GetCatalogOptions, ProductCreate, ProductUpdate, SocketConfig } from '../Types'
|
import { GetCatalogOptions, ProductCreate, ProductUpdate, SocketConfig } from '../Types'
|
||||||
import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode, parseProductNode, toProductNode, uploadingNecessaryImagesOfProduct } from '../Utils/business'
|
import {
|
||||||
|
parseCatalogNode,
|
||||||
|
parseCollectionsNode,
|
||||||
|
parseOrderDetailsNode,
|
||||||
|
parseProductNode,
|
||||||
|
toProductNode,
|
||||||
|
uploadingNecessaryImagesOfProduct
|
||||||
|
} from '../Utils/business'
|
||||||
import { BinaryNode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
|
import { BinaryNode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
|
||||||
import { getBinaryNodeChild } from '../WABinary/generic-utils'
|
import { getBinaryNodeChild } from '../WABinary/generic-utils'
|
||||||
import { makeMessagesRecvSocket } from './messages-recv'
|
import { makeMessagesRecvSocket } from './messages-recv'
|
||||||
|
|
||||||
export const makeBusinessSocket = (config: SocketConfig) => {
|
export const makeBusinessSocket = (config: SocketConfig) => {
|
||||||
const sock = makeMessagesRecvSocket(config)
|
const sock = makeMessagesRecvSocket(config)
|
||||||
const {
|
const { authState, query, waUploadToServer } = sock
|
||||||
authState,
|
|
||||||
query,
|
|
||||||
waUploadToServer
|
|
||||||
} = sock
|
|
||||||
|
|
||||||
const getCatalog = async ({ jid, limit, cursor }: GetCatalogOptions) => {
|
const getCatalog = async ({ jid, limit, cursor }: GetCatalogOptions) => {
|
||||||
jid = jid || authState.creds.me?.id
|
jid = jid || authState.creds.me?.id
|
||||||
@@ -31,7 +34,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
tag: 'height',
|
tag: 'height',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: Buffer.from('100')
|
content: Buffer.from('100')
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
@@ -54,7 +57,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
tag: 'product_catalog',
|
tag: 'product_catalog',
|
||||||
attrs: {
|
attrs: {
|
||||||
jid,
|
jid,
|
||||||
'allow_shop_source': 'true'
|
allow_shop_source: 'true'
|
||||||
},
|
},
|
||||||
content: queryParamNodes
|
content: queryParamNodes
|
||||||
}
|
}
|
||||||
@@ -72,13 +75,13 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'get',
|
type: 'get',
|
||||||
xmlns: 'w:biz:catalog',
|
xmlns: 'w:biz:catalog',
|
||||||
'smax_id': '35'
|
smax_id: '35'
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
tag: 'collections',
|
tag: 'collections',
|
||||||
attrs: {
|
attrs: {
|
||||||
'biz_jid': jid,
|
biz_jid: jid
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -116,7 +119,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'get',
|
type: 'get',
|
||||||
xmlns: 'fb:thrift_iq',
|
xmlns: 'fb:thrift_iq',
|
||||||
'smax_id': '5'
|
smax_id: '5'
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -245,8 +248,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
{
|
{
|
||||||
tag: 'product_catalog_delete',
|
tag: 'product_catalog_delete',
|
||||||
attrs: { v: '1' },
|
attrs: { v: '1' },
|
||||||
content: productIds.map(
|
content: productIds.map(id => ({
|
||||||
id => ({
|
|
||||||
tag: 'product',
|
tag: 'product',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: [
|
content: [
|
||||||
@@ -256,8 +258,7 @@ export const makeBusinessSocket = (config: SocketConfig) => {
|
|||||||
content: Buffer.from(id)
|
content: Buffer.from(id)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
}))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,12 +2,52 @@ import NodeCache from '@cacheable/node-cache'
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { DEFAULT_CACHE_TTLS, PROCESSABLE_HISTORY_TYPES } from '../Defaults'
|
import { DEFAULT_CACHE_TTLS, PROCESSABLE_HISTORY_TYPES } from '../Defaults'
|
||||||
import { ALL_WA_PATCH_NAMES, BotListInfo, ChatModification, ChatMutation, LTHashState, MessageUpsertType, PresenceData, SocketConfig, WABusinessHoursConfig, WABusinessProfile, WAMediaUpload, WAMessage, WAPatchCreate, WAPatchName, WAPresence, WAPrivacyCallValue, WAPrivacyGroupAddValue, WAPrivacyMessagesValue, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '../Types'
|
import {
|
||||||
|
ALL_WA_PATCH_NAMES,
|
||||||
|
BotListInfo,
|
||||||
|
ChatModification,
|
||||||
|
ChatMutation,
|
||||||
|
LTHashState,
|
||||||
|
MessageUpsertType,
|
||||||
|
PresenceData,
|
||||||
|
SocketConfig,
|
||||||
|
WABusinessHoursConfig,
|
||||||
|
WABusinessProfile,
|
||||||
|
WAMediaUpload,
|
||||||
|
WAMessage,
|
||||||
|
WAPatchCreate,
|
||||||
|
WAPatchName,
|
||||||
|
WAPresence,
|
||||||
|
WAPrivacyCallValue,
|
||||||
|
WAPrivacyGroupAddValue,
|
||||||
|
WAPrivacyMessagesValue,
|
||||||
|
WAPrivacyOnlineValue,
|
||||||
|
WAPrivacyValue,
|
||||||
|
WAReadReceiptsValue
|
||||||
|
} from '../Types'
|
||||||
import { LabelActionBody } from '../Types/Label'
|
import { LabelActionBody } from '../Types/Label'
|
||||||
import { chatModificationToAppPatch, ChatMutationMap, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, extractSyncdPatches, generateProfilePicture, getHistoryMsg, newLTHashState, processSyncAction } from '../Utils'
|
import {
|
||||||
|
chatModificationToAppPatch,
|
||||||
|
ChatMutationMap,
|
||||||
|
decodePatches,
|
||||||
|
decodeSyncdSnapshot,
|
||||||
|
encodeSyncdPatch,
|
||||||
|
extractSyncdPatches,
|
||||||
|
generateProfilePicture,
|
||||||
|
getHistoryMsg,
|
||||||
|
newLTHashState,
|
||||||
|
processSyncAction
|
||||||
|
} from '../Utils'
|
||||||
import { makeMutex } from '../Utils/make-mutex'
|
import { makeMutex } from '../Utils/make-mutex'
|
||||||
import processMessage from '../Utils/process-message'
|
import processMessage from '../Utils/process-message'
|
||||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary'
|
import {
|
||||||
|
BinaryNode,
|
||||||
|
getBinaryNodeChild,
|
||||||
|
getBinaryNodeChildren,
|
||||||
|
jidNormalizedUser,
|
||||||
|
reduceBinaryNodeToDictionary,
|
||||||
|
S_WHATSAPP_NET
|
||||||
|
} from '../WABinary'
|
||||||
import { USyncQuery, USyncUser } from '../WAUSync'
|
import { USyncQuery, USyncUser } from '../WAUSync'
|
||||||
import { makeUSyncSocket } from './usync'
|
import { makeUSyncSocket } from './usync'
|
||||||
const MAX_SYNC_ATTEMPTS = 2
|
const MAX_SYNC_ATTEMPTS = 2
|
||||||
@@ -19,18 +59,10 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
fireInitQueries,
|
fireInitQueries,
|
||||||
appStateMacVerification,
|
appStateMacVerification,
|
||||||
shouldIgnoreJid,
|
shouldIgnoreJid,
|
||||||
shouldSyncHistoryMessage,
|
shouldSyncHistoryMessage
|
||||||
} = config
|
} = config
|
||||||
const sock = makeUSyncSocket(config)
|
const sock = makeUSyncSocket(config)
|
||||||
const {
|
const { ev, ws, authState, generateMessageTag, sendNode, query, onUnexpectedError } = sock
|
||||||
ev,
|
|
||||||
ws,
|
|
||||||
authState,
|
|
||||||
generateMessageTag,
|
|
||||||
sendNode,
|
|
||||||
query,
|
|
||||||
onUnexpectedError,
|
|
||||||
} = sock
|
|
||||||
|
|
||||||
let privacySettings: { [_: string]: string } | undefined
|
let privacySettings: { [_: string]: string } | undefined
|
||||||
let needToFlushWithAppStateSync = false
|
let needToFlushWithAppStateSync = false
|
||||||
@@ -38,7 +70,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
/** this mutex ensures that the notifications (receipts, messages etc.) are processed in order */
|
/** this mutex ensures that the notifications (receipts, messages etc.) are processed in order */
|
||||||
const processingMutex = makeMutex()
|
const processingMutex = makeMutex()
|
||||||
|
|
||||||
const placeholderResendCache = config.placeholderResendCache || new NodeCache({
|
const placeholderResendCache =
|
||||||
|
config.placeholderResendCache ||
|
||||||
|
new NodeCache({
|
||||||
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
||||||
useClones: false
|
useClones: false
|
||||||
})
|
})
|
||||||
@@ -62,9 +96,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'get'
|
type: 'get'
|
||||||
},
|
},
|
||||||
content: [
|
content: [{ tag: 'privacy', attrs: {} }]
|
||||||
{ tag: 'privacy', attrs: {} }
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
privacySettings = reduceBinaryNodeToDictionary(content?.[0] as BinaryNode, 'category')
|
privacySettings = reduceBinaryNodeToDictionary(content?.[0] as BinaryNode, 'category')
|
||||||
}
|
}
|
||||||
@@ -81,7 +113,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'set'
|
type: 'set'
|
||||||
},
|
},
|
||||||
content: [{
|
content: [
|
||||||
|
{
|
||||||
tag: 'privacy',
|
tag: 'privacy',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: [
|
content: [
|
||||||
@@ -90,7 +123,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
attrs: { name, value }
|
attrs: { name, value }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,12 +168,14 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'set'
|
type: 'set'
|
||||||
},
|
},
|
||||||
content: [{
|
content: [
|
||||||
|
{
|
||||||
tag: 'disappearing_mode',
|
tag: 'disappearing_mode',
|
||||||
attrs: {
|
attrs: {
|
||||||
duration: duration.toString()
|
duration: duration.toString()
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,12 +187,14 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'get'
|
type: 'get'
|
||||||
},
|
},
|
||||||
content: [{
|
content: [
|
||||||
|
{
|
||||||
tag: 'bot',
|
tag: 'bot',
|
||||||
attrs: {
|
attrs: {
|
||||||
v: '2'
|
v: '2'
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const botNode = getBinaryNodeChild(resp, 'bot')
|
const botNode = getBinaryNodeChild(resp, 'bot')
|
||||||
@@ -177,9 +215,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onWhatsApp = async (...jids: string[]) => {
|
const onWhatsApp = async (...jids: string[]) => {
|
||||||
const usyncQuery = new USyncQuery()
|
const usyncQuery = new USyncQuery().withContactProtocol().withLIDProtocol()
|
||||||
.withContactProtocol()
|
|
||||||
.withLIDProtocol()
|
|
||||||
|
|
||||||
for (const jid of jids) {
|
for (const jid of jids) {
|
||||||
const phone = `+${jid.replace('+', '').split('@')[0].split(':')[0]}`
|
const phone = `+${jid.replace('+', '').split('@')[0].split(':')[0]}`
|
||||||
@@ -189,13 +225,12 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
const results = await sock.executeUSyncQuery(usyncQuery)
|
const results = await sock.executeUSyncQuery(usyncQuery)
|
||||||
|
|
||||||
if (results) {
|
if (results) {
|
||||||
return results.list.filter((a) => !!a.contact).map(({ contact, id, lid }) => ({ jid: id, exists: contact, lid }))
|
return results.list.filter(a => !!a.contact).map(({ contact, id, lid }) => ({ jid: id, exists: contact, lid }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchStatus = async (...jids: string[]) => {
|
const fetchStatus = async (...jids: string[]) => {
|
||||||
const usyncQuery = new USyncQuery()
|
const usyncQuery = new USyncQuery().withStatusProtocol()
|
||||||
.withStatusProtocol()
|
|
||||||
|
|
||||||
for (const jid of jids) {
|
for (const jid of jids) {
|
||||||
usyncQuery.withUser(new USyncUser().withId(jid))
|
usyncQuery.withUser(new USyncUser().withId(jid))
|
||||||
@@ -208,8 +243,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fetchDisappearingDuration = async (...jids: string[]) => {
|
const fetchDisappearingDuration = async (...jids: string[]) => {
|
||||||
const usyncQuery = new USyncQuery()
|
const usyncQuery = new USyncQuery().withDisappearingModeProtocol()
|
||||||
.withDisappearingModeProtocol()
|
|
||||||
|
|
||||||
for (const jid of jids) {
|
for (const jid of jids) {
|
||||||
usyncQuery.withUser(new USyncUser().withId(jid))
|
usyncQuery.withUser(new USyncUser().withId(jid))
|
||||||
@@ -225,7 +259,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
const updateProfilePicture = async (jid: string, content: WAMediaUpload) => {
|
const updateProfilePicture = async (jid: string, content: WAMediaUpload) => {
|
||||||
let targetJid
|
let targetJid
|
||||||
if (!jid) {
|
if (!jid) {
|
||||||
throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update')
|
throw new Boom(
|
||||||
|
'Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) {
|
if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) {
|
||||||
@@ -255,7 +291,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
const removeProfilePicture = async (jid: string) => {
|
const removeProfilePicture = async (jid: string) => {
|
||||||
let targetJid
|
let targetJid
|
||||||
if (!jid) {
|
if (!jid) {
|
||||||
throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update')
|
throw new Boom(
|
||||||
|
'Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) {
|
if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) {
|
||||||
@@ -307,8 +345,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const listNode = getBinaryNodeChild(result, 'list')
|
const listNode = getBinaryNodeChild(result, 'list')
|
||||||
return getBinaryNodeChildren(listNode, 'item')
|
return getBinaryNodeChildren(listNode, 'item').map(n => n.attrs.jid)
|
||||||
.map(n => n.attrs.jid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateBlockStatus = async (jid: string, action: 'block' | 'unblock') => {
|
const updateBlockStatus = async (jid: string, action: 'block' | 'unblock') => {
|
||||||
@@ -339,14 +376,18 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
xmlns: 'w:biz',
|
xmlns: 'w:biz',
|
||||||
type: 'get'
|
type: 'get'
|
||||||
},
|
},
|
||||||
content: [{
|
content: [
|
||||||
|
{
|
||||||
tag: 'business_profile',
|
tag: 'business_profile',
|
||||||
attrs: { v: '244' },
|
attrs: { v: '244' },
|
||||||
content: [{
|
content: [
|
||||||
|
{
|
||||||
tag: 'profile',
|
tag: 'profile',
|
||||||
attrs: { jid }
|
attrs: { jid }
|
||||||
}]
|
}
|
||||||
}]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const profileNode = getBinaryNodeChild(results, 'business_profile')
|
const profileNode = getBinaryNodeChild(results, 'business_profile')
|
||||||
@@ -369,9 +410,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
website: websiteStr ? [websiteStr] : [],
|
website: websiteStr ? [websiteStr] : [],
|
||||||
email: email?.content?.toString(),
|
email: email?.content?.toString(),
|
||||||
category: category?.content?.toString(),
|
category: category?.content?.toString(),
|
||||||
'business_hours': {
|
business_hours: {
|
||||||
timezone: businessHours?.attrs?.timezone,
|
timezone: businessHours?.attrs?.timezone,
|
||||||
'business_config': businessHoursConfig?.map(({ attrs }) => attrs as unknown as WABusinessHoursConfig)
|
business_config: businessHoursConfig?.map(({ attrs }) => attrs as unknown as WABusinessHoursConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,14 +426,14 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'set',
|
type: 'set',
|
||||||
xmlns: 'urn:xmpp:whatsapp:dirty',
|
xmlns: 'urn:xmpp:whatsapp:dirty',
|
||||||
id: generateMessageTag(),
|
id: generateMessageTag()
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
tag: 'clean',
|
tag: 'clean',
|
||||||
attrs: {
|
attrs: {
|
||||||
type,
|
type,
|
||||||
...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null),
|
...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -413,14 +454,14 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resyncAppState = ev.createBufferedFunction(async(collections: readonly WAPatchName[], isInitialSync: boolean) => {
|
const resyncAppState = ev.createBufferedFunction(
|
||||||
|
async (collections: readonly WAPatchName[], isInitialSync: boolean) => {
|
||||||
// we use this to determine which events to fire
|
// we use this to determine which events to fire
|
||||||
// otherwise when we resync from scratch -- all notifications will fire
|
// otherwise when we resync from scratch -- all notifications will fire
|
||||||
const initialVersionMap: { [T in WAPatchName]?: number } = {}
|
const initialVersionMap: { [T in WAPatchName]?: number } = {}
|
||||||
const globalMutationMap: ChatMutationMap = {}
|
const globalMutationMap: ChatMutationMap = {}
|
||||||
|
|
||||||
await authState.keys.transaction(
|
await authState.keys.transaction(async () => {
|
||||||
async() => {
|
|
||||||
const collectionsToHandle = new Set<string>(collections)
|
const collectionsToHandle = new Set<string>(collections)
|
||||||
// in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
|
// in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
|
||||||
const attemptsMap: { [T in WAPatchName]?: number } = {}
|
const attemptsMap: { [T in WAPatchName]?: number } = {}
|
||||||
@@ -453,7 +494,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
name,
|
name,
|
||||||
version: state.version.toString(),
|
version: state.version.toString(),
|
||||||
// return snapshot if being synced from scratch
|
// return snapshot if being synced from scratch
|
||||||
'return_snapshot': (!state.version).toString()
|
return_snapshot: (!state.version).toString()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -519,15 +560,17 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
if (hasMorePatches) {
|
if (hasMorePatches) {
|
||||||
logger.info(`${name} has more patches...`)
|
logger.info(`${name} has more patches...`)
|
||||||
} else { // collection is done with sync
|
} else {
|
||||||
|
// collection is done with sync
|
||||||
collectionsToHandle.delete(name)
|
collectionsToHandle.delete(name)
|
||||||
}
|
}
|
||||||
} 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
|
const isIrrecoverableError =
|
||||||
|| error.output?.statusCode === 404
|
attemptsMap[name]! >= MAX_SYNC_ATTEMPTS ||
|
||||||
|| error.name === 'TypeError'
|
error.output?.statusCode === 404 ||
|
||||||
|
error.name === 'TypeError'
|
||||||
logger.info(
|
logger.info(
|
||||||
{ name, error: error.stack },
|
{ name, error: error.stack },
|
||||||
`failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`
|
`failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`
|
||||||
@@ -543,14 +586,14 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const { onMutation } = newAppStateChunkHandler(isInitialSync)
|
const { onMutation } = newAppStateChunkHandler(isInitialSync)
|
||||||
for (const key in globalMutationMap) {
|
for (const key in globalMutationMap) {
|
||||||
onMutation(globalMutationMap[key])
|
onMutation(globalMutationMap[key])
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fetch the profile picture of a user/group
|
* fetch the profile picture of a user/group
|
||||||
@@ -559,7 +602,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
*/
|
*/
|
||||||
const profilePictureUrl = async (jid: string, type: 'preview' | 'image' = 'preview', timeoutMs?: number) => {
|
const profilePictureUrl = async (jid: string, type: 'preview' | 'image' = 'preview', timeoutMs?: number) => {
|
||||||
jid = jidNormalizedUser(jid)
|
jid = jidNormalizedUser(jid)
|
||||||
const result = await query({
|
const result = await query(
|
||||||
|
{
|
||||||
tag: 'iq',
|
tag: 'iq',
|
||||||
attrs: {
|
attrs: {
|
||||||
target: jid,
|
target: jid,
|
||||||
@@ -567,10 +611,10 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
type: 'get',
|
type: 'get',
|
||||||
xmlns: 'w:profile:picture'
|
xmlns: 'w:profile:picture'
|
||||||
},
|
},
|
||||||
content: [
|
content: [{ tag: 'picture', attrs: { type, query: 'url' } }]
|
||||||
{ tag: 'picture', attrs: { type, query: 'url' } }
|
},
|
||||||
]
|
timeoutMs
|
||||||
}, timeoutMs)
|
)
|
||||||
const child = getBinaryNodeChild(result, 'picture')
|
const child = getBinaryNodeChild(result, 'picture')
|
||||||
return child?.attrs?.url
|
return child?.attrs?.url
|
||||||
}
|
}
|
||||||
@@ -597,7 +641,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
tag: 'chatstate',
|
tag: 'chatstate',
|
||||||
attrs: {
|
attrs: {
|
||||||
from: me.id,
|
from: me.id,
|
||||||
to: toJid!,
|
to: toJid!
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -613,7 +657,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
* @param toJid the jid to subscribe to
|
* @param toJid the jid to subscribe to
|
||||||
* @param tcToken token for subscription, use if present
|
* @param tcToken token for subscription, use if present
|
||||||
*/
|
*/
|
||||||
const presenceSubscribe = (toJid: string, tcToken?: Buffer) => (
|
const presenceSubscribe = (toJid: string, tcToken?: Buffer) =>
|
||||||
sendNode({
|
sendNode({
|
||||||
tag: 'presence',
|
tag: 'presence',
|
||||||
attrs: {
|
attrs: {
|
||||||
@@ -631,7 +675,6 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
]
|
]
|
||||||
: undefined
|
: undefined
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const handlePresenceUpdate = ({ tag, attrs, content }: BinaryNode) => {
|
const handlePresenceUpdate = ({ tag, attrs, content }: BinaryNode) => {
|
||||||
let presence: PresenceData | undefined
|
let presence: PresenceData | undefined
|
||||||
@@ -676,12 +719,10 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let initial: LTHashState
|
let initial: LTHashState
|
||||||
let encodeResult: { patch: proto.ISyncdPatch, state: LTHashState }
|
let encodeResult: { patch: proto.ISyncdPatch; state: LTHashState }
|
||||||
|
|
||||||
await processingMutex.mutex(
|
await processingMutex.mutex(async () => {
|
||||||
async() => {
|
await authState.keys.transaction(async () => {
|
||||||
await authState.keys.transaction(
|
|
||||||
async() => {
|
|
||||||
logger.debug({ patch: patchCreate }, 'applying app patch')
|
logger.debug({ patch: patchCreate }, 'applying app patch')
|
||||||
|
|
||||||
await resyncAppState([name], false)
|
await resyncAppState([name], false)
|
||||||
@@ -689,12 +730,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name])
|
const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name])
|
||||||
initial = currentSyncVersion || newLTHashState()
|
initial = currentSyncVersion || newLTHashState()
|
||||||
|
|
||||||
encodeResult = await encodeSyncdPatch(
|
encodeResult = await encodeSyncdPatch(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey)
|
||||||
patchCreate,
|
|
||||||
myAppStateKeyId,
|
|
||||||
initial,
|
|
||||||
getAppStateSyncKey,
|
|
||||||
)
|
|
||||||
const { patch, state } = encodeResult
|
const { patch, state } = encodeResult
|
||||||
|
|
||||||
const node: BinaryNode = {
|
const node: BinaryNode = {
|
||||||
@@ -714,7 +750,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
attrs: {
|
attrs: {
|
||||||
name,
|
name,
|
||||||
version: (state.version - 1).toString(),
|
version: (state.version - 1).toString(),
|
||||||
'return_snapshot': 'false'
|
return_snapshot: 'false'
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -731,21 +767,19 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
await query(node)
|
await query(node)
|
||||||
|
|
||||||
await authState.keys.set({ 'app-state-sync-version': { [name]: state } })
|
await authState.keys.set({ 'app-state-sync-version': { [name]: state } })
|
||||||
}
|
})
|
||||||
)
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (config.emitOwnEvents) {
|
if (config.emitOwnEvents) {
|
||||||
const { onMutation } = newAppStateChunkHandler(false)
|
const { onMutation } = newAppStateChunkHandler(false)
|
||||||
const { mutationMap } = await decodePatches(
|
const { mutationMap } = await decodePatches(
|
||||||
name,
|
name,
|
||||||
[{ ...encodeResult!.patch, version: { version: encodeResult!.state.version }, }],
|
[{ ...encodeResult!.patch, version: { version: encodeResult!.state.version } }],
|
||||||
initial!,
|
initial!,
|
||||||
getAppStateSyncKey,
|
getAppStateSyncKey,
|
||||||
config.options,
|
config.options,
|
||||||
undefined,
|
undefined,
|
||||||
logger,
|
logger
|
||||||
)
|
)
|
||||||
for (const key in mutationMap) {
|
for (const key in mutationMap) {
|
||||||
onMutation(mutationMap[key])
|
onMutation(mutationMap[key])
|
||||||
@@ -760,22 +794,25 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
attrs: {
|
attrs: {
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
xmlns: 'w',
|
xmlns: 'w',
|
||||||
type: 'get',
|
type: 'get'
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{ tag: 'props', attrs: {
|
{
|
||||||
|
tag: 'props',
|
||||||
|
attrs: {
|
||||||
protocol: '2',
|
protocol: '2',
|
||||||
hash: authState?.creds?.lastPropHash || ''
|
hash: authState?.creds?.lastPropHash || ''
|
||||||
} }
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const propsNode = getBinaryNodeChild(resultNode, 'props')
|
const propsNode = getBinaryNodeChild(resultNode, 'props')
|
||||||
|
|
||||||
|
|
||||||
let props: { [_: string]: string } = {}
|
let props: { [_: string]: string } = {}
|
||||||
if (propsNode) {
|
if (propsNode) {
|
||||||
if(propsNode.attrs?.hash) { // on some clients, the hash is returning as undefined
|
if (propsNode.attrs?.hash) {
|
||||||
|
// on some clients, the hash is returning as undefined
|
||||||
authState.creds.lastPropHash = propsNode?.attrs?.hash
|
authState.creds.lastPropHash = propsNode?.attrs?.hash
|
||||||
ev.emit('creds.update', authState.creds)
|
ev.emit('creds.update', authState.creds)
|
||||||
}
|
}
|
||||||
@@ -801,70 +838,88 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
/**
|
/**
|
||||||
* Star or Unstar a message
|
* Star or Unstar a message
|
||||||
*/
|
*/
|
||||||
const star = (jid: string, messages: { id: string, fromMe?: boolean }[], star: boolean) => {
|
const star = (jid: string, messages: { id: string; fromMe?: boolean }[], star: boolean) => {
|
||||||
return chatModify({
|
return chatModify(
|
||||||
|
{
|
||||||
star: {
|
star: {
|
||||||
messages,
|
messages,
|
||||||
star
|
star
|
||||||
}
|
}
|
||||||
}, jid)
|
},
|
||||||
|
jid
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds label
|
* Adds label
|
||||||
*/
|
*/
|
||||||
const addLabel = (jid: string, labels: LabelActionBody) => {
|
const addLabel = (jid: string, labels: LabelActionBody) => {
|
||||||
return chatModify({
|
return chatModify(
|
||||||
|
{
|
||||||
addLabel: {
|
addLabel: {
|
||||||
...labels
|
...labels
|
||||||
}
|
}
|
||||||
}, jid)
|
},
|
||||||
|
jid
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds label for the chats
|
* Adds label for the chats
|
||||||
*/
|
*/
|
||||||
const addChatLabel = (jid: string, labelId: string) => {
|
const addChatLabel = (jid: string, labelId: string) => {
|
||||||
return chatModify({
|
return chatModify(
|
||||||
|
{
|
||||||
addChatLabel: {
|
addChatLabel: {
|
||||||
labelId
|
labelId
|
||||||
}
|
}
|
||||||
}, jid)
|
},
|
||||||
|
jid
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes label for the chat
|
* Removes label for the chat
|
||||||
*/
|
*/
|
||||||
const removeChatLabel = (jid: string, labelId: string) => {
|
const removeChatLabel = (jid: string, labelId: string) => {
|
||||||
return chatModify({
|
return chatModify(
|
||||||
|
{
|
||||||
removeChatLabel: {
|
removeChatLabel: {
|
||||||
labelId
|
labelId
|
||||||
}
|
}
|
||||||
}, jid)
|
},
|
||||||
|
jid
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds label for the message
|
* Adds label for the message
|
||||||
*/
|
*/
|
||||||
const addMessageLabel = (jid: string, messageId: string, labelId: string) => {
|
const addMessageLabel = (jid: string, messageId: string, labelId: string) => {
|
||||||
return chatModify({
|
return chatModify(
|
||||||
|
{
|
||||||
addMessageLabel: {
|
addMessageLabel: {
|
||||||
messageId,
|
messageId,
|
||||||
labelId
|
labelId
|
||||||
}
|
}
|
||||||
}, jid)
|
},
|
||||||
|
jid
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes label for the message
|
* Removes label for the message
|
||||||
*/
|
*/
|
||||||
const removeMessageLabel = (jid: string, messageId: string, labelId: string) => {
|
const removeMessageLabel = (jid: string, messageId: string, labelId: string) => {
|
||||||
return chatModify({
|
return chatModify(
|
||||||
|
{
|
||||||
removeMessageLabel: {
|
removeMessageLabel: {
|
||||||
messageId,
|
messageId,
|
||||||
labelId
|
labelId
|
||||||
}
|
}
|
||||||
}, jid)
|
},
|
||||||
|
jid
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -872,18 +927,14 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
* help ensure parity with WA Web
|
* help ensure parity with WA Web
|
||||||
* */
|
* */
|
||||||
const executeInitQueries = async () => {
|
const executeInitQueries = async () => {
|
||||||
await Promise.all([
|
await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings()])
|
||||||
fetchProps(),
|
|
||||||
fetchBlocklist(),
|
|
||||||
fetchPrivacySettings(),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const upsertMessage = ev.createBufferedFunction(async (msg: WAMessage, type: MessageUpsertType) => {
|
const upsertMessage = ev.createBufferedFunction(async (msg: WAMessage, type: MessageUpsertType) => {
|
||||||
ev.emit('messages.upsert', { messages: [msg], type })
|
ev.emit('messages.upsert', { messages: [msg], type })
|
||||||
|
|
||||||
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) {
|
||||||
@@ -898,10 +949,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
const historyMsg = getHistoryMsg(msg.message!)
|
const historyMsg = getHistoryMsg(msg.message!)
|
||||||
const shouldProcessHistoryMsg = historyMsg
|
const shouldProcessHistoryMsg = historyMsg
|
||||||
? (
|
? shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType!)
|
||||||
shouldSyncHistoryMessage(historyMsg)
|
|
||||||
&& PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType!)
|
|
||||||
)
|
|
||||||
: false
|
: false
|
||||||
|
|
||||||
if (historyMsg && !authState.creds.myAppStateKeyId) {
|
if (historyMsg && !authState.creds.myAppStateKeyId) {
|
||||||
@@ -911,32 +959,23 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
(async () => {
|
(async () => {
|
||||||
if(
|
if (historyMsg && authState.creds.myAppStateKeyId) {
|
||||||
historyMsg
|
|
||||||
&& authState.creds.myAppStateKeyId
|
|
||||||
) {
|
|
||||||
pendingAppStateSync = false
|
pendingAppStateSync = false
|
||||||
await doAppStateSync()
|
await doAppStateSync()
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
processMessage(
|
processMessage(msg, {
|
||||||
msg,
|
|
||||||
{
|
|
||||||
shouldProcessHistoryMsg,
|
shouldProcessHistoryMsg,
|
||||||
placeholderResendCache,
|
placeholderResendCache,
|
||||||
ev,
|
ev,
|
||||||
creds: authState.creds,
|
creds: authState.creds,
|
||||||
keyStore: authState.keys,
|
keyStore: authState.keys,
|
||||||
logger,
|
logger,
|
||||||
options: config.options,
|
options: config.options
|
||||||
}
|
})
|
||||||
)
|
|
||||||
])
|
])
|
||||||
|
|
||||||
if(
|
if (msg.message?.protocolMessage?.appStateSyncKeyShare && pendingAppStateSync) {
|
||||||
msg.message?.protocolMessage?.appStateSyncKeyShare
|
|
||||||
&& pendingAppStateSync
|
|
||||||
) {
|
|
||||||
await doAppStateSync()
|
await doAppStateSync()
|
||||||
pendingAppStateSync = false
|
pendingAppStateSync = false
|
||||||
}
|
}
|
||||||
@@ -988,23 +1027,21 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
|
ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
|
||||||
if (connection === 'open') {
|
if (connection === 'open') {
|
||||||
if (fireInitQueries) {
|
if (fireInitQueries) {
|
||||||
executeInitQueries()
|
executeInitQueries().catch(error => onUnexpectedError(error, 'init queries'))
|
||||||
.catch(
|
}
|
||||||
error => onUnexpectedError(error, 'init queries')
|
|
||||||
|
sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable').catch(error =>
|
||||||
|
onUnexpectedError(error, 'presence update requests')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable')
|
if (
|
||||||
.catch(
|
receivedPendingNotifications && // if we don't have the app state key
|
||||||
error => onUnexpectedError(error, 'presence update requests')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(receivedPendingNotifications && // if we don't have the app state key
|
|
||||||
// we keep buffering events until we finally have
|
// we keep buffering events until we finally have
|
||||||
// the key and can sync the messages
|
// the key and can sync the messages
|
||||||
// todo scrutinize
|
// todo scrutinize
|
||||||
!authState.creds?.myAppStateKeyId) {
|
!authState.creds?.myAppStateKeyId
|
||||||
|
) {
|
||||||
ev.buffer()
|
ev.buffer()
|
||||||
needToFlushWithAppStateSync = true
|
needToFlushWithAppStateSync = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,50 @@
|
|||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { GroupMetadata, GroupParticipant, ParticipantAction, SocketConfig, WAMessageKey, WAMessageStubType } from '../Types'
|
import {
|
||||||
|
GroupMetadata,
|
||||||
|
GroupParticipant,
|
||||||
|
ParticipantAction,
|
||||||
|
SocketConfig,
|
||||||
|
WAMessageKey,
|
||||||
|
WAMessageStubType
|
||||||
|
} from '../Types'
|
||||||
import { generateMessageIDV2, unixTimestampSeconds } from '../Utils'
|
import { generateMessageIDV2, unixTimestampSeconds } from '../Utils'
|
||||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString, jidEncode, jidNormalizedUser } from '../WABinary'
|
import {
|
||||||
|
BinaryNode,
|
||||||
|
getBinaryNodeChild,
|
||||||
|
getBinaryNodeChildren,
|
||||||
|
getBinaryNodeChildString,
|
||||||
|
jidEncode,
|
||||||
|
jidNormalizedUser
|
||||||
|
} from '../WABinary'
|
||||||
import { makeChatsSocket } from './chats'
|
import { makeChatsSocket } from './chats'
|
||||||
|
|
||||||
export const makeGroupsSocket = (config: SocketConfig) => {
|
export const makeGroupsSocket = (config: SocketConfig) => {
|
||||||
const sock = makeChatsSocket(config)
|
const sock = makeChatsSocket(config)
|
||||||
const { authState, ev, query, upsertMessage } = sock
|
const { authState, ev, query, upsertMessage } = sock
|
||||||
|
|
||||||
const groupQuery = async(jid: string, type: 'get' | 'set', content: BinaryNode[]) => (
|
const groupQuery = async (jid: string, type: 'get' | 'set', content: BinaryNode[]) =>
|
||||||
query({
|
query({
|
||||||
tag: 'iq',
|
tag: 'iq',
|
||||||
attrs: {
|
attrs: {
|
||||||
type,
|
type,
|
||||||
xmlns: 'w:g2',
|
xmlns: 'w:g2',
|
||||||
to: jid,
|
to: jid
|
||||||
},
|
},
|
||||||
content
|
content
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const groupMetadata = async (jid: string) => {
|
const groupMetadata = async (jid: string) => {
|
||||||
const result = await groupQuery(
|
const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }])
|
||||||
jid,
|
|
||||||
'get',
|
|
||||||
[ { tag: 'query', attrs: { request: 'interactive' } } ]
|
|
||||||
)
|
|
||||||
return extractGroupMetadata(result)
|
return extractGroupMetadata(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const groupFetchAllParticipating = async () => {
|
const groupFetchAllParticipating = async () => {
|
||||||
const result = await query({
|
const result = await query({
|
||||||
tag: 'iq',
|
tag: 'iq',
|
||||||
attrs: {
|
attrs: {
|
||||||
to: '@g.us',
|
to: '@g.us',
|
||||||
xmlns: 'w:g2',
|
xmlns: 'w:g2',
|
||||||
type: 'get',
|
type: 'get'
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -83,10 +91,7 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
groupMetadata,
|
groupMetadata,
|
||||||
groupCreate: async (subject: string, participants: string[]) => {
|
groupCreate: async (subject: string, participants: string[]) => {
|
||||||
const key = generateMessageIDV2()
|
const key = generateMessageIDV2()
|
||||||
const result = await groupQuery(
|
const result = await groupQuery('@g.us', 'set', [
|
||||||
'@g.us',
|
|
||||||
'set',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
tag: 'create',
|
tag: 'create',
|
||||||
attrs: {
|
attrs: {
|
||||||
@@ -98,58 +103,41 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
attrs: { jid }
|
attrs: { jid }
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
)
|
|
||||||
return extractGroupMetadata(result)
|
return extractGroupMetadata(result)
|
||||||
},
|
},
|
||||||
groupLeave: async (id: string) => {
|
groupLeave: async (id: string) => {
|
||||||
await groupQuery(
|
await groupQuery('@g.us', 'set', [
|
||||||
'@g.us',
|
|
||||||
'set',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
tag: 'leave',
|
tag: 'leave',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: [
|
content: [{ tag: 'group', attrs: { id } }]
|
||||||
{ tag: 'group', attrs: { id } }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
)
|
|
||||||
},
|
},
|
||||||
groupUpdateSubject: async (jid: string, subject: string) => {
|
groupUpdateSubject: async (jid: string, subject: string) => {
|
||||||
await groupQuery(
|
await groupQuery(jid, 'set', [
|
||||||
jid,
|
|
||||||
'set',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
tag: 'subject',
|
tag: 'subject',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: Buffer.from(subject, 'utf-8')
|
content: Buffer.from(subject, 'utf-8')
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
)
|
|
||||||
},
|
},
|
||||||
groupRequestParticipantsList: async (jid: string) => {
|
groupRequestParticipantsList: async (jid: string) => {
|
||||||
const result = await groupQuery(
|
const result = await groupQuery(jid, 'get', [
|
||||||
jid,
|
|
||||||
'get',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
tag: 'membership_approval_requests',
|
tag: 'membership_approval_requests',
|
||||||
attrs: {}
|
attrs: {}
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
)
|
|
||||||
const node = getBinaryNodeChild(result, 'membership_approval_requests')
|
const node = getBinaryNodeChild(result, 'membership_approval_requests')
|
||||||
const participants = getBinaryNodeChildren(node, 'membership_approval_request')
|
const participants = getBinaryNodeChildren(node, 'membership_approval_request')
|
||||||
return participants.map(v => v.attrs)
|
return participants.map(v => v.attrs)
|
||||||
},
|
},
|
||||||
groupRequestParticipantsUpdate: async (jid: string, participants: string[], action: 'approve' | 'reject') => {
|
groupRequestParticipantsUpdate: async (jid: string, participants: string[], action: 'approve' | 'reject') => {
|
||||||
const result = await groupQuery(
|
const result = await groupQuery(jid, 'set', [
|
||||||
jid,
|
{
|
||||||
'set',
|
|
||||||
[{
|
|
||||||
tag: 'membership_requests_action',
|
tag: 'membership_requests_action',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: [
|
content: [
|
||||||
@@ -162,8 +150,8 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}
|
||||||
)
|
])
|
||||||
const node = getBinaryNodeChild(result, 'membership_requests_action')
|
const node = getBinaryNodeChild(result, 'membership_requests_action')
|
||||||
const nodeAction = getBinaryNodeChild(node, action)
|
const nodeAction = getBinaryNodeChild(node, action)
|
||||||
const participantsAffected = getBinaryNodeChildren(nodeAction, 'participant')
|
const participantsAffected = getBinaryNodeChildren(nodeAction, 'participant')
|
||||||
@@ -171,15 +159,8 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
return { status: p.attrs.error || '200', jid: p.attrs.jid }
|
return { status: p.attrs.error || '200', jid: p.attrs.jid }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
groupParticipantsUpdate: async(
|
groupParticipantsUpdate: async (jid: string, participants: string[], action: ParticipantAction) => {
|
||||||
jid: string,
|
const result = await groupQuery(jid, 'set', [
|
||||||
participants: string[],
|
|
||||||
action: ParticipantAction
|
|
||||||
) => {
|
|
||||||
const result = await groupQuery(
|
|
||||||
jid,
|
|
||||||
'set',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
tag: action,
|
tag: action,
|
||||||
attrs: {},
|
attrs: {},
|
||||||
@@ -188,8 +169,7 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
attrs: { jid }
|
attrs: { jid }
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
)
|
|
||||||
const node = getBinaryNodeChild(result, action)
|
const node = getBinaryNodeChild(result, action)
|
||||||
const participantsAffected = getBinaryNodeChildren(node, 'participant')
|
const participantsAffected = getBinaryNodeChildren(node, 'participant')
|
||||||
return participantsAffected.map(p => {
|
return participantsAffected.map(p => {
|
||||||
@@ -200,22 +180,16 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
const metadata = await groupMetadata(jid)
|
const metadata = await groupMetadata(jid)
|
||||||
const prev = metadata.descId ?? null
|
const prev = metadata.descId ?? null
|
||||||
|
|
||||||
await groupQuery(
|
await groupQuery(jid, 'set', [
|
||||||
jid,
|
|
||||||
'set',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
tag: 'description',
|
tag: 'description',
|
||||||
attrs: {
|
attrs: {
|
||||||
...(description ? { id: generateMessageIDV2() } : { delete: 'true' }),
|
...(description ? { id: generateMessageIDV2() } : { delete: 'true' }),
|
||||||
...(prev ? { prev } : {})
|
...(prev ? { prev } : {})
|
||||||
},
|
},
|
||||||
content: description ? [
|
content: description ? [{ tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8') }] : undefined
|
||||||
{ tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8') }
|
|
||||||
] : undefined
|
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
)
|
|
||||||
},
|
},
|
||||||
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: {} }])
|
||||||
@@ -240,7 +214,9 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
* @returns true if successful
|
* @returns true if successful
|
||||||
*/
|
*/
|
||||||
groupRevokeInviteV4: async (groupJid: string, invitedJid: string) => {
|
groupRevokeInviteV4: async (groupJid: string, invitedJid: string) => {
|
||||||
const result = await groupQuery(groupJid, 'set', [{ tag: 'revoke', attrs: {}, content: [{ tag: 'participant', attrs: { jid: invitedJid } }] }])
|
const result = await groupQuery(groupJid, 'set', [
|
||||||
|
{ tag: 'revoke', attrs: {}, content: [{ tag: 'participant', attrs: { jid: invitedJid } }] }
|
||||||
|
])
|
||||||
return !!result
|
return !!result
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -249,16 +225,19 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
* @param key the key of the invite message, or optionally only provide the jid of the person who sent the invite
|
* @param key the key of the invite message, or optionally only provide the jid of the person who sent the invite
|
||||||
* @param inviteMessage the message to accept
|
* @param inviteMessage the message to accept
|
||||||
*/
|
*/
|
||||||
groupAcceptInviteV4: ev.createBufferedFunction(async(key: string | WAMessageKey, inviteMessage: proto.Message.IGroupInviteMessage) => {
|
groupAcceptInviteV4: ev.createBufferedFunction(
|
||||||
|
async (key: string | WAMessageKey, inviteMessage: proto.Message.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!
|
||||||
}
|
}
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
|
|
||||||
// if we have the full message key
|
// if we have the full message key
|
||||||
// update the invite message to be expired
|
// update the invite message to be expired
|
||||||
@@ -286,12 +265,10 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
remoteJid: inviteMessage.groupJid,
|
remoteJid: inviteMessage.groupJid,
|
||||||
id: generateMessageIDV2(sock.user?.id),
|
id: generateMessageIDV2(sock.user?.id),
|
||||||
fromMe: false,
|
fromMe: false,
|
||||||
participant: key.remoteJid,
|
participant: key.remoteJid
|
||||||
},
|
},
|
||||||
messageStubType: WAMessageStubType.GROUP_PARTICIPANT_ADD,
|
messageStubType: WAMessageStubType.GROUP_PARTICIPANT_ADD,
|
||||||
messageStubParameters: [
|
messageStubParameters: [authState.creds.me!.id],
|
||||||
authState.creds.me!.id
|
|
||||||
],
|
|
||||||
participant: key.remoteJid,
|
participant: key.remoteJid,
|
||||||
messageTimestamp: unixTimestampSeconds()
|
messageTimestamp: unixTimestampSeconds()
|
||||||
},
|
},
|
||||||
@@ -299,15 +276,16 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return results.attrs.from
|
return results.attrs.from
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
groupGetInviteInfo: async (code: string) => {
|
groupGetInviteInfo: async (code: string) => {
|
||||||
const results = await groupQuery('@g.us', 'get', [{ tag: 'invite', attrs: { code } }])
|
const results = await groupQuery('@g.us', 'get', [{ tag: 'invite', attrs: { code } }])
|
||||||
return extractGroupMetadata(results)
|
return extractGroupMetadata(results)
|
||||||
},
|
},
|
||||||
groupToggleEphemeral: async (jid: string, ephemeralExpiration: number) => {
|
groupToggleEphemeral: async (jid: string, ephemeralExpiration: number) => {
|
||||||
const content: BinaryNode = ephemeralExpiration ?
|
const content: BinaryNode = ephemeralExpiration
|
||||||
{ tag: 'ephemeral', attrs: { expiration: ephemeralExpiration.toString() } } :
|
? { tag: 'ephemeral', attrs: { expiration: ephemeralExpiration.toString() } }
|
||||||
{ tag: 'not_ephemeral', attrs: { } }
|
: { tag: 'not_ephemeral', attrs: {} }
|
||||||
await groupQuery(jid, 'set', [content])
|
await groupQuery(jid, 'set', [content])
|
||||||
},
|
},
|
||||||
groupSettingUpdate: async (jid: string, setting: 'announcement' | 'not_announcement' | 'locked' | 'unlocked') => {
|
groupSettingUpdate: async (jid: string, setting: 'announcement' | 'not_announcement' | 'locked' | 'unlocked') => {
|
||||||
@@ -317,13 +295,14 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
await groupQuery(jid, 'set', [{ tag: 'member_add_mode', attrs: {}, content: mode }])
|
await groupQuery(jid, 'set', [{ tag: 'member_add_mode', attrs: {}, content: mode }])
|
||||||
},
|
},
|
||||||
groupJoinApprovalMode: async (jid: string, mode: 'on' | 'off') => {
|
groupJoinApprovalMode: async (jid: string, mode: 'on' | 'off') => {
|
||||||
await groupQuery(jid, 'set', [ { tag: 'membership_approval_mode', attrs: { }, content: [ { tag: 'group_join', attrs: { state: mode } } ] } ])
|
await groupQuery(jid, 'set', [
|
||||||
|
{ tag: 'membership_approval_mode', attrs: {}, content: [{ tag: 'group_join', attrs: { state: mode } }] }
|
||||||
|
])
|
||||||
},
|
},
|
||||||
groupFetchAllParticipating
|
groupFetchAllParticipating
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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')
|
||||||
@@ -355,14 +334,12 @@ export const extractGroupMetadata = (result: BinaryNode) => {
|
|||||||
isCommunityAnnounce: !!getBinaryNodeChild(group, 'default_sub_group'),
|
isCommunityAnnounce: !!getBinaryNodeChild(group, 'default_sub_group'),
|
||||||
joinApprovalMode: !!getBinaryNodeChild(group, 'membership_approval_mode'),
|
joinApprovalMode: !!getBinaryNodeChild(group, 'membership_approval_mode'),
|
||||||
memberAddMode,
|
memberAddMode,
|
||||||
participants: getBinaryNodeChildren(group, 'participant').map(
|
participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => {
|
||||||
({ attrs }) => {
|
|
||||||
return {
|
return {
|
||||||
id: attrs.jid,
|
id: attrs.jid,
|
||||||
admin: (attrs.type || null) as GroupParticipant['admin'],
|
admin: (attrs.type || null) as GroupParticipant['admin']
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
),
|
|
||||||
ephemeralDuration: eph ? +eph : undefined
|
ephemeralDuration: eph ? +eph : undefined
|
||||||
}
|
}
|
||||||
return metadata
|
return metadata
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ import { UserFacingSocketConfig } from '../Types'
|
|||||||
import { makeBusinessSocket } from './business'
|
import { makeBusinessSocket } from './business'
|
||||||
|
|
||||||
// export the last socket layer
|
// export the last socket layer
|
||||||
const makeWASocket = (config: UserFacingSocketConfig) => (
|
const makeWASocket = (config: UserFacingSocketConfig) =>
|
||||||
makeBusinessSocket({
|
makeBusinessSocket({
|
||||||
...DEFAULT_CONNECTION_CONFIG,
|
...DEFAULT_CONNECTION_CONFIG,
|
||||||
...config
|
...config
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
|
||||||
export default makeWASocket
|
export default makeWASocket
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
|
|
||||||
import NodeCache from '@cacheable/node-cache'
|
import NodeCache from '@cacheable/node-cache'
|
||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import Long = require('long');
|
import Long = require('long')
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults'
|
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults'
|
||||||
import { MessageReceiptType, MessageRelayOptions, MessageUserReceipt, SocketConfig, WACallEvent, WAMessageKey, WAMessageStatus, WAMessageStubType, WAPatchName } from '../Types'
|
import {
|
||||||
|
MessageReceiptType,
|
||||||
|
MessageRelayOptions,
|
||||||
|
MessageUserReceipt,
|
||||||
|
SocketConfig,
|
||||||
|
WACallEvent,
|
||||||
|
WAMessageKey,
|
||||||
|
WAMessageStatus,
|
||||||
|
WAMessageStubType,
|
||||||
|
WAPatchName
|
||||||
|
} from '../Types'
|
||||||
import {
|
import {
|
||||||
aesDecryptCTR,
|
aesDecryptCTR,
|
||||||
aesEncryptGCM,
|
aesEncryptGCM,
|
||||||
@@ -21,7 +30,8 @@ import {
|
|||||||
getCallStatusFromNode,
|
getCallStatusFromNode,
|
||||||
getHistoryMsg,
|
getHistoryMsg,
|
||||||
getNextPreKeys,
|
getNextPreKeys,
|
||||||
getStatusFromReceiptType, hkdf,
|
getStatusFromReceiptType,
|
||||||
|
hkdf,
|
||||||
MISSING_KEYS_ERROR_TEXT,
|
MISSING_KEYS_ERROR_TEXT,
|
||||||
NACK_REASONS,
|
NACK_REASONS,
|
||||||
NO_MESSAGE_FOUND_ERROR_TEXT,
|
NO_MESSAGE_FOUND_ERROR_TEXT,
|
||||||
@@ -37,7 +47,8 @@ import {
|
|||||||
getBinaryNodeChild,
|
getBinaryNodeChild,
|
||||||
getBinaryNodeChildBuffer,
|
getBinaryNodeChildBuffer,
|
||||||
getBinaryNodeChildren,
|
getBinaryNodeChildren,
|
||||||
isJidGroup, isJidStatusBroadcast,
|
isJidGroup,
|
||||||
|
isJidStatusBroadcast,
|
||||||
isJidUser,
|
isJidUser,
|
||||||
jidDecode,
|
jidDecode,
|
||||||
jidNormalizedUser,
|
jidNormalizedUser,
|
||||||
@@ -47,13 +58,7 @@ import { extractGroupMetadata } from './groups'
|
|||||||
import { makeMessagesSocket } from './messages-send'
|
import { makeMessagesSocket } from './messages-send'
|
||||||
|
|
||||||
export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||||
const {
|
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid } = config
|
||||||
logger,
|
|
||||||
retryRequestDelayMs,
|
|
||||||
maxMsgRetryCount,
|
|
||||||
getMessage,
|
|
||||||
shouldIgnoreJid
|
|
||||||
} = config
|
|
||||||
const sock = makeMessagesSocket(config)
|
const sock = makeMessagesSocket(config)
|
||||||
const {
|
const {
|
||||||
ev,
|
ev,
|
||||||
@@ -70,22 +75,28 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
relayMessage,
|
relayMessage,
|
||||||
sendReceipt,
|
sendReceipt,
|
||||||
uploadPreKeys,
|
uploadPreKeys,
|
||||||
sendPeerDataOperationMessage,
|
sendPeerDataOperationMessage
|
||||||
} = sock
|
} = sock
|
||||||
|
|
||||||
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
||||||
const retryMutex = makeMutex()
|
const retryMutex = makeMutex()
|
||||||
|
|
||||||
const msgRetryCache = config.msgRetryCounterCache || new NodeCache({
|
const msgRetryCache =
|
||||||
|
config.msgRetryCounterCache ||
|
||||||
|
new NodeCache({
|
||||||
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
||||||
useClones: false
|
useClones: false
|
||||||
})
|
})
|
||||||
const callOfferCache = config.callOfferCache || new NodeCache({
|
const callOfferCache =
|
||||||
|
config.callOfferCache ||
|
||||||
|
new NodeCache({
|
||||||
stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
||||||
useClones: false
|
useClones: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const placeholderResendCache = config.placeholderResendCache || new NodeCache({
|
const placeholderResendCache =
|
||||||
|
config.placeholderResendCache ||
|
||||||
|
new NodeCache({
|
||||||
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
||||||
useClones: false
|
useClones: false
|
||||||
})
|
})
|
||||||
@@ -114,7 +125,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
stanza.attrs.recipient = attrs.recipient
|
stanza.attrs.recipient = attrs.recipient
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!!attrs.type && (tag !== 'message' || getBinaryNodeChild({ tag, attrs, content }, 'unavailable') || errorCode !== 0)) {
|
if (
|
||||||
|
!!attrs.type &&
|
||||||
|
(tag !== 'message' || getBinaryNodeChild({ tag, attrs, content }, 'unavailable') || errorCode !== 0)
|
||||||
|
) {
|
||||||
stanza.attrs.type = attrs.type
|
stanza.attrs.type = attrs.type
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,22 +141,24 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rejectCall = async (callId: string, callFrom: string) => {
|
const rejectCall = async (callId: string, callFrom: string) => {
|
||||||
const stanza: BinaryNode = ({
|
const stanza: BinaryNode = {
|
||||||
tag: 'call',
|
tag: 'call',
|
||||||
attrs: {
|
attrs: {
|
||||||
from: authState.creds.me!.id,
|
from: authState.creds.me!.id,
|
||||||
to: callFrom,
|
to: callFrom
|
||||||
},
|
},
|
||||||
content: [{
|
content: [
|
||||||
|
{
|
||||||
tag: 'reject',
|
tag: 'reject',
|
||||||
attrs: {
|
attrs: {
|
||||||
'call-id': callId,
|
'call-id': callId,
|
||||||
'call-creator': callFrom,
|
'call-creator': callFrom,
|
||||||
count: '0',
|
count: '0'
|
||||||
},
|
},
|
||||||
content: undefined,
|
content: undefined
|
||||||
}],
|
}
|
||||||
})
|
]
|
||||||
|
}
|
||||||
await query(stanza)
|
await query(stanza)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,8 +187,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deviceIdentity = encodeSignedDeviceIdentity(account!, true)
|
const deviceIdentity = encodeSignedDeviceIdentity(account!, true)
|
||||||
await authState.keys.transaction(
|
await authState.keys.transaction(async () => {
|
||||||
async() => {
|
|
||||||
const receipt: BinaryNode = {
|
const receipt: BinaryNode = {
|
||||||
tag: 'receipt',
|
tag: 'receipt',
|
||||||
attrs: {
|
attrs: {
|
||||||
@@ -231,8 +246,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
await sendNode(receipt)
|
await sendNode(receipt)
|
||||||
|
|
||||||
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt')
|
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt')
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEncryptNotification = async (node: BinaryNode) => {
|
const handleEncryptNotification = async (node: BinaryNode) => {
|
||||||
@@ -258,11 +272,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleGroupNotification = (
|
const handleGroupNotification = (participant: string, child: BinaryNode, msg: Partial<proto.IWebMessageInfo>) => {
|
||||||
participant: string,
|
|
||||||
child: BinaryNode,
|
|
||||||
msg: Partial<proto.IWebMessageInfo>
|
|
||||||
) => {
|
|
||||||
const participantJid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || participant
|
const participantJid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || participant
|
||||||
switch (child?.tag) {
|
switch (child?.tag) {
|
||||||
case 'create':
|
case 'create':
|
||||||
@@ -272,15 +282,19 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
msg.messageStubParameters = [metadata.subject]
|
msg.messageStubParameters = [metadata.subject]
|
||||||
msg.key = { participant: metadata.owner }
|
msg.key = { participant: metadata.owner }
|
||||||
|
|
||||||
ev.emit('chats.upsert', [{
|
ev.emit('chats.upsert', [
|
||||||
|
{
|
||||||
id: metadata.id,
|
id: metadata.id,
|
||||||
name: metadata.subject,
|
name: metadata.subject,
|
||||||
conversationTimestamp: metadata.creation,
|
conversationTimestamp: metadata.creation
|
||||||
}])
|
}
|
||||||
ev.emit('groups.upsert', [{
|
])
|
||||||
|
ev.emit('groups.upsert', [
|
||||||
|
{
|
||||||
...metadata,
|
...metadata,
|
||||||
author: participant
|
author: participant
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
break
|
break
|
||||||
case 'ephemeral':
|
case 'ephemeral':
|
||||||
case 'not_ephemeral':
|
case 'not_ephemeral':
|
||||||
@@ -329,12 +343,12 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
case 'announcement':
|
case 'announcement':
|
||||||
case 'not_announcement':
|
case 'not_announcement':
|
||||||
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE
|
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE
|
||||||
msg.messageStubParameters = [ (child.tag === 'announcement') ? 'on' : 'off' ]
|
msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off']
|
||||||
break
|
break
|
||||||
case 'locked':
|
case 'locked':
|
||||||
case 'unlocked':
|
case 'unlocked':
|
||||||
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT
|
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT
|
||||||
msg.messageStubParameters = [ (child.tag === 'locked') ? 'on' : 'off' ]
|
msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off']
|
||||||
break
|
break
|
||||||
case 'invite':
|
case 'invite':
|
||||||
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK
|
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK
|
||||||
@@ -420,10 +434,12 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const setPicture = getBinaryNodeChild(node, 'set')
|
const setPicture = getBinaryNodeChild(node, 'set')
|
||||||
const delPicture = getBinaryNodeChild(node, 'delete')
|
const delPicture = getBinaryNodeChild(node, 'delete')
|
||||||
|
|
||||||
ev.emit('contacts.update', [{
|
ev.emit('contacts.update', [
|
||||||
id: jidNormalizedUser(node?.attrs?.from) || ((setPicture || delPicture)?.attrs?.hash) || '',
|
{
|
||||||
|
id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
|
||||||
imgUrl: setPicture ? 'changed' : 'removed'
|
imgUrl: setPicture ? 'changed' : 'removed'
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
|
|
||||||
if (isJidGroup(from)) {
|
if (isJidGroup(from)) {
|
||||||
const node = setPicture || delPicture
|
const node = setPicture || delPicture
|
||||||
@@ -435,7 +451,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
result.participant = node?.attrs.author
|
result.participant = node?.attrs.author
|
||||||
result.key = {
|
result.key = {
|
||||||
...result.key || {},
|
...(result.key || {}),
|
||||||
participant: setPicture?.attrs.author
|
participant: setPicture?.attrs.author
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,8 +469,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
...authState.creds.accountSettings,
|
...authState.creds.accountSettings,
|
||||||
defaultDisappearingMode: {
|
defaultDisappearingMode: {
|
||||||
ephemeralExpiration: newDuration,
|
ephemeralExpiration: newDuration,
|
||||||
ephemeralSettingTimestamp: timestamp,
|
ephemeralSettingTimestamp: timestamp
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if (child.tag === 'blocklist') {
|
} else if (child.tag === 'blocklist') {
|
||||||
@@ -462,7 +478,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
for (const { attrs } of blocklists) {
|
for (const { attrs } of blocklists) {
|
||||||
const blocklist = [attrs.jid]
|
const blocklist = [attrs.jid]
|
||||||
const type = (attrs.action === 'block') ? 'add' : 'remove'
|
const type = attrs.action === 'block' ? 'add' : 'remove'
|
||||||
ev.emit('blocklist.update', { blocklist, type })
|
ev.emit('blocklist.update', { blocklist, type })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -471,17 +487,28 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
case 'link_code_companion_reg':
|
case 'link_code_companion_reg':
|
||||||
const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg')
|
const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg')
|
||||||
const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'))
|
const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'))
|
||||||
const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'))
|
const primaryIdentityPublicKey = toRequiredBuffer(
|
||||||
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'))
|
getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub')
|
||||||
|
)
|
||||||
|
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(
|
||||||
|
getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub')
|
||||||
|
)
|
||||||
const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped)
|
const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped)
|
||||||
const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey)
|
const companionSharedKey = Curve.sharedKey(
|
||||||
|
authState.creds.pairingEphemeralKeyPair.private,
|
||||||
|
codePairingPublicKey
|
||||||
|
)
|
||||||
const random = randomBytes(32)
|
const random = randomBytes(32)
|
||||||
const linkCodeSalt = randomBytes(32)
|
const linkCodeSalt = randomBytes(32)
|
||||||
const linkCodePairingExpanded = await hkdf(companionSharedKey, 32, {
|
const linkCodePairingExpanded = await hkdf(companionSharedKey, 32, {
|
||||||
salt: linkCodeSalt,
|
salt: linkCodeSalt,
|
||||||
info: 'link_code_pairing_key_bundle_encryption_key'
|
info: 'link_code_pairing_key_bundle_encryption_key'
|
||||||
})
|
})
|
||||||
const encryptPayload = Buffer.concat([Buffer.from(authState.creds.signedIdentityKey.public), primaryIdentityPublicKey, random])
|
const encryptPayload = Buffer.concat([
|
||||||
|
Buffer.from(authState.creds.signedIdentityKey.public),
|
||||||
|
primaryIdentityPublicKey,
|
||||||
|
random
|
||||||
|
])
|
||||||
const encryptIv = randomBytes(12)
|
const encryptIv = randomBytes(12)
|
||||||
const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0))
|
const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0))
|
||||||
const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted])
|
const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted])
|
||||||
@@ -501,7 +528,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
tag: 'link_code_companion_reg',
|
tag: 'link_code_companion_reg',
|
||||||
attrs: {
|
attrs: {
|
||||||
jid: authState.creds.me!.id,
|
jid: authState.creds.me!.id,
|
||||||
stage: 'companion_finish',
|
stage: 'companion_finish'
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -561,11 +588,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
msgRetryCache.set(key, newValue)
|
msgRetryCache.set(key, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendMessagesAgain = async(
|
const sendMessagesAgain = async (key: proto.IMessageKey, ids: string[], retryNode: BinaryNode) => {
|
||||||
key: proto.IMessageKey,
|
|
||||||
ids: string[],
|
|
||||||
retryNode: BinaryNode
|
|
||||||
) => {
|
|
||||||
// todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
|
// todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
|
||||||
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 remoteJid = key.remoteJid!
|
||||||
@@ -606,7 +629,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const handleReceipt = async (node: BinaryNode) => {
|
const handleReceipt = async (node: BinaryNode) => {
|
||||||
const { attrs, content } = node
|
const { attrs, content } = node
|
||||||
const isLid = attrs.from.includes('lid')
|
const isLid = attrs.from.includes('lid')
|
||||||
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id)
|
const isNodeFromMe = areJidsSameUser(
|
||||||
|
attrs.participant || attrs.from,
|
||||||
|
isLid ? authState.creds.me?.lid : authState.creds.me?.id
|
||||||
|
)
|
||||||
const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient
|
const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient
|
||||||
const fromMe = !attrs.recipient || (attrs.type === 'retry' && isNodeFromMe)
|
const fromMe = !attrs.recipient || (attrs.type === 'retry' && isNodeFromMe)
|
||||||
|
|
||||||
@@ -631,21 +657,18 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
processingMutex.mutex(
|
processingMutex.mutex(async () => {
|
||||||
async() => {
|
|
||||||
const status = getStatusFromReceiptType(attrs.type)
|
const status = getStatusFromReceiptType(attrs.type)
|
||||||
if (
|
if (
|
||||||
typeof status !== 'undefined' &&
|
typeof status !== 'undefined' &&
|
||||||
(
|
|
||||||
// basically, we only want to know when a message from us has been delivered to/read by the other person
|
// basically, we only want to know when a message from us has been delivered to/read by the other person
|
||||||
// or another device of ours has read some messages
|
// or another device of ours has read some messages
|
||||||
status >= proto.WebMessageInfo.Status.SERVER_ACK ||
|
(status >= proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe)
|
||||||
!isNodeFromMe
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) {
|
if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) {
|
||||||
if (attrs.participant) {
|
if (attrs.participant) {
|
||||||
const updateKey: keyof MessageUserReceipt = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp'
|
const updateKey: keyof MessageUserReceipt =
|
||||||
|
status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp'
|
||||||
ev.emit(
|
ev.emit(
|
||||||
'message-receipt.update',
|
'message-receipt.update',
|
||||||
ids.map(id => ({
|
ids.map(id => ({
|
||||||
@@ -687,8 +710,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
logger.info({ attrs, key }, 'will not send message again, as sent too many times')
|
logger.info({ attrs, key }, 'will not send message again, as sent too many times')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
])
|
])
|
||||||
} finally {
|
} finally {
|
||||||
await sendMessageAck(node)
|
await sendMessageAck(node)
|
||||||
@@ -705,8 +727,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
processingMutex.mutex(
|
processingMutex.mutex(async () => {
|
||||||
async() => {
|
|
||||||
const msg = await processNotification(node)
|
const msg = await processNotification(node)
|
||||||
if (msg) {
|
if (msg) {
|
||||||
const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me!.id)
|
const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me!.id)
|
||||||
@@ -723,8 +744,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const fullMsg = proto.WebMessageInfo.fromObject(msg)
|
const fullMsg = proto.WebMessageInfo.fromObject(msg)
|
||||||
await upsertMessage(fullMsg, 'append')
|
await upsertMessage(fullMsg, 'append')
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
])
|
])
|
||||||
} finally {
|
} finally {
|
||||||
await sendMessageAck(node)
|
await sendMessageAck(node)
|
||||||
@@ -764,27 +784,27 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(
|
fullMessage: msg,
|
||||||
node,
|
category,
|
||||||
authState.creds.me!.id,
|
author,
|
||||||
authState.creds.me!.lid || '',
|
decrypt
|
||||||
signalRepository,
|
} = decryptMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '', signalRepository, logger)
|
||||||
logger,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (response && msg?.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
|
if (response && msg?.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
|
||||||
msg.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT, response]
|
msg.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT, response]
|
||||||
}
|
}
|
||||||
|
|
||||||
if(msg.message?.protocolMessage?.type === proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER && node.attrs.sender_pn) {
|
if (
|
||||||
|
msg.message?.protocolMessage?.type === proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER &&
|
||||||
|
node.attrs.sender_pn
|
||||||
|
) {
|
||||||
ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn })
|
ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn })
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
processingMutex.mutex(
|
processingMutex.mutex(async () => {
|
||||||
async() => {
|
|
||||||
await decrypt()
|
await decrypt()
|
||||||
// message failed to decrypt
|
// message failed to decrypt
|
||||||
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) {
|
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) {
|
||||||
@@ -792,8 +812,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
return sendMessageAck(node, NACK_REASONS.ParsingError)
|
return sendMessageAck(node, NACK_REASONS.ParsingError)
|
||||||
}
|
}
|
||||||
|
|
||||||
retryMutex.mutex(
|
retryMutex.mutex(async () => {
|
||||||
async() => {
|
|
||||||
if (ws.isOpen) {
|
if (ws.isOpen) {
|
||||||
if (getBinaryNodeChild(node, 'unavailable')) {
|
if (getBinaryNodeChild(node, 'unavailable')) {
|
||||||
return
|
return
|
||||||
@@ -807,15 +826,16 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
} else {
|
} else {
|
||||||
logger.debug({ node }, 'connection closed, ignoring retry req')
|
logger.debug({ node }, 'connection closed, ignoring retry req')
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
// no type in the receipt => message delivered
|
// no type in the receipt => message delivered
|
||||||
let type: MessageReceiptType = undefined
|
let type: MessageReceiptType = undefined
|
||||||
let participant = msg.key.participant
|
let participant = msg.key.participant
|
||||||
if(category === 'peer') { // special peer message
|
if (category === 'peer') {
|
||||||
|
// special peer message
|
||||||
type = 'peer_msg'
|
type = 'peer_msg'
|
||||||
} 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!)) {
|
||||||
@@ -840,8 +860,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
await sendMessageAck(node)
|
await sendMessageAck(node)
|
||||||
|
|
||||||
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify')
|
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify')
|
||||||
}
|
})
|
||||||
)
|
|
||||||
])
|
])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ error, node }, 'error in handling message')
|
logger.error({ error, node }, 'error in handling message')
|
||||||
@@ -891,9 +910,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pdoMessage = {
|
const pdoMessage = {
|
||||||
placeholderMessageResendRequest: [{
|
placeholderMessageResendRequest: [
|
||||||
|
{
|
||||||
messageKey
|
messageKey
|
||||||
}],
|
}
|
||||||
|
],
|
||||||
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -919,7 +940,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
id: callId,
|
id: callId,
|
||||||
date: new Date(+attrs.t * 1000),
|
date: new Date(+attrs.t * 1000),
|
||||||
offline: !!attrs.offline,
|
offline: !!attrs.offline,
|
||||||
status,
|
status
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'offer') {
|
if (status === 'offer') {
|
||||||
@@ -968,20 +989,15 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
// device could not display the message
|
// device could not display the message
|
||||||
if (attrs.error) {
|
if (attrs.error) {
|
||||||
logger.warn({ attrs }, 'received error in ack')
|
logger.warn({ attrs }, 'received error in ack')
|
||||||
ev.emit(
|
ev.emit('messages.update', [
|
||||||
'messages.update',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
key,
|
key,
|
||||||
update: {
|
update: {
|
||||||
status: WAMessageStatus.ERROR,
|
status: WAMessageStatus.ERROR,
|
||||||
messageStubParameters: [
|
messageStubParameters: [attrs.error]
|
||||||
attrs.error
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,8 +1013,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
ev.flush()
|
ev.flush()
|
||||||
|
|
||||||
function execTask() {
|
function execTask() {
|
||||||
return exec(node, false)
|
return exec(node, false).catch(err => onUnexpectedError(err, identifier))
|
||||||
.catch(err => onUnexpectedError(err, identifier))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1035,10 +1050,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const nodeProcessor = nodeProcessorMap.get(type)
|
const nodeProcessor = nodeProcessorMap.get(type)
|
||||||
|
|
||||||
if (!nodeProcessor) {
|
if (!nodeProcessor) {
|
||||||
onUnexpectedError(
|
onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node')
|
||||||
new Error(`unknown offline node type: ${type}`),
|
|
||||||
'processing offline node'
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1056,7 +1068,12 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
const offlineNodeProcessor = makeOfflineNodeProcessor()
|
const offlineNodeProcessor = makeOfflineNodeProcessor()
|
||||||
|
|
||||||
const processNode = (type: MessageType, node: BinaryNode, identifier: string, exec: (node: BinaryNode) => Promise<void>) => {
|
const processNode = (
|
||||||
|
type: MessageType,
|
||||||
|
node: BinaryNode,
|
||||||
|
identifier: string,
|
||||||
|
exec: (node: BinaryNode) => Promise<void>
|
||||||
|
) => {
|
||||||
const isOffline = !!node.attrs.offline
|
const isOffline = !!node.attrs.offline
|
||||||
|
|
||||||
if (isOffline) {
|
if (isOffline) {
|
||||||
@@ -1083,8 +1100,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
processNode('notification', node, 'handling notification', handleNotification)
|
processNode('notification', node, 'handling notification', handleNotification)
|
||||||
})
|
})
|
||||||
ws.on('CB:ack,class:message', (node: BinaryNode) => {
|
ws.on('CB:ack,class:message', (node: BinaryNode) => {
|
||||||
handleBadAck(node)
|
handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'))
|
||||||
.catch(error => onUnexpectedError(error, 'handling bad ack'))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ev.on('call', ([call]) => {
|
ev.on('call', ([call]) => {
|
||||||
@@ -1096,11 +1112,13 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
id: call.id,
|
id: call.id,
|
||||||
fromMe: false
|
fromMe: false
|
||||||
},
|
},
|
||||||
messageTimestamp: unixTimestampSeconds(call.date),
|
messageTimestamp: unixTimestampSeconds(call.date)
|
||||||
}
|
}
|
||||||
if (call.status === 'timeout') {
|
if (call.status === 'timeout') {
|
||||||
if (call.isGroup) {
|
if (call.isGroup) {
|
||||||
msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_GROUP_VIDEO : WAMessageStubType.CALL_MISSED_GROUP_VOICE
|
msg.messageStubType = call.isVideo
|
||||||
|
? WAMessageStubType.CALL_MISSED_GROUP_VIDEO
|
||||||
|
: WAMessageStubType.CALL_MISSED_GROUP_VOICE
|
||||||
} else {
|
} else {
|
||||||
msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE
|
msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE
|
||||||
}
|
}
|
||||||
@@ -1126,6 +1144,6 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
sendRetryRequest,
|
sendRetryRequest,
|
||||||
rejectCall,
|
rejectCall,
|
||||||
fetchMessageHistory,
|
fetchMessageHistory,
|
||||||
requestPlaceholderResend,
|
requestPlaceholderResend
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,49 @@
|
|||||||
|
|
||||||
import NodeCache from '@cacheable/node-cache'
|
import NodeCache from '@cacheable/node-cache'
|
||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults'
|
import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults'
|
||||||
import { AnyMessageContent, MediaConnInfo, MessageReceiptType, MessageRelayOptions, MiscMessageGenerationOptions, SocketConfig, WAMessageKey } from '../Types'
|
import {
|
||||||
import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils'
|
AnyMessageContent,
|
||||||
|
MediaConnInfo,
|
||||||
|
MessageReceiptType,
|
||||||
|
MessageRelayOptions,
|
||||||
|
MiscMessageGenerationOptions,
|
||||||
|
SocketConfig,
|
||||||
|
WAMessageKey
|
||||||
|
} from '../Types'
|
||||||
|
import {
|
||||||
|
aggregateMessageKeysNotFromMe,
|
||||||
|
assertMediaContent,
|
||||||
|
bindWaitForEvent,
|
||||||
|
decryptMediaRetryData,
|
||||||
|
encodeSignedDeviceIdentity,
|
||||||
|
encodeWAMessage,
|
||||||
|
encryptMediaRetryRequest,
|
||||||
|
extractDeviceJids,
|
||||||
|
generateMessageIDV2,
|
||||||
|
generateWAMessage,
|
||||||
|
getStatusCodeForMediaRetry,
|
||||||
|
getUrlFromDirectPath,
|
||||||
|
getWAUploadToServer,
|
||||||
|
normalizeMessageContent,
|
||||||
|
parseAndInjectE2ESessions,
|
||||||
|
unixTimestampSeconds
|
||||||
|
} from '../Utils'
|
||||||
import { getUrlInfo } from '../Utils/link-preview'
|
import { getUrlInfo } from '../Utils/link-preview'
|
||||||
import { areJidsSameUser, BinaryNode, BinaryNodeAttributes, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidUser, jidDecode, jidEncode, jidNormalizedUser, JidWithDevice, S_WHATSAPP_NET } from '../WABinary'
|
import {
|
||||||
|
areJidsSameUser,
|
||||||
|
BinaryNode,
|
||||||
|
BinaryNodeAttributes,
|
||||||
|
getBinaryNodeChild,
|
||||||
|
getBinaryNodeChildren,
|
||||||
|
isJidGroup,
|
||||||
|
isJidUser,
|
||||||
|
jidDecode,
|
||||||
|
jidEncode,
|
||||||
|
jidNormalizedUser,
|
||||||
|
JidWithDevice,
|
||||||
|
S_WHATSAPP_NET
|
||||||
|
} from '../WABinary'
|
||||||
import { USyncQuery, USyncUser } from '../WAUSync'
|
import { USyncQuery, USyncUser } from '../WAUSync'
|
||||||
import { makeGroupsSocket } from './groups'
|
import { makeGroupsSocket } from './groups'
|
||||||
|
|
||||||
@@ -17,7 +54,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
generateHighQualityLinkPreview,
|
generateHighQualityLinkPreview,
|
||||||
options: axiosOptions,
|
options: axiosOptions,
|
||||||
patchMessageBeforeSending,
|
patchMessageBeforeSending,
|
||||||
cachedGroupMetadata,
|
cachedGroupMetadata
|
||||||
} = config
|
} = config
|
||||||
const sock = makeGroupsSocket(config)
|
const sock = makeGroupsSocket(config)
|
||||||
const {
|
const {
|
||||||
@@ -30,10 +67,12 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
fetchPrivacySettings,
|
fetchPrivacySettings,
|
||||||
sendNode,
|
sendNode,
|
||||||
groupMetadata,
|
groupMetadata,
|
||||||
groupToggleEphemeral,
|
groupToggleEphemeral
|
||||||
} = sock
|
} = sock
|
||||||
|
|
||||||
const userDevicesCache = config.userDevicesCache || new NodeCache({
|
const userDevicesCache =
|
||||||
|
config.userDevicesCache ||
|
||||||
|
new NodeCache({
|
||||||
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
||||||
useClones: false
|
useClones: false
|
||||||
})
|
})
|
||||||
@@ -41,25 +80,23 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
let mediaConn: Promise<MediaConnInfo>
|
let mediaConn: Promise<MediaConnInfo>
|
||||||
const refreshMediaConn = async (forceGet = false) => {
|
const refreshMediaConn = async (forceGet = false) => {
|
||||||
const media = await mediaConn
|
const media = await mediaConn
|
||||||
if(!media || forceGet || (new Date().getTime() - media.fetchDate.getTime()) > media.ttl * 1000) {
|
if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
|
||||||
mediaConn = (async () => {
|
mediaConn = (async () => {
|
||||||
const result = await query({
|
const result = await query({
|
||||||
tag: 'iq',
|
tag: 'iq',
|
||||||
attrs: {
|
attrs: {
|
||||||
type: 'set',
|
type: 'set',
|
||||||
xmlns: 'w:m',
|
xmlns: 'w:m',
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET
|
||||||
},
|
},
|
||||||
content: [{ tag: 'media_conn', attrs: {} }]
|
content: [{ tag: 'media_conn', attrs: {} }]
|
||||||
})
|
})
|
||||||
const mediaConnNode = getBinaryNodeChild(result, 'media_conn')
|
const mediaConnNode = getBinaryNodeChild(result, 'media_conn')
|
||||||
const node: MediaConnInfo = {
|
const node: MediaConnInfo = {
|
||||||
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(
|
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
|
||||||
({ attrs }) => ({
|
|
||||||
hostname: attrs.hostname,
|
hostname: attrs.hostname,
|
||||||
maxContentLengthBytes: +attrs.maxContentLengthBytes,
|
maxContentLengthBytes: +attrs.maxContentLengthBytes
|
||||||
})
|
})),
|
||||||
),
|
|
||||||
auth: mediaConnNode!.attrs.auth,
|
auth: mediaConnNode!.attrs.auth,
|
||||||
ttl: +mediaConnNode!.attrs.ttl,
|
ttl: +mediaConnNode!.attrs.ttl,
|
||||||
fetchDate: new Date()
|
fetchDate: new Date()
|
||||||
@@ -76,12 +113,17 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
* generic send receipt function
|
* generic send receipt function
|
||||||
* used for receipts of phone call, read, delivery etc.
|
* used for receipts of phone call, read, delivery etc.
|
||||||
* */
|
* */
|
||||||
const sendReceipt = async(jid: string, participant: string | undefined, messageIds: string[], type: MessageReceiptType) => {
|
const sendReceipt = async (
|
||||||
|
jid: string,
|
||||||
|
participant: string | undefined,
|
||||||
|
messageIds: string[],
|
||||||
|
type: MessageReceiptType
|
||||||
|
) => {
|
||||||
const node: BinaryNode = {
|
const node: BinaryNode = {
|
||||||
tag: 'receipt',
|
tag: 'receipt',
|
||||||
attrs: {
|
attrs: {
|
||||||
id: messageIds[0],
|
id: messageIds[0]
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
const isReadReceipt = type === 'read' || type === 'read-self'
|
const isReadReceipt = type === 'read' || type === 'read-self'
|
||||||
if (isReadReceipt) {
|
if (isReadReceipt) {
|
||||||
@@ -168,9 +210,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
return deviceResults
|
return deviceResults
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = new USyncQuery()
|
const query = new USyncQuery().withContext('message').withDeviceProtocol()
|
||||||
.withContext('message')
|
|
||||||
.withDeviceProtocol()
|
|
||||||
|
|
||||||
for (const jid of toFetch) {
|
for (const jid of toFetch) {
|
||||||
query.withUser(new USyncUser().withId(jid))
|
query.withUser(new USyncUser().withId(jid))
|
||||||
@@ -203,14 +243,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
if (force) {
|
if (force) {
|
||||||
jidsRequiringFetch = jids
|
jidsRequiringFetch = jids
|
||||||
} else {
|
} else {
|
||||||
const addrs = jids.map(jid => (
|
const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid))
|
||||||
signalRepository
|
|
||||||
.jidToSignalProtocolAddress(jid)
|
|
||||||
))
|
|
||||||
const sessions = await authState.keys.get('session', addrs)
|
const sessions = await authState.keys.get('session', addrs)
|
||||||
for (const jid of jids) {
|
for (const jid of jids) {
|
||||||
const signalId = signalRepository
|
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
|
||||||
.jidToSignalProtocolAddress(jid)
|
|
||||||
if (!sessions[signalId]) {
|
if (!sessions[signalId]) {
|
||||||
jidsRequiringFetch.push(jid)
|
jidsRequiringFetch.push(jid)
|
||||||
}
|
}
|
||||||
@@ -224,18 +260,16 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
attrs: {
|
attrs: {
|
||||||
xmlns: 'encrypt',
|
xmlns: 'encrypt',
|
||||||
type: 'get',
|
type: 'get',
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
tag: 'key',
|
tag: 'key',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: jidsRequiringFetch.map(
|
content: jidsRequiringFetch.map(jid => ({
|
||||||
jid => ({
|
|
||||||
tag: 'user',
|
tag: 'user',
|
||||||
attrs: { jid },
|
attrs: { jid }
|
||||||
})
|
}))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -268,18 +302,14 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
additionalAttributes: {
|
additionalAttributes: {
|
||||||
category: 'peer',
|
category: 'peer',
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
push_priority: 'high_force',
|
push_priority: 'high_force'
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return msgId
|
return msgId
|
||||||
}
|
}
|
||||||
|
|
||||||
const createParticipantNodes = async(
|
const createParticipantNodes = async (jids: string[], message: proto.IMessage, extraAttrs?: BinaryNode['attrs']) => {
|
||||||
jids: string[],
|
|
||||||
message: proto.IMessage,
|
|
||||||
extraAttrs?: BinaryNode['attrs']
|
|
||||||
) => {
|
|
||||||
let patched = await patchMessageBeforeSending(message, jids)
|
let patched = await patchMessageBeforeSending(message, jids)
|
||||||
if (!Array.isArray(patched)) {
|
if (!Array.isArray(patched)) {
|
||||||
patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched]
|
patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched]
|
||||||
@@ -288,16 +318,14 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
let shouldIncludeDeviceIdentity = false
|
let shouldIncludeDeviceIdentity = false
|
||||||
|
|
||||||
const nodes = await Promise.all(
|
const nodes = await Promise.all(
|
||||||
patched.map(
|
patched.map(async patchedMessageWithJid => {
|
||||||
async patchedMessageWithJid => {
|
|
||||||
const { recipientJid: jid, ...patchedMessage } = patchedMessageWithJid
|
const { recipientJid: jid, ...patchedMessage } = patchedMessageWithJid
|
||||||
if (!jid) {
|
if (!jid) {
|
||||||
return {} as BinaryNode
|
return {} as BinaryNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes = encodeWAMessage(patchedMessage)
|
const bytes = encodeWAMessage(patchedMessage)
|
||||||
const { type, ciphertext } = await signalRepository
|
const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes })
|
||||||
.encryptMessage({ jid, data: bytes })
|
|
||||||
if (type === 'pkmsg') {
|
if (type === 'pkmsg') {
|
||||||
shouldIncludeDeviceIdentity = true
|
shouldIncludeDeviceIdentity = true
|
||||||
}
|
}
|
||||||
@@ -305,19 +333,20 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const node: BinaryNode = {
|
const node: BinaryNode = {
|
||||||
tag: 'to',
|
tag: 'to',
|
||||||
attrs: { jid },
|
attrs: { jid },
|
||||||
content: [{
|
content: [
|
||||||
|
{
|
||||||
tag: 'enc',
|
tag: 'enc',
|
||||||
attrs: {
|
attrs: {
|
||||||
v: '2',
|
v: '2',
|
||||||
type,
|
type,
|
||||||
...extraAttrs || {}
|
...(extraAttrs || {})
|
||||||
},
|
},
|
||||||
content: ciphertext
|
content: ciphertext
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
return node
|
return node
|
||||||
}
|
})
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return { nodes, shouldIncludeDeviceIdentity }
|
return { nodes, shouldIncludeDeviceIdentity }
|
||||||
}
|
}
|
||||||
@@ -325,7 +354,15 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const relayMessage = async (
|
const relayMessage = async (
|
||||||
jid: string,
|
jid: string,
|
||||||
message: proto.IMessage,
|
message: proto.IMessage,
|
||||||
{ messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }: MessageRelayOptions
|
{
|
||||||
|
messageId: msgId,
|
||||||
|
participant,
|
||||||
|
additionalAttributes,
|
||||||
|
additionalNodes,
|
||||||
|
useUserDevicesCache,
|
||||||
|
useCachedGroupMetadata,
|
||||||
|
statusJidList
|
||||||
|
}: MessageRelayOptions
|
||||||
) => {
|
) => {
|
||||||
const meId = authState.creds.me!.id
|
const meId = authState.creds.me!.id
|
||||||
|
|
||||||
@@ -342,7 +379,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus
|
useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus
|
||||||
|
|
||||||
const participants: BinaryNode[] = []
|
const participants: BinaryNode[] = []
|
||||||
const destinationJid = (!isStatus) ? jidEncode(user, isLid ? 'lid' : isGroup ? 'g.us' : 's.whatsapp.net') : statusJid
|
const destinationJid = !isStatus ? jidEncode(user, isLid ? 'lid' : isGroup ? 'g.us' : 's.whatsapp.net') : statusJid
|
||||||
const binaryNodeContent: BinaryNode[] = []
|
const binaryNodeContent: BinaryNode[] = []
|
||||||
const devices: JidWithDevice[] = []
|
const devices: JidWithDevice[] = []
|
||||||
|
|
||||||
@@ -360,15 +397,14 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
// only send to the specific device that asked for a retry
|
// only send to the specific device that asked for a retry
|
||||||
// otherwise the message is sent out to every device that should be a recipient
|
// otherwise the message is sent out to every device that should be a recipient
|
||||||
if (!isGroup && !isStatus) {
|
if (!isGroup && !isStatus) {
|
||||||
additionalAttributes = { ...additionalAttributes, 'device_fanout': 'false' }
|
additionalAttributes = { ...additionalAttributes, device_fanout: 'false' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { user, device } = jidDecode(participant.jid)!
|
const { user, device } = jidDecode(participant.jid)!
|
||||||
devices.push({ user, device })
|
devices.push({ user, device })
|
||||||
}
|
}
|
||||||
|
|
||||||
await authState.keys.transaction(
|
await authState.keys.transaction(async () => {
|
||||||
async() => {
|
|
||||||
const mediaType = getMediaType(message)
|
const mediaType = getMediaType(message)
|
||||||
if (mediaType) {
|
if (mediaType) {
|
||||||
extraAttrs['mediatype'] = mediaType
|
extraAttrs['mediatype'] = mediaType
|
||||||
@@ -401,7 +437,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
if (!participant) {
|
if (!participant) {
|
||||||
const participantsList = (groupData && !isStatus) ? groupData.participants.map(p => p.id) : []
|
const participantsList = groupData && !isStatus ? groupData.participants.map(p => p.id) : []
|
||||||
if (isStatus && statusJidList) {
|
if (isStatus && statusJidList) {
|
||||||
participantsList.push(...statusJidList)
|
participantsList.push(...statusJidList)
|
||||||
}
|
}
|
||||||
@@ -425,13 +461,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
const bytes = encodeWAMessage(patched)
|
const bytes = encodeWAMessage(patched)
|
||||||
|
|
||||||
const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage(
|
const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
|
||||||
{
|
|
||||||
group: destinationJid,
|
group: destinationJid,
|
||||||
data: bytes,
|
data: bytes,
|
||||||
meId,
|
meId
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const senderKeyJids: string[] = []
|
const senderKeyJids: string[] = []
|
||||||
// ensure a connection is established with every device
|
// ensure a connection is established with every device
|
||||||
@@ -491,7 +525,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const otherJids: string[] = []
|
const otherJids: string[] = []
|
||||||
for (const { user, device } of devices) {
|
for (const { user, device } of devices) {
|
||||||
const isMe = user === meUser
|
const isMe = user === meUser
|
||||||
const jid = jidEncode(isMe && isLid ? authState.creds?.me?.lid!.split(':')[0] || user : user, isLid ? 'lid' : 's.whatsapp.net', device)
|
const jid = jidEncode(
|
||||||
|
isMe && isLid ? authState.creds?.me?.lid!.split(':')[0] || user : user,
|
||||||
|
isLid ? 'lid' : 's.whatsapp.net',
|
||||||
|
device
|
||||||
|
)
|
||||||
if (isMe) {
|
if (isMe) {
|
||||||
meJids.push(jid)
|
meJids.push(jid)
|
||||||
} else {
|
} else {
|
||||||
@@ -558,7 +596,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldIncludeDeviceIdentity) {
|
if (shouldIncludeDeviceIdentity) {
|
||||||
(stanza.content as BinaryNode[]).push({
|
;(stanza.content as BinaryNode[]).push({
|
||||||
tag: 'device-identity',
|
tag: 'device-identity',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: encodeSignedDeviceIdentity(authState.creds.account!, true)
|
content: encodeSignedDeviceIdentity(authState.creds.account!, true)
|
||||||
@@ -568,19 +606,17 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (additionalNodes && additionalNodes.length > 0) {
|
if (additionalNodes && additionalNodes.length > 0) {
|
||||||
(stanza.content as BinaryNode[]).push(...additionalNodes)
|
;(stanza.content as BinaryNode[]).push(...additionalNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug({ msgId }, `sending message to ${participants.length} devices`)
|
logger.debug({ msgId }, `sending message to ${participants.length} devices`)
|
||||||
|
|
||||||
await sendNode(stanza)
|
await sendNode(stanza)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return msgId
|
return msgId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const getMessageType = (message: proto.IMessage) => {
|
const getMessageType = (message: proto.IMessage) => {
|
||||||
if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) {
|
if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) {
|
||||||
return 'poll'
|
return 'poll'
|
||||||
@@ -636,16 +672,14 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
{
|
{
|
||||||
tag: 'tokens',
|
tag: 'tokens',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: jids.map(
|
content: jids.map(jid => ({
|
||||||
jid => ({
|
|
||||||
tag: 'token',
|
tag: 'token',
|
||||||
attrs: {
|
attrs: {
|
||||||
jid: jidNormalizedUser(jid),
|
jid: jidNormalizedUser(jid),
|
||||||
t,
|
t,
|
||||||
type: 'trusted_contact'
|
type: 'trusted_contact'
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -678,10 +712,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const node = await encryptMediaRetryRequest(message.key, mediaKey, meId)
|
const node = await encryptMediaRetryRequest(message.key, mediaKey, meId)
|
||||||
|
|
||||||
let error: Error | undefined = undefined
|
let error: Error | undefined = undefined
|
||||||
await Promise.all(
|
await Promise.all([
|
||||||
[
|
|
||||||
sendNode(node),
|
sendNode(node),
|
||||||
waitForMsgMediaUpdate(async(update) => {
|
waitForMsgMediaUpdate(async update => {
|
||||||
const result = update.find(c => c.key.id === message.key.id)
|
const result = update.find(c => c.key.id === message.key.id)
|
||||||
if (result) {
|
if (result) {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
@@ -691,10 +724,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const media = await decryptMediaRetryData(result.media!, mediaKey, result.key.id!)
|
const media = await decryptMediaRetryData(result.media!, mediaKey, result.key.id!)
|
||||||
if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
|
if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
|
||||||
const resultStr = proto.MediaRetryNotification.ResultType[media.result!]
|
const resultStr = proto.MediaRetryNotification.ResultType[media.result!]
|
||||||
throw new Boom(
|
throw new Boom(`Media re-upload failed by device (${resultStr})`, {
|
||||||
`Media re-upload failed by device (${resultStr})`,
|
data: media,
|
||||||
{ data: media, statusCode: getStatusCodeForMediaRetry(media.result!) || 404 }
|
statusCode: getStatusCodeForMediaRetry(media.result!) || 404
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
content.directPath = media.directPath
|
content.directPath = media.directPath
|
||||||
@@ -709,24 +742,17 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]
|
])
|
||||||
)
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.emit('messages.update', [
|
ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }])
|
||||||
{ key: message.key, update: { message: message.message } }
|
|
||||||
])
|
|
||||||
|
|
||||||
return message
|
return message
|
||||||
},
|
},
|
||||||
sendMessage: async(
|
sendMessage: async (jid: string, content: AnyMessageContent, options: MiscMessageGenerationOptions = {}) => {
|
||||||
jid: string,
|
|
||||||
content: AnyMessageContent,
|
|
||||||
options: MiscMessageGenerationOptions = { }
|
|
||||||
) => {
|
|
||||||
const userJid = authState.creds.me!.id
|
const userJid = authState.creds.me!.id
|
||||||
if (
|
if (
|
||||||
typeof content === 'object' &&
|
typeof content === 'object' &&
|
||||||
@@ -735,40 +761,35 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
isJidGroup(jid)
|
isJidGroup(jid)
|
||||||
) {
|
) {
|
||||||
const { disappearingMessagesInChat } = content
|
const { disappearingMessagesInChat } = content
|
||||||
const value = typeof disappearingMessagesInChat === 'boolean' ?
|
const value =
|
||||||
(disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
|
typeof disappearingMessagesInChat === 'boolean'
|
||||||
disappearingMessagesInChat
|
? disappearingMessagesInChat
|
||||||
|
? WA_DEFAULT_EPHEMERAL
|
||||||
|
: 0
|
||||||
|
: disappearingMessagesInChat
|
||||||
await groupToggleEphemeral(jid, value)
|
await groupToggleEphemeral(jid, value)
|
||||||
} else {
|
} else {
|
||||||
const fullMsg = await generateWAMessage(
|
const fullMsg = await generateWAMessage(jid, content, {
|
||||||
jid,
|
|
||||||
content,
|
|
||||||
{
|
|
||||||
logger,
|
logger,
|
||||||
userJid,
|
userJid,
|
||||||
getUrlInfo: text => getUrlInfo(
|
getUrlInfo: text =>
|
||||||
text,
|
getUrlInfo(text, {
|
||||||
{
|
|
||||||
thumbnailWidth: linkPreviewImageThumbnailWidth,
|
thumbnailWidth: linkPreviewImageThumbnailWidth,
|
||||||
fetchOpts: {
|
fetchOpts: {
|
||||||
timeout: 3_000,
|
timeout: 3_000,
|
||||||
...axiosOptions || { }
|
...(axiosOptions || {})
|
||||||
},
|
},
|
||||||
logger,
|
logger,
|
||||||
uploadImage: generateHighQualityLinkPreview
|
uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
|
||||||
? waUploadToServer
|
}),
|
||||||
: undefined
|
|
||||||
},
|
|
||||||
),
|
|
||||||
//TODO: CACHE
|
//TODO: CACHE
|
||||||
getProfilePicUrl: sock.profilePictureUrl,
|
getProfilePicUrl: sock.profilePictureUrl,
|
||||||
upload: waUploadToServer,
|
upload: waUploadToServer,
|
||||||
mediaCache: config.mediaCache,
|
mediaCache: config.mediaCache,
|
||||||
options: config.options,
|
options: config.options,
|
||||||
messageId: generateMessageIDV2(sock.user?.id),
|
messageId: generateMessageIDV2(sock.user?.id),
|
||||||
...options,
|
...options
|
||||||
}
|
})
|
||||||
)
|
|
||||||
const isDeleteMsg = 'delete' in content && !!content.delete
|
const isDeleteMsg = 'delete' in content && !!content.delete
|
||||||
const isEditMsg = 'edit' in content && !!content.edit
|
const isEditMsg = 'edit' in content && !!content.edit
|
||||||
const isPinMsg = 'pin' in content && !!content.pin
|
const isPinMsg = 'pin' in content && !!content.pin
|
||||||
@@ -792,20 +813,26 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
tag: 'meta',
|
tag: 'meta',
|
||||||
attrs: {
|
attrs: {
|
||||||
polltype: 'creation'
|
polltype: 'creation'
|
||||||
},
|
}
|
||||||
} as BinaryNode)
|
} as BinaryNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('cachedGroupMetadata' in options) {
|
if ('cachedGroupMetadata' in options) {
|
||||||
console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.')
|
console.warn(
|
||||||
|
'cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await relayMessage(jid, fullMsg.message!, { messageId: fullMsg.key.id!, useCachedGroupMetadata: options.useCachedGroupMetadata, additionalAttributes, statusJidList: options.statusJidList, additionalNodes })
|
await relayMessage(jid, fullMsg.message!, {
|
||||||
|
messageId: fullMsg.key.id!,
|
||||||
|
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
||||||
|
additionalAttributes,
|
||||||
|
statusJidList: options.statusJidList,
|
||||||
|
additionalNodes
|
||||||
|
})
|
||||||
if (config.emitOwnEvents) {
|
if (config.emitOwnEvents) {
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
processingMutex.mutex(() => (
|
processingMutex.mutex(() => upsertMessage(fullMsg, 'append'))
|
||||||
upsertMessage(fullMsg, 'append')
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
getPlatformId,
|
getPlatformId,
|
||||||
makeEventBuffer,
|
makeEventBuffer,
|
||||||
makeNoiseHandler,
|
makeNoiseHandler,
|
||||||
promiseTimeout,
|
promiseTimeout
|
||||||
} from '../Utils'
|
} from '../Utils'
|
||||||
import {
|
import {
|
||||||
assertNodeErrorFree,
|
assertNodeErrorFree,
|
||||||
@@ -61,16 +61,17 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
defaultQueryTimeoutMs,
|
defaultQueryTimeoutMs,
|
||||||
transactionOpts,
|
transactionOpts,
|
||||||
qrTimeout,
|
qrTimeout,
|
||||||
makeSignalRepository,
|
makeSignalRepository
|
||||||
} = config
|
} = config
|
||||||
|
|
||||||
if (printQRInTerminal) {
|
if (printQRInTerminal) {
|
||||||
console.warn('⚠️ The printQRInTerminal option has been deprecated. You will no longer receive QR codes in the terminal automatically. Please listen to the connection.update event yourself and handle the QR your way. You can remove this message by removing this opttion. This message will be removed in a future version.')
|
console.warn(
|
||||||
|
'⚠️ The printQRInTerminal option has been deprecated. You will no longer receive QR codes in the terminal automatically. Please listen to the connection.update event yourself and handle the QR your way. You can remove this message by removing this opttion. This message will be removed in a future version.'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl
|
const url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl
|
||||||
|
|
||||||
|
|
||||||
if (config.mobile || url.protocol === 'tcp:') {
|
if (config.mobile || url.protocol === 'tcp:') {
|
||||||
throw new Boom('Mobile API is not supported anymore', { statusCode: DisconnectReason.loggedOut })
|
throw new Boom('Mobile API is not supported anymore', { statusCode: DisconnectReason.loggedOut })
|
||||||
}
|
}
|
||||||
@@ -116,17 +117,14 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bytes = noise.encodeFrame(data)
|
const bytes = noise.encodeFrame(data)
|
||||||
await promiseTimeout<void>(
|
await promiseTimeout<void>(connectTimeoutMs, async (resolve, reject) => {
|
||||||
connectTimeoutMs,
|
|
||||||
async(resolve, reject) => {
|
|
||||||
try {
|
try {
|
||||||
await sendPromise.call(ws, bytes)
|
await sendPromise.call(ws, bytes)
|
||||||
resolve()
|
resolve()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** send a binary node */
|
/** send a binary node */
|
||||||
@@ -141,10 +139,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
/** log & process any unexpected errors */
|
/** log & process any unexpected errors */
|
||||||
const onUnexpectedError = (err: Error | Boom, msg: string) => {
|
const onUnexpectedError = (err: Error | Boom, msg: string) => {
|
||||||
logger.error(
|
logger.error({ err }, `unexpected error in '${msg}'`)
|
||||||
{ err },
|
|
||||||
`unexpected error in '${msg}'`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** await the next incoming message */
|
/** await the next incoming message */
|
||||||
@@ -164,8 +159,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
ws.on('frame', onOpen)
|
ws.on('frame', onOpen)
|
||||||
ws.on('close', onClose)
|
ws.on('close', onClose)
|
||||||
ws.on('error', onClose)
|
ws.on('error', onClose)
|
||||||
})
|
}).finally(() => {
|
||||||
.finally(() => {
|
|
||||||
ws.off('frame', onOpen)
|
ws.off('frame', onOpen)
|
||||||
ws.off('close', onClose)
|
ws.off('close', onClose)
|
||||||
ws.off('error', onClose)
|
ws.off('error', onClose)
|
||||||
@@ -187,8 +181,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
let onRecv: (json) => void
|
let onRecv: (json) => void
|
||||||
let onErr: (err) => void
|
let onErr: (err) => void
|
||||||
try {
|
try {
|
||||||
const result = await promiseTimeout<T>(timeoutMs,
|
const result = await promiseTimeout<T>(timeoutMs, (resolve, reject) => {
|
||||||
(resolve, reject) => {
|
|
||||||
onRecv = resolve
|
onRecv = resolve
|
||||||
onErr = err => {
|
onErr = err => {
|
||||||
reject(err || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }))
|
reject(err || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }))
|
||||||
@@ -197,8 +190,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
ws.on(`TAG:${msgId}`, onRecv)
|
ws.on(`TAG:${msgId}`, onRecv)
|
||||||
ws.on('close', onErr) // if the socket closes, you'll never receive the message
|
ws.on('close', onErr) // if the socket closes, you'll never receive the message
|
||||||
ws.off('error', onErr)
|
ws.off('error', onErr)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return result as any
|
return result as any
|
||||||
} finally {
|
} finally {
|
||||||
@@ -216,10 +208,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
const msgId = node.attrs.id
|
const msgId = node.attrs.id
|
||||||
|
|
||||||
const [result] = await Promise.all([
|
const [result] = await Promise.all([waitForMessage(msgId, timeoutMs), sendNode(node)])
|
||||||
waitForMessage(msgId, timeoutMs),
|
|
||||||
sendNode(node)
|
|
||||||
])
|
|
||||||
|
|
||||||
if ('tag' in result) {
|
if ('tag' in result) {
|
||||||
assertNodeErrorFree(result)
|
assertNodeErrorFree(result)
|
||||||
@@ -255,15 +244,13 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
logger.info({ node }, 'logging in...')
|
logger.info({ node }, 'logging in...')
|
||||||
}
|
}
|
||||||
|
|
||||||
const payloadEnc = noise.encrypt(
|
const payloadEnc = noise.encrypt(proto.ClientPayload.encode(node).finish())
|
||||||
proto.ClientPayload.encode(node).finish()
|
|
||||||
)
|
|
||||||
await sendRawMessage(
|
await sendRawMessage(
|
||||||
proto.HandshakeMessage.encode({
|
proto.HandshakeMessage.encode({
|
||||||
clientFinish: {
|
clientFinish: {
|
||||||
static: keyEnc,
|
static: keyEnc,
|
||||||
payload: payloadEnc,
|
payload: payloadEnc
|
||||||
},
|
}
|
||||||
}).finish()
|
}).finish()
|
||||||
)
|
)
|
||||||
noise.finishInit()
|
noise.finishInit()
|
||||||
@@ -279,9 +266,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
type: 'get',
|
type: 'get',
|
||||||
to: S_WHATSAPP_NET
|
to: S_WHATSAPP_NET
|
||||||
},
|
},
|
||||||
content: [
|
content: [{ tag: 'count', attrs: {} }]
|
||||||
{ tag: 'count', attrs: {} }
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
const countChild = getBinaryNodeChild(result, 'count')
|
const countChild = getBinaryNodeChild(result, 'count')
|
||||||
return +countChild!.attrs.value
|
return +countChild!.attrs.value
|
||||||
@@ -289,8 +274,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
/** generates and uploads a set of pre-keys to the server */
|
/** generates and uploads a set of pre-keys to the server */
|
||||||
const uploadPreKeys = async (count = INITIAL_PREKEY_COUNT) => {
|
const uploadPreKeys = async (count = INITIAL_PREKEY_COUNT) => {
|
||||||
await keys.transaction(
|
await keys.transaction(async () => {
|
||||||
async() => {
|
|
||||||
logger.info({ count }, 'uploading pre-keys')
|
logger.info({ count }, 'uploading pre-keys')
|
||||||
const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
|
const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
|
||||||
|
|
||||||
@@ -298,8 +282,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
ev.emit('creds.update', update)
|
ev.emit('creds.update', update)
|
||||||
|
|
||||||
logger.info({ count }, 'uploaded pre-keys')
|
logger.info({ count }, 'uploaded pre-keys')
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadPreKeysToServerIfRequired = async () => {
|
const uploadPreKeysToServerIfRequired = async () => {
|
||||||
@@ -356,10 +339,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
closed = true
|
closed = true
|
||||||
logger.info(
|
logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed')
|
||||||
{ trace: error?.stack },
|
|
||||||
error ? 'connection errored' : 'connection closed'
|
|
||||||
)
|
|
||||||
|
|
||||||
clearInterval(keepAliveReq)
|
clearInterval(keepAliveReq)
|
||||||
clearTimeout(qrTimer)
|
clearTimeout(qrTimer)
|
||||||
@@ -402,16 +382,15 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
ws.on('open', onOpen)
|
ws.on('open', onOpen)
|
||||||
ws.on('close', onClose)
|
ws.on('close', onClose)
|
||||||
ws.on('error', onClose)
|
ws.on('error', onClose)
|
||||||
})
|
}).finally(() => {
|
||||||
.finally(() => {
|
|
||||||
ws.off('open', onOpen)
|
ws.off('open', onOpen)
|
||||||
ws.off('close', onClose)
|
ws.off('close', onClose)
|
||||||
ws.off('error', onClose)
|
ws.off('error', onClose)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const startKeepAliveRequest = () => (
|
const startKeepAliveRequest = () =>
|
||||||
keepAliveReq = setInterval(() => {
|
(keepAliveReq = setInterval(() => {
|
||||||
if (!lastDateRecv) {
|
if (!lastDateRecv) {
|
||||||
lastDateRecv = new Date()
|
lastDateRecv = new Date()
|
||||||
}
|
}
|
||||||
@@ -425,40 +404,33 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
|
end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
|
||||||
} else if (ws.isOpen) {
|
} else if (ws.isOpen) {
|
||||||
// if its all good, send a keep alive request
|
// if its all good, send a keep alive request
|
||||||
query(
|
query({
|
||||||
{
|
|
||||||
tag: 'iq',
|
tag: 'iq',
|
||||||
attrs: {
|
attrs: {
|
||||||
id: generateMessageTag(),
|
id: generateMessageTag(),
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'get',
|
type: 'get',
|
||||||
xmlns: 'w:p',
|
xmlns: 'w:p'
|
||||||
},
|
},
|
||||||
content: [{ tag: 'ping', attrs: {} }]
|
content: [{ tag: 'ping', attrs: {} }]
|
||||||
}
|
}).catch(err => {
|
||||||
)
|
|
||||||
.catch(err => {
|
|
||||||
logger.error({ trace: err.stack }, 'error in sending keep alive')
|
logger.error({ trace: err.stack }, 'error in sending keep alive')
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
logger.warn('keep alive called when WS not open')
|
logger.warn('keep alive called when WS not open')
|
||||||
}
|
}
|
||||||
}, keepAliveIntervalMs)
|
}, keepAliveIntervalMs))
|
||||||
)
|
|
||||||
/** i have no idea why this exists. pls enlighten me */
|
/** i have no idea why this exists. pls enlighten me */
|
||||||
const sendPassiveIq = (tag: 'passive' | 'active') => (
|
const sendPassiveIq = (tag: 'passive' | 'active') =>
|
||||||
query({
|
query({
|
||||||
tag: 'iq',
|
tag: 'iq',
|
||||||
attrs: {
|
attrs: {
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
xmlns: 'passive',
|
xmlns: 'passive',
|
||||||
type: 'set',
|
type: 'set'
|
||||||
},
|
},
|
||||||
content: [
|
content: [{ tag, attrs: {} }]
|
||||||
{ tag, attrs: {} }
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
|
||||||
/** logout & invalidate connection */
|
/** logout & invalidate connection */
|
||||||
const logout = async (msg?: string) => {
|
const logout = async (msg?: string) => {
|
||||||
@@ -583,7 +555,9 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
ws.on('error', mapWebSocketError(end))
|
ws.on('error', mapWebSocketError(end))
|
||||||
ws.on('close', () => end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed })))
|
ws.on('close', () => end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed })))
|
||||||
// the server terminated the connection
|
// the server terminated the connection
|
||||||
ws.on('CB:xmlstreamend', () => end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed })))
|
ws.on('CB:xmlstreamend', () =>
|
||||||
|
end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed }))
|
||||||
|
)
|
||||||
// QR gen
|
// QR gen
|
||||||
ws.on('CB:iq,type:set,pair-device', async (stanza: BinaryNode) => {
|
ws.on('CB:iq,type:set,pair-device', async (stanza: BinaryNode) => {
|
||||||
const iq: BinaryNode = {
|
const iq: BinaryNode = {
|
||||||
@@ -591,7 +565,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
attrs: {
|
attrs: {
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'result',
|
type: 'result',
|
||||||
id: stanza.attrs.id,
|
id: stanza.attrs.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await sendNode(iq)
|
await sendNode(iq)
|
||||||
@@ -729,8 +703,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
sendNode({
|
sendNode({
|
||||||
tag: 'presence',
|
tag: 'presence',
|
||||||
attrs: { name: 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')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -738,7 +711,6 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
Object.assign(creds, update)
|
Object.assign(creds, update)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'md' as 'md',
|
type: 'md' as 'md',
|
||||||
ws,
|
ws,
|
||||||
@@ -762,7 +734,7 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
requestPairingCode,
|
requestPairingCode,
|
||||||
/** Waits for the connection to WA to reach a state */
|
/** Waits for the connection to WA to reach a state */
|
||||||
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
|
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
|
||||||
sendWAMBuffer,
|
sendWAMBuffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -772,11 +744,6 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
* */
|
* */
|
||||||
function mapWebSocketError(handler: (err: Error) => void) {
|
function mapWebSocketError(handler: (err: Error) => void) {
|
||||||
return (error: Error) => {
|
return (error: Error) => {
|
||||||
handler(
|
handler(new Boom(`WebSocket Error (${error?.message})`, { statusCode: getCodeFromWSError(error), data: error }))
|
||||||
new Boom(
|
|
||||||
`WebSocket Error (${error?.message})`,
|
|
||||||
{ statusCode: getCodeFromWSError(error), data: error }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import { makeSocket } from './socket'
|
|||||||
export const makeUSyncSocket = (config: SocketConfig) => {
|
export const makeUSyncSocket = (config: SocketConfig) => {
|
||||||
const sock = makeSocket(config)
|
const sock = makeSocket(config)
|
||||||
|
|
||||||
const {
|
const { generateMessageTag, query } = sock
|
||||||
generateMessageTag,
|
|
||||||
query,
|
|
||||||
} = sock
|
|
||||||
|
|
||||||
const executeUSyncQuery = async (usyncQuery: USyncQuery) => {
|
const executeUSyncQuery = async (usyncQuery: USyncQuery) => {
|
||||||
if (usyncQuery.protocols.length === 0) {
|
if (usyncQuery.protocols.length === 0) {
|
||||||
@@ -21,15 +18,13 @@ export const makeUSyncSocket = (config: SocketConfig) => {
|
|||||||
// variable below has only validated users
|
// variable below has only validated users
|
||||||
const validUsers = usyncQuery.users
|
const validUsers = usyncQuery.users
|
||||||
|
|
||||||
const userNodes = validUsers.map((user) => {
|
const userNodes = validUsers.map(user => {
|
||||||
return {
|
return {
|
||||||
tag: 'user',
|
tag: 'user',
|
||||||
attrs: {
|
attrs: {
|
||||||
jid: !user.phone ? user.id : undefined,
|
jid: !user.phone ? user.id : undefined
|
||||||
},
|
},
|
||||||
content: usyncQuery.protocols
|
content: usyncQuery.protocols.map(a => a.getUserElement(user)).filter(a => a !== null)
|
||||||
.map((a) => a.getUserElement(user))
|
|
||||||
.filter(a => a !== null)
|
|
||||||
} as BinaryNode
|
} as BinaryNode
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -42,14 +37,14 @@ export const makeUSyncSocket = (config: SocketConfig) => {
|
|||||||
const queryNode: BinaryNode = {
|
const queryNode: BinaryNode = {
|
||||||
tag: 'query',
|
tag: 'query',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: usyncQuery.protocols.map((a) => a.getQueryElement())
|
content: usyncQuery.protocols.map(a => a.getQueryElement())
|
||||||
}
|
}
|
||||||
const iq = {
|
const iq = {
|
||||||
tag: 'iq',
|
tag: 'iq',
|
||||||
attrs: {
|
attrs: {
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'get',
|
type: 'get',
|
||||||
xmlns: 'usync',
|
xmlns: 'usync'
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -59,14 +54,11 @@ export const makeUSyncSocket = (config: SocketConfig) => {
|
|||||||
mode: usyncQuery.mode,
|
mode: usyncQuery.mode,
|
||||||
sid: generateMessageTag(),
|
sid: generateMessageTag(),
|
||||||
last: 'true',
|
last: 'true',
|
||||||
index: '0',
|
index: '0'
|
||||||
},
|
},
|
||||||
content: [
|
content: [queryNode, listNode]
|
||||||
queryNode,
|
|
||||||
listNode
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await query(iq)
|
const result = await query(iq)
|
||||||
@@ -76,6 +68,6 @@ export const makeUSyncSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...sock,
|
...sock,
|
||||||
executeUSyncQuery,
|
executeUSyncQuery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import { processSyncAction } from '../Utils/chat-utils'
|
|||||||
import logger from '../Utils/logger'
|
import logger from '../Utils/logger'
|
||||||
|
|
||||||
describe('App State Sync Tests', () => {
|
describe('App State Sync Tests', () => {
|
||||||
|
|
||||||
const me: Contact = { id: randomJid() }
|
const me: Contact = { id: randomJid() }
|
||||||
// case when initial sync is off
|
// case when initial sync is off
|
||||||
it('should return archive=false event', () => {
|
it('should return archive=false event', () => {
|
||||||
@@ -129,7 +128,7 @@ describe('App State Sync Tests', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
const ctx: InitialAppStateSyncOptions = {
|
const ctx: InitialAppStateSyncOptions = {
|
||||||
@@ -152,7 +151,7 @@ describe('App State Sync Tests', () => {
|
|||||||
const index = ['archive', jid]
|
const index = ['archive', jid]
|
||||||
const now = unixTimestampSeconds()
|
const now = unixTimestampSeconds()
|
||||||
|
|
||||||
const CASES: { settings: AccountSettings, mutations: ChatMutation[] }[] = [
|
const CASES: { settings: AccountSettings; mutations: ChatMutation[] }[] = [
|
||||||
{
|
{
|
||||||
settings: { unarchiveChats: true },
|
settings: { unarchiveChats: true },
|
||||||
mutations: [
|
mutations: [
|
||||||
@@ -169,7 +168,7 @@ describe('App State Sync Tests', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
settings: { unarchiveChats: false },
|
settings: { unarchiveChats: false },
|
||||||
@@ -187,7 +186,7 @@ describe('App State Sync Tests', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import logger from '../Utils/logger'
|
|||||||
import { randomJid } from './utils'
|
import { randomJid } from './utils'
|
||||||
|
|
||||||
describe('Event Buffer Tests', () => {
|
describe('Event Buffer Tests', () => {
|
||||||
|
|
||||||
let ev: ReturnType<typeof makeEventBuffer>
|
let ev: ReturnType<typeof makeEventBuffer>
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const _logger = logger.child({})
|
const _logger = logger.child({})
|
||||||
@@ -93,7 +92,8 @@ describe('Event Buffer Tests', () => {
|
|||||||
ev.on('chats.update', () => fail('not should have emitted'))
|
ev.on('chats.update', () => fail('not should have emitted'))
|
||||||
|
|
||||||
ev.buffer()
|
ev.buffer()
|
||||||
ev.emit('chats.update', [{
|
ev.emit('chats.update', [
|
||||||
|
{
|
||||||
id: chatId,
|
id: chatId,
|
||||||
archived: true,
|
archived: true,
|
||||||
conditional(buff) {
|
conditional(buff) {
|
||||||
@@ -101,8 +101,10 @@ describe('Event Buffer Tests', () => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}])
|
}
|
||||||
ev.emit('chats.update', [{
|
])
|
||||||
|
ev.emit('chats.update', [
|
||||||
|
{
|
||||||
id: chatId2,
|
id: chatId2,
|
||||||
archived: true,
|
archived: true,
|
||||||
conditional(buff) {
|
conditional(buff) {
|
||||||
@@ -110,24 +112,29 @@ describe('Event Buffer Tests', () => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
|
|
||||||
ev.flush()
|
ev.flush()
|
||||||
|
|
||||||
ev.buffer()
|
ev.buffer()
|
||||||
ev.emit('chats.upsert', [{
|
ev.emit('chats.upsert', [
|
||||||
|
{
|
||||||
id: chatId,
|
id: chatId,
|
||||||
conversationTimestamp: 123,
|
conversationTimestamp: 123,
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
muteEndTime: 123
|
muteEndTime: 123
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
ev.emit('messaging-history.set', {
|
ev.emit('messaging-history.set', {
|
||||||
chats: [{
|
chats: [
|
||||||
|
{
|
||||||
id: chatId2,
|
id: chatId2,
|
||||||
conversationTimestamp: 123,
|
conversationTimestamp: 123,
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
muteEndTime: 123
|
muteEndTime: 123
|
||||||
}],
|
}
|
||||||
|
],
|
||||||
contacts: [],
|
contacts: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
isLatest: false
|
isLatest: false
|
||||||
@@ -152,7 +159,8 @@ describe('Event Buffer Tests', () => {
|
|||||||
ev.on('chats.update', () => fail('not should have emitted'))
|
ev.on('chats.update', () => fail('not should have emitted'))
|
||||||
|
|
||||||
ev.buffer()
|
ev.buffer()
|
||||||
ev.emit('chats.update', [{
|
ev.emit('chats.update', [
|
||||||
|
{
|
||||||
id: chatId,
|
id: chatId,
|
||||||
archived: true,
|
archived: true,
|
||||||
conditional(buff) {
|
conditional(buff) {
|
||||||
@@ -160,13 +168,16 @@ describe('Event Buffer Tests', () => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}])
|
}
|
||||||
ev.emit('chats.upsert', [{
|
])
|
||||||
|
ev.emit('chats.upsert', [
|
||||||
|
{
|
||||||
id: chatId,
|
id: chatId,
|
||||||
conversationTimestamp: 123,
|
conversationTimestamp: 123,
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
muteEndTime: 123
|
muteEndTime: 123
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
|
|
||||||
ev.flush()
|
ev.flush()
|
||||||
|
|
||||||
|
|||||||
@@ -5,24 +5,18 @@ import { makeMockSignalKeyStore } from './utils'
|
|||||||
logger.level = 'trace'
|
logger.level = 'trace'
|
||||||
|
|
||||||
describe('Key Store w Transaction Tests', () => {
|
describe('Key Store w Transaction Tests', () => {
|
||||||
|
|
||||||
const rawStore = makeMockSignalKeyStore()
|
const rawStore = makeMockSignalKeyStore()
|
||||||
const store = addTransactionCapability(
|
const store = addTransactionCapability(rawStore, logger, {
|
||||||
rawStore,
|
|
||||||
logger,
|
|
||||||
{
|
|
||||||
maxCommitRetries: 1,
|
maxCommitRetries: 1,
|
||||||
delayBetweenTriesMs: 10
|
delayBetweenTriesMs: 10
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
it('should use transaction cache when mutated', async () => {
|
it('should use transaction cache when mutated', async () => {
|
||||||
const key = '123'
|
const key = '123'
|
||||||
const value = new Uint8Array(1)
|
const value = new Uint8Array(1)
|
||||||
const ogGet = rawStore.get
|
const ogGet = rawStore.get
|
||||||
await store.transaction(
|
await store.transaction(async () => {
|
||||||
async() => {
|
await store.set({ session: { [key]: value } })
|
||||||
await store.set({ 'session': { [key]: value } })
|
|
||||||
|
|
||||||
rawStore.get = () => {
|
rawStore.get = () => {
|
||||||
throw new Error('should not have been called')
|
throw new Error('should not have been called')
|
||||||
@@ -30,8 +24,7 @@ describe('Key Store w Transaction Tests', () => {
|
|||||||
|
|
||||||
const { [key]: stored } = await store.get('session', [key])
|
const { [key]: stored } = await store.get('session', [key])
|
||||||
expect(stored).toEqual(new Uint8Array(1))
|
expect(stored).toEqual(new Uint8Array(1))
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
rawStore.get = ogGet
|
rawStore.get = ogGet
|
||||||
})
|
})
|
||||||
@@ -39,15 +32,11 @@ describe('Key Store w Transaction Tests', () => {
|
|||||||
it('should not commit a failed transaction', async () => {
|
it('should not commit a failed transaction', async () => {
|
||||||
const key = 'abcd'
|
const key = 'abcd'
|
||||||
await expect(
|
await expect(
|
||||||
store.transaction(
|
store.transaction(async () => {
|
||||||
async() => {
|
await store.set({ session: { [key]: new Uint8Array(1) } })
|
||||||
await store.set({ 'session': { [key]: new Uint8Array(1) } })
|
|
||||||
throw new Error('fail')
|
throw new Error('fail')
|
||||||
}
|
})
|
||||||
)
|
).rejects.toThrowError('fail')
|
||||||
).rejects.toThrowError(
|
|
||||||
'fail'
|
|
||||||
)
|
|
||||||
|
|
||||||
const { [key]: stored } = await store.get('session', [key])
|
const { [key]: stored } = await store.get('session', [key])
|
||||||
expect(stored).toBeUndefined()
|
expect(stored).toBeUndefined()
|
||||||
@@ -61,10 +50,9 @@ describe('Key Store w Transaction Tests', () => {
|
|||||||
promiseResolve = resolve
|
promiseResolve = resolve
|
||||||
})
|
})
|
||||||
|
|
||||||
store.transaction(
|
store.transaction(async () => {
|
||||||
async() => {
|
|
||||||
await store.set({
|
await store.set({
|
||||||
'session': {
|
session: {
|
||||||
'1': new Uint8Array(1)
|
'1': new Uint8Array(1)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -72,17 +60,14 @@ describe('Key Store w Transaction Tests', () => {
|
|||||||
await delay(5)
|
await delay(5)
|
||||||
// reolve the promise to let the other transaction continue
|
// reolve the promise to let the other transaction continue
|
||||||
promiseResolve()
|
promiseResolve()
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
await store.transaction(
|
await store.transaction(async () => {
|
||||||
async() => {
|
|
||||||
await promise
|
await promise
|
||||||
await delay(5)
|
await delay(5)
|
||||||
|
|
||||||
expect(store.isInTransaction()).toBe(true)
|
expect(store.isInTransaction()).toBe(true)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
expect(store.isInTransaction()).toBe(false)
|
expect(store.isInTransaction()).toBe(false)
|
||||||
// ensure that the transaction were committed
|
// ensure that the transaction were committed
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { SignalAuthState, SignalDataTypeMap } from '../Types'
|
|||||||
import { Curve, generateRegistrationId, generateSignalPubKey, signedKeyPair } from '../Utils'
|
import { Curve, generateRegistrationId, generateSignalPubKey, signedKeyPair } from '../Utils'
|
||||||
|
|
||||||
describe('Signal Tests', () => {
|
describe('Signal Tests', () => {
|
||||||
|
|
||||||
it('should correctly encrypt/decrypt 1 message', async () => {
|
it('should correctly encrypt/decrypt 1 message', async () => {
|
||||||
const user1 = makeUser()
|
const user1 = makeUser()
|
||||||
const user2 = makeUser()
|
const user2 = makeUser()
|
||||||
@@ -12,13 +11,9 @@ describe('Signal Tests', () => {
|
|||||||
|
|
||||||
await prepareForSendingMessage(user1, user2)
|
await prepareForSendingMessage(user1, user2)
|
||||||
|
|
||||||
const result = await user1.repository.encryptMessage(
|
const result = await user1.repository.encryptMessage({ jid: user2.jid, data: msg })
|
||||||
{ jid: user2.jid, data: msg }
|
|
||||||
)
|
|
||||||
|
|
||||||
const dec = await user2.repository.decryptMessage(
|
const dec = await user2.repository.decryptMessage({ jid: user1.jid, ...result })
|
||||||
{ jid: user1.jid, ...result }
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(dec).toEqual(msg)
|
expect(dec).toEqual(msg)
|
||||||
})
|
})
|
||||||
@@ -32,13 +27,9 @@ describe('Signal Tests', () => {
|
|||||||
for (let preKeyId = 2; preKeyId <= 3; preKeyId++) {
|
for (let preKeyId = 2; preKeyId <= 3; preKeyId++) {
|
||||||
await prepareForSendingMessage(user1, user2, preKeyId)
|
await prepareForSendingMessage(user1, user2, preKeyId)
|
||||||
|
|
||||||
const result = await user1.repository.encryptMessage(
|
const result = await user1.repository.encryptMessage({ jid: user2.jid, data: msg })
|
||||||
{ jid: user2.jid, data: msg }
|
|
||||||
)
|
|
||||||
|
|
||||||
const dec = await user2.repository.decryptMessage(
|
const dec = await user2.repository.decryptMessage({ jid: user1.jid, ...result })
|
||||||
{ jid: user1.jid, ...result }
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(dec).toEqual(msg)
|
expect(dec).toEqual(msg)
|
||||||
}
|
}
|
||||||
@@ -53,13 +44,9 @@ describe('Signal Tests', () => {
|
|||||||
await prepareForSendingMessage(user1, user2)
|
await prepareForSendingMessage(user1, user2)
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
const result = await user1.repository.encryptMessage(
|
const result = await user1.repository.encryptMessage({ jid: user2.jid, data: msg })
|
||||||
{ jid: user2.jid, data: msg }
|
|
||||||
)
|
|
||||||
|
|
||||||
const dec = await user2.repository.decryptMessage(
|
const dec = await user2.repository.decryptMessage({ jid: user1.jid, ...result })
|
||||||
{ jid: user1.jid, ...result }
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(dec).toEqual(msg)
|
expect(dec).toEqual(msg)
|
||||||
}
|
}
|
||||||
@@ -72,36 +59,30 @@ describe('Signal Tests', () => {
|
|||||||
const msg = Buffer.from('hello there!')
|
const msg = Buffer.from('hello there!')
|
||||||
|
|
||||||
const sender = participants[0]
|
const sender = participants[0]
|
||||||
const enc = await sender.repository.encryptGroupMessage(
|
const enc = await sender.repository.encryptGroupMessage({
|
||||||
{
|
|
||||||
group: groupId,
|
group: groupId,
|
||||||
meId: sender.jid,
|
meId: sender.jid,
|
||||||
data: msg
|
data: msg
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
for (const participant of participants) {
|
for (const participant of participants) {
|
||||||
if (participant === sender) {
|
if (participant === sender) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
await participant.repository.processSenderKeyDistributionMessage(
|
await participant.repository.processSenderKeyDistributionMessage({
|
||||||
{
|
|
||||||
item: {
|
item: {
|
||||||
groupId,
|
groupId,
|
||||||
axolotlSenderKeyDistributionMessage: enc.senderKeyDistributionMessage
|
axolotlSenderKeyDistributionMessage: enc.senderKeyDistributionMessage
|
||||||
},
|
},
|
||||||
authorJid: sender.jid
|
authorJid: sender.jid
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const dec = await participant.repository.decryptGroupMessage(
|
const dec = await participant.repository.decryptGroupMessage({
|
||||||
{
|
|
||||||
group: groupId,
|
group: groupId,
|
||||||
authorJid: sender.jid,
|
authorJid: sender.jid,
|
||||||
msg: enc.ciphertext
|
msg: enc.ciphertext
|
||||||
}
|
})
|
||||||
)
|
|
||||||
expect(dec).toEqual(msg)
|
expect(dec).toEqual(msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -116,14 +97,9 @@ function makeUser() {
|
|||||||
return { store, jid, repository }
|
return { store, jid, repository }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function prepareForSendingMessage(
|
async function prepareForSendingMessage(sender: User, receiver: User, preKeyId = 2) {
|
||||||
sender: User,
|
|
||||||
receiver: User,
|
|
||||||
preKeyId = 2
|
|
||||||
) {
|
|
||||||
const preKey = Curve.generateKeyPair()
|
const preKey = Curve.generateKeyPair()
|
||||||
await sender.repository.injectE2ESession(
|
await sender.repository.injectE2ESession({
|
||||||
{
|
|
||||||
jid: receiver.jid,
|
jid: receiver.jid,
|
||||||
session: {
|
session: {
|
||||||
registrationId: receiver.store.creds.registrationId,
|
registrationId: receiver.store.creds.registrationId,
|
||||||
@@ -131,15 +107,14 @@ async function prepareForSendingMessage(
|
|||||||
signedPreKey: {
|
signedPreKey: {
|
||||||
keyId: receiver.store.creds.signedPreKey.keyId,
|
keyId: receiver.store.creds.signedPreKey.keyId,
|
||||||
publicKey: generateSignalPubKey(receiver.store.creds.signedPreKey.keyPair.public),
|
publicKey: generateSignalPubKey(receiver.store.creds.signedPreKey.keyPair.public),
|
||||||
signature: receiver.store.creds.signedPreKey.signature,
|
signature: receiver.store.creds.signedPreKey.signature
|
||||||
},
|
},
|
||||||
preKey: {
|
preKey: {
|
||||||
keyId: preKeyId,
|
keyId: preKeyId,
|
||||||
publicKey: generateSignalPubKey(preKey.public),
|
publicKey: generateSignalPubKey(preKey.public)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
await receiver.store.keys.set({
|
await receiver.store.keys.set({
|
||||||
'pre-key': {
|
'pre-key': {
|
||||||
@@ -156,7 +131,7 @@ function makeTestAuthState(): SignalAuthState {
|
|||||||
creds: {
|
creds: {
|
||||||
signedIdentityKey: identityKey,
|
signedIdentityKey: identityKey,
|
||||||
registrationId: generateRegistrationId(),
|
registrationId: generateRegistrationId(),
|
||||||
signedPreKey: signedKeyPair(identityKey, 1),
|
signedPreKey: signedKeyPair(identityKey, 1)
|
||||||
},
|
},
|
||||||
keys: {
|
keys: {
|
||||||
get(type, ids) {
|
get(type, ids) {
|
||||||
@@ -176,7 +151,7 @@ function makeTestAuthState(): SignalAuthState {
|
|||||||
store[getUniqueId(type, id)] = data[type][id]
|
store[getUniqueId(type, id)] = data[type][id]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,10 @@ const TEST_VECTORS: TestVector[] = [
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
plaintext: readFileSync('./Media/icon.png')
|
plaintext: readFileSync('./Media/icon.png')
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
describe('Media Download Tests', () => {
|
describe('Media Download Tests', () => {
|
||||||
|
|
||||||
it('should download a full encrypted media correctly', async () => {
|
it('should download a full encrypted media correctly', async () => {
|
||||||
for (const { type, message, plaintext } of TEST_VECTORS) {
|
for (const { type, message, plaintext } of TEST_VECTORS) {
|
||||||
const readPipe = await downloadContentFromMessage(message, type)
|
const readPipe = await downloadContentFromMessage(message, type)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { WAMessageContent } from '../Types'
|
|||||||
import { normalizeMessageContent } from '../Utils'
|
import { normalizeMessageContent } from '../Utils'
|
||||||
|
|
||||||
describe('Messages Tests', () => {
|
describe('Messages Tests', () => {
|
||||||
|
|
||||||
it('should correctly unwrap messages', () => {
|
it('should correctly unwrap messages', () => {
|
||||||
const CONTENT = { imageMessage: {} }
|
const CONTENT = { imageMessage: {} }
|
||||||
expectRightContent(CONTENT)
|
expectRightContent(CONTENT)
|
||||||
@@ -29,9 +28,7 @@ describe('Messages Tests', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function expectRightContent(content: WAMessageContent) {
|
function expectRightContent(content: WAMessageContent) {
|
||||||
expect(
|
expect(normalizeMessageContent(content)).toHaveProperty('imageMessage')
|
||||||
normalizeMessageContent(content)
|
|
||||||
).toHaveProperty('imageMessage')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -27,7 +27,7 @@ export function makeMockSignalKeyStore(): SignalKeyStore {
|
|||||||
store[getUniqueId(type, id)] = data[type][id]
|
store[getUniqueId(type, id)] = data[type][id]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUniqueId(type: string, id: string) {
|
function getUniqueId(type: string, id: string) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { proto } from '../../WAProto'
|
|||||||
import type { Contact } from './Contact'
|
import type { Contact } from './Contact'
|
||||||
import type { MinimalMessage } from './Message'
|
import type { MinimalMessage } from './Message'
|
||||||
|
|
||||||
export type KeyPair = { public: Uint8Array, private: Uint8Array }
|
export type KeyPair = { public: Uint8Array; private: Uint8Array }
|
||||||
export type SignedKeyPair = {
|
export type SignedKeyPair = {
|
||||||
keyPair: KeyPair
|
keyPair: KeyPair
|
||||||
signature: Uint8Array
|
signature: Uint8Array
|
||||||
@@ -67,7 +67,7 @@ export type AuthenticationCreds = SignalCreds & {
|
|||||||
|
|
||||||
export type SignalDataTypeMap = {
|
export type SignalDataTypeMap = {
|
||||||
'pre-key': KeyPair
|
'pre-key': KeyPair
|
||||||
'session': Uint8Array
|
session: Uint8Array
|
||||||
'sender-key': Uint8Array
|
'sender-key': Uint8Array
|
||||||
'sender-key-memory': { [jid: string]: boolean }
|
'sender-key-memory': { [jid: string]: boolean }
|
||||||
'app-state-sync-key': proto.Message.IAppStateSyncKeyData
|
'app-state-sync-key': proto.Message.IAppStateSyncKeyData
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export type WACallUpdateType = 'offer' | 'ringing' | 'timeout' | 'reject' | 'accept' | 'terminate'
|
export type WACallUpdateType = 'offer' | 'ringing' | 'timeout' | 'reject' | 'accept' | 'terminate'
|
||||||
|
|
||||||
export type WACallEvent = {
|
export type WACallEvent = {
|
||||||
|
|||||||
@@ -22,9 +22,15 @@ export type WAPrivacyMessagesValue = 'all' | 'contacts'
|
|||||||
/** set of statuses visible to other people; see updatePresence() in WhatsAppWeb.Send */
|
/** set of statuses visible to other people; see updatePresence() in WhatsAppWeb.Send */
|
||||||
export type WAPresence = 'unavailable' | 'available' | 'composing' | 'recording' | 'paused'
|
export type WAPresence = 'unavailable' | 'available' | 'composing' | 'recording' | 'paused'
|
||||||
|
|
||||||
export const ALL_WA_PATCH_NAMES = ['critical_block', 'critical_unblock_low', 'regular_high', 'regular_low', 'regular'] as const
|
export const ALL_WA_PATCH_NAMES = [
|
||||||
|
'critical_block',
|
||||||
|
'critical_unblock_low',
|
||||||
|
'regular_high',
|
||||||
|
'regular_low',
|
||||||
|
'regular'
|
||||||
|
] as const
|
||||||
|
|
||||||
export type WAPatchName = typeof ALL_WA_PATCH_NAMES[number]
|
export type WAPatchName = (typeof ALL_WA_PATCH_NAMES)[number]
|
||||||
|
|
||||||
export interface PresenceData {
|
export interface PresenceData {
|
||||||
lastKnownPresence: WAPresence
|
lastKnownPresence: WAPresence
|
||||||
@@ -54,7 +60,8 @@ export type Chat = proto.IConversation & {
|
|||||||
lastMessageRecvTimestamp?: number
|
lastMessageRecvTimestamp?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChatUpdate = Partial<Chat & {
|
export type ChatUpdate = Partial<
|
||||||
|
Chat & {
|
||||||
/**
|
/**
|
||||||
* if specified in the update,
|
* if specified in the update,
|
||||||
* the EV buffer will check if the condition gets fulfilled before applying the update
|
* the EV buffer will check if the condition gets fulfilled before applying the update
|
||||||
@@ -65,7 +72,8 @@ export type ChatUpdate = Partial<Chat & {
|
|||||||
* undefined if the condition is not yet fulfilled
|
* undefined if the condition is not yet fulfilled
|
||||||
* */
|
* */
|
||||||
conditional: (bufferedData: BufferedEventData) => boolean | undefined
|
conditional: (bufferedData: BufferedEventData) => boolean | undefined
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the last messages in a chat, sorted reverse-chronologically. That is, the latest message should be first in the chat
|
* the last messages in a chat, sorted reverse-chronologically. That is, the latest message should be first in the chat
|
||||||
@@ -74,7 +82,7 @@ export type ChatUpdate = Partial<Chat & {
|
|||||||
export type LastMessageList = MinimalMessage[] | proto.SyncActionValue.ISyncActionMessageRange
|
export type LastMessageList = MinimalMessage[] | proto.SyncActionValue.ISyncActionMessageRange
|
||||||
|
|
||||||
export type ChatModification =
|
export type ChatModification =
|
||||||
{
|
| {
|
||||||
archive: boolean
|
archive: boolean
|
||||||
lastMessages: LastMessageList
|
lastMessages: LastMessageList
|
||||||
}
|
}
|
||||||
@@ -86,12 +94,13 @@ export type ChatModification =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
clear: boolean
|
clear: boolean
|
||||||
} | {
|
}
|
||||||
deleteForMe: { deleteMedia: boolean, key: WAMessageKey, timestamp: number }
|
| {
|
||||||
|
deleteForMe: { deleteMedia: boolean; key: WAMessageKey; timestamp: number }
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
star: {
|
star: {
|
||||||
messages: { id: string, fromMe?: boolean }[]
|
messages: { id: string; fromMe?: boolean }[]
|
||||||
star: boolean
|
star: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +108,7 @@ export type ChatModification =
|
|||||||
markRead: boolean
|
markRead: boolean
|
||||||
lastMessages: LastMessageList
|
lastMessages: LastMessageList
|
||||||
}
|
}
|
||||||
| { delete: true, lastMessages: LastMessageList }
|
| { delete: true; lastMessages: LastMessageList }
|
||||||
// Label
|
// Label
|
||||||
| { addLabel: LabelActionBody }
|
| { addLabel: LabelActionBody }
|
||||||
// Label assosiation
|
// Label assosiation
|
||||||
|
|||||||
@@ -29,42 +29,48 @@ export type BaileysEventMap = {
|
|||||||
'chats.upsert': Chat[]
|
'chats.upsert': Chat[]
|
||||||
/** update the given chats */
|
/** update the given chats */
|
||||||
'chats.update': ChatUpdate[]
|
'chats.update': ChatUpdate[]
|
||||||
'chats.phoneNumberShare': {lid: string, jid: string}
|
'chats.phoneNumberShare': { lid: string; jid: string }
|
||||||
/** delete chats with given ID */
|
/** delete chats with given ID */
|
||||||
'chats.delete': string[]
|
'chats.delete': string[]
|
||||||
/** presence of contact in a chat updated */
|
/** presence of contact in a chat updated */
|
||||||
'presence.update': { id: string, presences: { [participant: string]: PresenceData } }
|
'presence.update': { id: string; presences: { [participant: string]: PresenceData } }
|
||||||
|
|
||||||
'contacts.upsert': Contact[]
|
'contacts.upsert': Contact[]
|
||||||
'contacts.update': Partial<Contact>[]
|
'contacts.update': Partial<Contact>[]
|
||||||
|
|
||||||
'messages.delete': { keys: WAMessageKey[] } | { jid: string, all: true }
|
'messages.delete': { keys: WAMessageKey[] } | { jid: string; all: true }
|
||||||
'messages.update': WAMessageUpdate[]
|
'messages.update': WAMessageUpdate[]
|
||||||
'messages.media-update': { key: WAMessageKey, media?: { ciphertext: Uint8Array, iv: Uint8Array }, error?: Boom }[]
|
'messages.media-update': { key: WAMessageKey; media?: { ciphertext: Uint8Array; iv: Uint8Array }; error?: Boom }[]
|
||||||
/**
|
/**
|
||||||
* add/update the given messages. If they were received while the connection was online,
|
* add/update the given messages. If they were received while the connection was online,
|
||||||
* the update will have type: "notify"
|
* the update will have type: "notify"
|
||||||
* if requestId is provided, then the messages was received from the phone due to it being unavailable
|
* if requestId is provided, then the messages was received from the phone due to it being unavailable
|
||||||
* */
|
* */
|
||||||
'messages.upsert': { messages: WAMessage[], type: MessageUpsertType, requestId?: string }
|
'messages.upsert': { messages: WAMessage[]; type: MessageUpsertType; requestId?: string }
|
||||||
/** message was reacted to. If reaction was removed -- then "reaction.text" will be falsey */
|
/** message was reacted to. If reaction was removed -- then "reaction.text" will be falsey */
|
||||||
'messages.reaction': { key: WAMessageKey, reaction: proto.IReaction }[]
|
'messages.reaction': { key: WAMessageKey; reaction: proto.IReaction }[]
|
||||||
|
|
||||||
'message-receipt.update': MessageUserReceiptUpdate[]
|
'message-receipt.update': MessageUserReceiptUpdate[]
|
||||||
|
|
||||||
'groups.upsert': GroupMetadata[]
|
'groups.upsert': GroupMetadata[]
|
||||||
'groups.update': Partial<GroupMetadata>[]
|
'groups.update': Partial<GroupMetadata>[]
|
||||||
/** apply an action to participants in a group */
|
/** apply an action to participants in a group */
|
||||||
'group-participants.update': { id: string, author: string, participants: string[], action: ParticipantAction }
|
'group-participants.update': { id: string; author: string; participants: string[]; action: ParticipantAction }
|
||||||
'group.join-request': { id: string, author: string, participant: string, action: RequestJoinAction, method: RequestJoinMethod }
|
'group.join-request': {
|
||||||
|
id: string
|
||||||
|
author: string
|
||||||
|
participant: string
|
||||||
|
action: RequestJoinAction
|
||||||
|
method: RequestJoinMethod
|
||||||
|
}
|
||||||
|
|
||||||
'blocklist.set': { blocklist: string[] }
|
'blocklist.set': { blocklist: string[] }
|
||||||
'blocklist.update': { blocklist: string[], type: 'add' | 'remove' }
|
'blocklist.update': { blocklist: string[]; type: 'add' | 'remove' }
|
||||||
|
|
||||||
/** Receive an update on a call, including when the call was received, rejected, accepted */
|
/** Receive an update on a call, including when the call was received, rejected, accepted */
|
||||||
'call': WACallEvent[]
|
call: WACallEvent[]
|
||||||
'labels.edit': Label
|
'labels.edit': Label
|
||||||
'labels.association': { association: LabelAssociation, type: 'add' | 'remove' }
|
'labels.association': { association: LabelAssociation; type: 'add' | 'remove' }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BufferedEventData = {
|
export type BufferedEventData = {
|
||||||
@@ -83,11 +89,11 @@ export type BufferedEventData = {
|
|||||||
chatDeletes: Set<string>
|
chatDeletes: Set<string>
|
||||||
contactUpserts: { [jid: string]: Contact }
|
contactUpserts: { [jid: string]: Contact }
|
||||||
contactUpdates: { [jid: string]: Partial<Contact> }
|
contactUpdates: { [jid: string]: Partial<Contact> }
|
||||||
messageUpserts: { [key: string]: { type: MessageUpsertType, message: WAMessage } }
|
messageUpserts: { [key: string]: { type: MessageUpsertType; message: WAMessage } }
|
||||||
messageUpdates: { [key: string]: WAMessageUpdate }
|
messageUpdates: { [key: string]: WAMessageUpdate }
|
||||||
messageDeletes: { [key: string]: WAMessageKey }
|
messageDeletes: { [key: string]: WAMessageKey }
|
||||||
messageReactions: { [key: string]: { key: WAMessageKey, reactions: proto.IReaction[] } }
|
messageReactions: { [key: string]: { key: WAMessageKey; reactions: proto.IReaction[] } }
|
||||||
messageReceipts: { [key: string]: { key: WAMessageKey, userReceipt: proto.IUserReceipt[] } }
|
messageReceipts: { [key: string]: { key: WAMessageKey; userReceipt: proto.IUserReceipt[] } }
|
||||||
groupUpdates: { [jid: string]: Partial<GroupMetadata> }
|
groupUpdates: { [jid: string]: Partial<GroupMetadata> }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { Contact } from './Contact'
|
import { Contact } from './Contact'
|
||||||
|
|
||||||
export type GroupParticipant = (Contact & { isAdmin?: boolean, isSuperAdmin?: boolean, admin?: 'admin' | 'superadmin' | null })
|
export type GroupParticipant = Contact & {
|
||||||
|
isAdmin?: boolean
|
||||||
|
isSuperAdmin?: boolean
|
||||||
|
admin?: 'admin' | 'superadmin' | null
|
||||||
|
}
|
||||||
|
|
||||||
export type ParticipantAction = 'add' | 'remove' | 'promote' | 'demote' | 'modify'
|
export type ParticipantAction = 'add' | 'remove' | 'promote' | 'demote' | 'modify'
|
||||||
|
|
||||||
@@ -46,7 +50,6 @@ export interface GroupMetadata {
|
|||||||
author?: string
|
author?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface WAGroupCreateResponse {
|
export interface WAGroupCreateResponse {
|
||||||
status: number
|
status: number
|
||||||
gid?: string
|
gid?: string
|
||||||
|
|||||||
@@ -44,5 +44,5 @@ export enum LabelColor {
|
|||||||
Color17,
|
Color17,
|
||||||
Color18,
|
Color18,
|
||||||
Color19,
|
Color19,
|
||||||
Color20,
|
Color20
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,12 @@ export type WAMessageKey = proto.IMessageKey
|
|||||||
export type WATextMessage = proto.Message.IExtendedTextMessage
|
export type WATextMessage = proto.Message.IExtendedTextMessage
|
||||||
export type WAContextInfo = proto.IContextInfo
|
export type WAContextInfo = proto.IContextInfo
|
||||||
export type WALocationMessage = proto.Message.ILocationMessage
|
export type WALocationMessage = proto.Message.ILocationMessage
|
||||||
export type WAGenericMediaMessage = proto.Message.IVideoMessage | proto.Message.IImageMessage | proto.Message.IAudioMessage | proto.Message.IDocumentMessage | proto.Message.IStickerMessage
|
export type WAGenericMediaMessage =
|
||||||
|
| proto.Message.IVideoMessage
|
||||||
|
| proto.Message.IImageMessage
|
||||||
|
| proto.Message.IAudioMessage
|
||||||
|
| proto.Message.IDocumentMessage
|
||||||
|
| proto.Message.IStickerMessage
|
||||||
export const WAMessageStubType = proto.WebMessageInfo.StubType
|
export const WAMessageStubType = proto.WebMessageInfo.StubType
|
||||||
export const WAMessageStatus = proto.WebMessageInfo.Status
|
export const WAMessageStatus = proto.WebMessageInfo.Status
|
||||||
import { ILogger } from '../Utils/logger'
|
import { ILogger } from '../Utils/logger'
|
||||||
@@ -27,14 +32,22 @@ export type WAMediaUpload = Buffer | WAMediaPayloadStream | WAMediaPayloadURL
|
|||||||
/** 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 | null, directPath?: string | null, url?: string | null }
|
export type DownloadableMessage = { mediaKey?: Uint8Array | null; directPath?: string | null; url?: string | null }
|
||||||
|
|
||||||
export type MessageReceiptType = 'read' | 'read-self' | 'hist_sync' | 'peer_msg' | 'sender' | 'inactive' | 'played' | undefined
|
export type MessageReceiptType =
|
||||||
|
| 'read'
|
||||||
|
| 'read-self'
|
||||||
|
| 'hist_sync'
|
||||||
|
| 'peer_msg'
|
||||||
|
| 'sender'
|
||||||
|
| 'inactive'
|
||||||
|
| 'played'
|
||||||
|
| undefined
|
||||||
|
|
||||||
export type MediaConnInfo = {
|
export type MediaConnInfo = {
|
||||||
auth: string
|
auth: string
|
||||||
ttl: number
|
ttl: number
|
||||||
hosts: { hostname: string, maxContentLengthBytes: number }[]
|
hosts: { hostname: string; maxContentLengthBytes: number }[]
|
||||||
fetchDate: Date
|
fetchDate: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,11 +101,13 @@ type RequestPhoneNumber = {
|
|||||||
|
|
||||||
export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING
|
export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING
|
||||||
export type AnyMediaMessageContent = (
|
export type AnyMediaMessageContent = (
|
||||||
({
|
| ({
|
||||||
image: WAMediaUpload
|
image: WAMediaUpload
|
||||||
caption?: string
|
caption?: string
|
||||||
jpegThumbnail?: string
|
jpegThumbnail?: string
|
||||||
} & Mentionable & Contextable & WithDimensions)
|
} & Mentionable &
|
||||||
|
Contextable &
|
||||||
|
WithDimensions)
|
||||||
| ({
|
| ({
|
||||||
video: WAMediaUpload
|
video: WAMediaUpload
|
||||||
caption?: string
|
caption?: string
|
||||||
@@ -100,7 +115,9 @@ export type AnyMediaMessageContent = (
|
|||||||
jpegThumbnail?: string
|
jpegThumbnail?: string
|
||||||
/** if set to true, will send as a `video note` */
|
/** if set to true, will send as a `video note` */
|
||||||
ptv?: boolean
|
ptv?: boolean
|
||||||
} & Mentionable & Contextable & WithDimensions)
|
} & Mentionable &
|
||||||
|
Contextable &
|
||||||
|
WithDimensions)
|
||||||
| {
|
| {
|
||||||
audio: WAMediaUpload
|
audio: WAMediaUpload
|
||||||
/** if set to true, will send as a `voice note` */
|
/** if set to true, will send as a `voice note` */
|
||||||
@@ -111,13 +128,14 @@ export type AnyMediaMessageContent = (
|
|||||||
| ({
|
| ({
|
||||||
sticker: WAMediaUpload
|
sticker: WAMediaUpload
|
||||||
isAnimated?: boolean
|
isAnimated?: boolean
|
||||||
} & WithDimensions) | ({
|
} & WithDimensions)
|
||||||
|
| ({
|
||||||
document: WAMediaUpload
|
document: WAMediaUpload
|
||||||
mimetype: string
|
mimetype: string
|
||||||
fileName?: string
|
fileName?: string
|
||||||
caption?: string
|
caption?: string
|
||||||
} & Contextable))
|
} & Contextable)
|
||||||
& { mimetype?: string } & Editable
|
) & { mimetype?: string } & Editable
|
||||||
|
|
||||||
export type ButtonReplyInfo = {
|
export type ButtonReplyInfo = {
|
||||||
displayText: string
|
displayText: string
|
||||||
@@ -138,15 +156,18 @@ export type WASendableProduct = Omit<proto.Message.ProductMessage.IProductSnapsh
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type AnyRegularMessageContent = (
|
export type AnyRegularMessageContent = (
|
||||||
({
|
| ({
|
||||||
text: string
|
text: string
|
||||||
linkPreview?: WAUrlInfo | null
|
linkPreview?: WAUrlInfo | null
|
||||||
}
|
} & Mentionable &
|
||||||
& Mentionable & Contextable & Editable)
|
Contextable &
|
||||||
|
Editable)
|
||||||
| AnyMediaMessageContent
|
| AnyMediaMessageContent
|
||||||
| ({
|
| ({
|
||||||
poll: PollMessageOptions
|
poll: PollMessageOptions
|
||||||
} & Mentionable & Contextable & Editable)
|
} & Mentionable &
|
||||||
|
Contextable &
|
||||||
|
Editable)
|
||||||
| {
|
| {
|
||||||
contacts: {
|
contacts: {
|
||||||
displayName?: string
|
displayName?: string
|
||||||
@@ -180,16 +201,23 @@ export type AnyRegularMessageContent = (
|
|||||||
businessOwnerJid?: string
|
businessOwnerJid?: string
|
||||||
body?: string
|
body?: string
|
||||||
footer?: string
|
footer?: string
|
||||||
} | SharePhoneNumber | RequestPhoneNumber
|
}
|
||||||
) & ViewOnce
|
| SharePhoneNumber
|
||||||
|
| RequestPhoneNumber
|
||||||
|
) &
|
||||||
|
ViewOnce
|
||||||
|
|
||||||
export type AnyMessageContent = AnyRegularMessageContent | {
|
export type AnyMessageContent =
|
||||||
|
| AnyRegularMessageContent
|
||||||
|
| {
|
||||||
forward: WAMessage
|
forward: WAMessage
|
||||||
force?: boolean
|
force?: boolean
|
||||||
} | {
|
}
|
||||||
|
| {
|
||||||
/** Delete your message or anyone's message in a group (admin required) */
|
/** Delete your message or anyone's message in a group (admin required) */
|
||||||
delete: WAMessageKey
|
delete: WAMessageKey
|
||||||
} | {
|
}
|
||||||
|
| {
|
||||||
disappearingMessagesInChat: boolean | number
|
disappearingMessagesInChat: boolean | number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +232,7 @@ type MinimalRelayOptions = {
|
|||||||
|
|
||||||
export type MessageRelayOptions = MinimalRelayOptions & {
|
export type MessageRelayOptions = MinimalRelayOptions & {
|
||||||
/** only send to a specific participant; used when a message decryption fails for a single user */
|
/** only send to a specific participant; used when a message decryption fails for a single user */
|
||||||
participant?: { jid: string, count: number }
|
participant?: { jid: string; count: number }
|
||||||
/** additional attributes to add to the WA binary node */
|
/** additional attributes to add to the WA binary node */
|
||||||
additionalAttributes?: { [_: string]: string }
|
additionalAttributes?: { [_: string]: string }
|
||||||
additionalNodes?: BinaryNode[]
|
additionalNodes?: BinaryNode[]
|
||||||
@@ -236,7 +264,10 @@ export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions &
|
|||||||
userJid: string
|
userJid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WAMediaUploadFunction = (readStream: Readable, opts: { fileEncSha256B64: string, mediaType: MediaType, timeoutMs?: number }) => Promise<{ mediaUrl: string, directPath: string }>
|
export type WAMediaUploadFunction = (
|
||||||
|
readStream: Readable,
|
||||||
|
opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number }
|
||||||
|
) => Promise<{ mediaUrl: string; directPath: string }>
|
||||||
|
|
||||||
export type MediaGenerationOptions = {
|
export type MediaGenerationOptions = {
|
||||||
logger?: ILogger
|
logger?: ILogger
|
||||||
@@ -268,11 +299,11 @@ export type MessageUpsertType = 'append' | 'notify'
|
|||||||
|
|
||||||
export type MessageUserReceipt = proto.IUserReceipt
|
export type MessageUserReceipt = proto.IUserReceipt
|
||||||
|
|
||||||
export type WAMessageUpdate = { update: Partial<WAMessage>, key: proto.IMessageKey }
|
export type WAMessageUpdate = { update: Partial<WAMessage>; key: proto.IMessageKey }
|
||||||
|
|
||||||
export type WAMessageCursor = { before: WAMessageKey | undefined } | { after: WAMessageKey | undefined }
|
export type WAMessageCursor = { before: WAMessageKey | undefined } | { after: WAMessageKey | undefined }
|
||||||
|
|
||||||
export type MessageUserReceiptUpdate = { key: proto.IMessageKey, receipt: MessageUserReceipt }
|
export type MessageUserReceiptUpdate = { key: proto.IMessageKey; receipt: MessageUserReceipt }
|
||||||
|
|
||||||
export type MediaDecryptionKeyInfo = {
|
export type MediaDecryptionKeyInfo = {
|
||||||
iv: Buffer
|
iv: Buffer
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { WAMediaUpload } from './Message'
|
|||||||
|
|
||||||
export type CatalogResult = {
|
export type CatalogResult = {
|
||||||
data: {
|
data: {
|
||||||
paging: { cursors: { before: string, after: string } }
|
paging: { cursors: { before: string; after: string } }
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
data: any[]
|
data: any[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,7 @@ type E2ESessionOpts = {
|
|||||||
|
|
||||||
export type SignalRepository = {
|
export type SignalRepository = {
|
||||||
decryptGroupMessage(opts: DecryptGroupSignalOpts): Promise<Uint8Array>
|
decryptGroupMessage(opts: DecryptGroupSignalOpts): Promise<Uint8Array>
|
||||||
processSenderKeyDistributionMessage(
|
processSenderKeyDistributionMessage(opts: ProcessSenderKeyDistributionMessageOpts): Promise<void>
|
||||||
opts: ProcessSenderKeyDistributionMessageOpts
|
|
||||||
): Promise<void>
|
|
||||||
decryptMessage(opts: DecryptSignalProtoOpts): Promise<Uint8Array>
|
decryptMessage(opts: DecryptSignalProtoOpts): Promise<Uint8Array>
|
||||||
encryptMessage(opts: EncryptMessageOpts): Promise<{
|
encryptMessage(opts: EncryptMessageOpts): Promise<{
|
||||||
type: 'pkmsg' | 'msg'
|
type: 'pkmsg' | 'msg'
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { AxiosRequestConfig } from 'axios'
|
import { AxiosRequestConfig } from 'axios'
|
||||||
import type { Agent } from 'https'
|
import type { Agent } from 'https'
|
||||||
import type { URL } from 'url'
|
import type { URL } from 'url'
|
||||||
@@ -108,8 +107,11 @@ export type SocketConfig = {
|
|||||||
* */
|
* */
|
||||||
patchMessageBeforeSending: (
|
patchMessageBeforeSending: (
|
||||||
msg: proto.IMessage,
|
msg: proto.IMessage,
|
||||||
recipientJids?: string[],
|
recipientJids?: string[]
|
||||||
) => Promise<PatchedMessageWithRecipientJID[] | PatchedMessageWithRecipientJID> | PatchedMessageWithRecipientJID[] | PatchedMessageWithRecipientJID
|
) =>
|
||||||
|
| Promise<PatchedMessageWithRecipientJID[] | PatchedMessageWithRecipientJID>
|
||||||
|
| PatchedMessageWithRecipientJID[]
|
||||||
|
| PatchedMessageWithRecipientJID
|
||||||
|
|
||||||
/** verify app state MACs */
|
/** verify app state MACs */
|
||||||
appStateMacVerification: {
|
appStateMacVerification: {
|
||||||
|
|||||||
@@ -63,4 +63,4 @@ export type WABusinessProfile = {
|
|||||||
address?: string
|
address?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CurveKeyPair = { private: Uint8Array, public: Uint8Array }
|
export type CurveKeyPair = { private: Uint8Array; public: Uint8Array }
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import NodeCache from '@cacheable/node-cache'
|
import NodeCache from '@cacheable/node-cache'
|
||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import { DEFAULT_CACHE_TTLS } from '../Defaults'
|
import { DEFAULT_CACHE_TTLS } from '../Defaults'
|
||||||
import type { AuthenticationCreds, CacheStore, SignalDataSet, SignalDataTypeMap, SignalKeyStore, SignalKeyStoreWithTransaction, TransactionCapabilityOptions } from '../Types'
|
import type {
|
||||||
|
AuthenticationCreds,
|
||||||
|
CacheStore,
|
||||||
|
SignalDataSet,
|
||||||
|
SignalDataTypeMap,
|
||||||
|
SignalKeyStore,
|
||||||
|
SignalKeyStoreWithTransaction,
|
||||||
|
TransactionCapabilityOptions
|
||||||
|
} from '../Types'
|
||||||
import { Curve, signedKeyPair } from './crypto'
|
import { Curve, signedKeyPair } from './crypto'
|
||||||
import { delay, generateRegistrationId } from './generics'
|
import { delay, generateRegistrationId } from './generics'
|
||||||
import { ILogger } from './logger'
|
import { ILogger } from './logger'
|
||||||
@@ -17,10 +25,12 @@ export function makeCacheableSignalKeyStore(
|
|||||||
logger?: ILogger,
|
logger?: ILogger,
|
||||||
_cache?: CacheStore
|
_cache?: CacheStore
|
||||||
): SignalKeyStore {
|
): SignalKeyStore {
|
||||||
const cache = _cache || new NodeCache({
|
const cache =
|
||||||
|
_cache ||
|
||||||
|
new NodeCache({
|
||||||
stdTTL: DEFAULT_CACHE_TTLS.SIGNAL_STORE, // 5 minutes
|
stdTTL: DEFAULT_CACHE_TTLS.SIGNAL_STORE, // 5 minutes
|
||||||
useClones: false,
|
useClones: false,
|
||||||
deleteOnExpire: true,
|
deleteOnExpire: true
|
||||||
})
|
})
|
||||||
|
|
||||||
function getUniqueId(type: string, id: string) {
|
function getUniqueId(type: string, id: string) {
|
||||||
@@ -98,31 +108,24 @@ export const addTransactionCapability = (
|
|||||||
get: async (type, ids) => {
|
get: async (type, ids) => {
|
||||||
if (isInTransaction()) {
|
if (isInTransaction()) {
|
||||||
const dict = transactionCache[type]
|
const dict = transactionCache[type]
|
||||||
const idsRequiringFetch = dict
|
const idsRequiringFetch = dict ? ids.filter(item => typeof dict[item] === 'undefined') : ids
|
||||||
? ids.filter(item => typeof dict[item] === 'undefined')
|
|
||||||
: ids
|
|
||||||
// only fetch if there are any items to fetch
|
// only fetch if there are any items to fetch
|
||||||
if (idsRequiringFetch.length) {
|
if (idsRequiringFetch.length) {
|
||||||
dbQueriesInTransaction += 1
|
dbQueriesInTransaction += 1
|
||||||
const result = await state.get(type, idsRequiringFetch)
|
const result = await state.get(type, idsRequiringFetch)
|
||||||
|
|
||||||
transactionCache[type] ||= {}
|
transactionCache[type] ||= {}
|
||||||
Object.assign(
|
Object.assign(transactionCache[type]!, result)
|
||||||
transactionCache[type]!,
|
|
||||||
result
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ids.reduce(
|
return ids.reduce((dict, id) => {
|
||||||
(dict, id) => {
|
|
||||||
const value = transactionCache[type]?.[id]
|
const value = transactionCache[type]?.[id]
|
||||||
if (value) {
|
if (value) {
|
||||||
dict[id] = value
|
dict[id] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
return dict
|
return dict
|
||||||
}, { }
|
}, {})
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
return state.get(type, ids)
|
return state.get(type, ids)
|
||||||
}
|
}
|
||||||
@@ -211,6 +214,6 @@ export const initAuthCreds = (): AuthenticationCreds => {
|
|||||||
registered: false,
|
registered: false,
|
||||||
pairingCode: undefined,
|
pairingCode: undefined,
|
||||||
lastPropHash: undefined,
|
lastPropHash: undefined,
|
||||||
routingInfo: undefined,
|
routingInfo: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,9 @@ export const captureEventStream = (ev: BaileysEventEmitter, filename: string) =>
|
|||||||
const content = JSON.stringify({ timestamp: Date.now(), event: args[0], data: args[1] }) + '\n'
|
const content = JSON.stringify({ timestamp: Date.now(), event: args[0], data: args[1] }) + '\n'
|
||||||
const result = oldEmit.apply(ev, args)
|
const result = oldEmit.apply(ev, args)
|
||||||
|
|
||||||
writeMutex.mutex(
|
writeMutex.mutex(async () => {
|
||||||
async() => {
|
|
||||||
await writeFile(filename, content, { flag: 'a' })
|
await writeFile(filename, content, { flag: 'a' })
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -52,7 +50,7 @@ export const readAndEmitEventStream = (filename: string, delayIntervalMs = 0) =>
|
|||||||
if (line) {
|
if (line) {
|
||||||
const { event, data } = JSON.parse(line)
|
const { event, data } = JSON.parse(line)
|
||||||
ev.emit(event, data)
|
ev.emit(event, data)
|
||||||
delayIntervalMs && await delay(delayIntervalMs)
|
delayIntervalMs && (await delay(delayIntervalMs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { createHash } from 'crypto'
|
import { createHash } from 'crypto'
|
||||||
import { CatalogCollection, CatalogStatus, OrderDetails, OrderProduct, Product, ProductCreate, ProductUpdate, WAMediaUpload, WAMediaUploadFunction } from '../Types'
|
import {
|
||||||
|
CatalogCollection,
|
||||||
|
CatalogStatus,
|
||||||
|
OrderDetails,
|
||||||
|
OrderProduct,
|
||||||
|
Product,
|
||||||
|
ProductCreate,
|
||||||
|
ProductUpdate,
|
||||||
|
WAMediaUpload,
|
||||||
|
WAMediaUploadFunction
|
||||||
|
} from '../Types'
|
||||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary'
|
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary'
|
||||||
import { getStream, getUrlFromDirectPath, toReadable } from './messages-media'
|
import { getStream, getUrlFromDirectPath, toReadable } from './messages-media'
|
||||||
|
|
||||||
@@ -11,16 +21,13 @@ export const parseCatalogNode = (node: BinaryNode) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
products,
|
products,
|
||||||
nextPageCursor: paging
|
nextPageCursor: paging ? getBinaryNodeChildString(paging, 'after') : undefined
|
||||||
? getBinaryNodeChildString(paging, 'after')
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseCollectionsNode = (node: BinaryNode) => {
|
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')!
|
||||||
|
|
||||||
@@ -31,8 +38,7 @@ export const parseCollectionsNode = (node: BinaryNode) => {
|
|||||||
products,
|
products,
|
||||||
status: parseStatusInfo(collectionNode)
|
status: parseStatusInfo(collectionNode)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
collections
|
collections
|
||||||
@@ -41,8 +47,7 @@ export const parseCollectionsNode = (node: BinaryNode) => {
|
|||||||
|
|
||||||
export const parseOrderDetailsNode = (node: BinaryNode) => {
|
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')!,
|
||||||
@@ -52,15 +57,14 @@ export const parseOrderDetailsNode = (node: BinaryNode) => {
|
|||||||
currency: getBinaryNodeChildString(productNode, 'currency')!,
|
currency: getBinaryNodeChildString(productNode, 'currency')!,
|
||||||
quantity: +getBinaryNodeChildString(productNode, 'quantity')!
|
quantity: +getBinaryNodeChildString(productNode, 'quantity')!
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const priceNode = getBinaryNodeChild(orderNode, 'price')
|
const priceNode = getBinaryNodeChild(orderNode, 'price')
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -108,8 +112,7 @@ export const toProductNode = (productId: string | undefined, product: ProductCre
|
|||||||
content.push({
|
content.push({
|
||||||
tag: 'media',
|
tag: 'media',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: product.images.map(
|
content: product.images.map(img => {
|
||||||
img => {
|
|
||||||
if (!('url' in img)) {
|
if (!('url' in img)) {
|
||||||
throw new Boom('Expected img for product to already be uploaded', { statusCode: 400 })
|
throw new Boom('Expected img for product to already be uploaded', { statusCode: 400 })
|
||||||
}
|
}
|
||||||
@@ -125,8 +128,7 @@ export const toProductNode = (productId: string | undefined, product: ProductCre
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +166,6 @@ export const toProductNode = (productId: string | undefined, product: ProductCre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (typeof product.isHidden !== 'undefined') {
|
if (typeof product.isHidden !== 'undefined') {
|
||||||
attrs['is_hidden'] = product.isHidden.toString()
|
attrs['is_hidden'] = product.isHidden.toString()
|
||||||
}
|
}
|
||||||
@@ -188,7 +189,7 @@ export const parseProductNode = (productNode: BinaryNode) => {
|
|||||||
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')!,
|
||||||
@@ -197,7 +198,7 @@ export const parseProductNode = (productNode: BinaryNode) => {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
return product
|
return product
|
||||||
@@ -206,10 +207,16 @@ export const parseProductNode = (productNode: BinaryNode) => {
|
|||||||
/**
|
/**
|
||||||
* Uploads images not already uploaded to WA's servers
|
* Uploads images not already uploaded to WA's servers
|
||||||
*/
|
*/
|
||||||
export async function uploadingNecessaryImagesOfProduct<T extends ProductUpdate | ProductCreate>(product: T, waUploadToServer: WAMediaUploadFunction, timeoutMs = 30_000) {
|
export async function uploadingNecessaryImagesOfProduct<T extends ProductUpdate | ProductCreate>(
|
||||||
|
product: T,
|
||||||
|
waUploadToServer: WAMediaUploadFunction,
|
||||||
|
timeoutMs = 30_000
|
||||||
|
) {
|
||||||
product = {
|
product = {
|
||||||
...product,
|
...product,
|
||||||
images: product.images ? await uploadingNecessaryImages(product.images, waUploadToServer, timeoutMs) : product.images
|
images: product.images
|
||||||
|
? await uploadingNecessaryImages(product.images, waUploadToServer, timeoutMs)
|
||||||
|
: product.images
|
||||||
}
|
}
|
||||||
return product
|
return product
|
||||||
}
|
}
|
||||||
@@ -223,9 +230,7 @@ export const uploadingNecessaryImages = async(
|
|||||||
timeoutMs = 30_000
|
timeoutMs = 30_000
|
||||||
) => {
|
) => {
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
images.map<Promise<{ url: string }>>(
|
images.map<Promise<{ url: string }>>(async img => {
|
||||||
async img => {
|
|
||||||
|
|
||||||
if ('url' in img) {
|
if ('url' in img) {
|
||||||
const url = img.url.toString()
|
const url = img.url.toString()
|
||||||
if (url.includes('.whatsapp.net')) {
|
if (url.includes('.whatsapp.net')) {
|
||||||
@@ -243,17 +248,13 @@ export const uploadingNecessaryImages = async(
|
|||||||
|
|
||||||
const sha = hasher.digest('base64')
|
const sha = hasher.digest('base64')
|
||||||
|
|
||||||
const { directPath } = await waUploadToServer(
|
const { directPath } = await waUploadToServer(toReadable(Buffer.concat(contentBlocks)), {
|
||||||
toReadable(Buffer.concat(contentBlocks)),
|
|
||||||
{
|
|
||||||
mediaType: 'product-catalog-image',
|
mediaType: 'product-catalog-image',
|
||||||
fileEncSha256B64: sha,
|
fileEncSha256B64: sha,
|
||||||
timeoutMs
|
timeoutMs
|
||||||
}
|
})
|
||||||
)
|
|
||||||
return { url: getUrlFromDirectPath(directPath) }
|
return { url: getUrlFromDirectPath(directPath) }
|
||||||
}
|
})
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
@@ -270,6 +271,6 @@ 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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,26 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { AxiosRequestConfig } from 'axios'
|
import { AxiosRequestConfig } from 'axios'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { BaileysEventEmitter, Chat, ChatModification, ChatMutation, ChatUpdate, Contact, InitialAppStateSyncOptions, LastMessageList, LTHashState, WAPatchCreate, WAPatchName } from '../Types'
|
import {
|
||||||
|
BaileysEventEmitter,
|
||||||
|
Chat,
|
||||||
|
ChatModification,
|
||||||
|
ChatMutation,
|
||||||
|
ChatUpdate,
|
||||||
|
Contact,
|
||||||
|
InitialAppStateSyncOptions,
|
||||||
|
LastMessageList,
|
||||||
|
LTHashState,
|
||||||
|
WAPatchCreate,
|
||||||
|
WAPatchName
|
||||||
|
} from '../Types'
|
||||||
import { ChatLabelAssociation, LabelAssociationType, MessageLabelAssociation } from '../Types/LabelAssociation'
|
import { ChatLabelAssociation, LabelAssociationType, MessageLabelAssociation } from '../Types/LabelAssociation'
|
||||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidNormalizedUser } from '../WABinary'
|
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidNormalizedUser } from '../WABinary'
|
||||||
import { aesDecrypt, aesEncrypt, hkdf, hmacSign } from './crypto'
|
import { aesDecrypt, aesEncrypt, hkdf, hmacSign } from './crypto'
|
||||||
import { toNumber } from './generics'
|
import { toNumber } from './generics'
|
||||||
import { ILogger } from './logger'
|
import { ILogger } from './logger'
|
||||||
import { LT_HASH_ANTI_TAMPERING } from './lt-hash'
|
import { LT_HASH_ANTI_TAMPERING } from './lt-hash'
|
||||||
import { downloadContentFromMessage, } from './messages-media'
|
import { downloadContentFromMessage } from './messages-media'
|
||||||
|
|
||||||
type FetchAppStateSyncKey = (keyId: string) => Promise<proto.Message.IAppStateSyncKeyData | null | undefined>
|
type FetchAppStateSyncKey = (keyId: string) => Promise<proto.Message.IAppStateSyncKeyData | null | undefined>
|
||||||
|
|
||||||
@@ -25,7 +37,12 @@ const mutationKeys = async(keydata: Uint8Array) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateMac = (operation: proto.SyncdMutation.SyncdOperation, data: Buffer, keyId: Uint8Array | string, key: Buffer) => {
|
const generateMac = (
|
||||||
|
operation: proto.SyncdMutation.SyncdOperation,
|
||||||
|
data: Buffer,
|
||||||
|
keyId: Uint8Array | string,
|
||||||
|
key: Buffer
|
||||||
|
) => {
|
||||||
const getKeyData = () => {
|
const getKeyData = () => {
|
||||||
let r: number
|
let r: number
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
@@ -58,7 +75,7 @@ const to64BitNetworkOrder = (e: number) => {
|
|||||||
return buff
|
return buff
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mac = { indexMac: Uint8Array, valueMac: Uint8Array, operation: proto.SyncdMutation.SyncdOperation }
|
type Mac = { indexMac: Uint8Array; valueMac: Uint8Array; operation: proto.SyncdMutation.SyncdOperation }
|
||||||
|
|
||||||
const makeLtHashGenerator = ({ indexValueMap, hash }: Pick<LTHashState, 'hash' | 'indexValueMap'>) => {
|
const makeLtHashGenerator = ({ indexValueMap, hash }: Pick<LTHashState, 'hash' | 'indexValueMap'>) => {
|
||||||
indexValueMap = { ...indexValueMap }
|
indexValueMap = { ...indexValueMap }
|
||||||
@@ -100,21 +117,18 @@ const makeLtHashGenerator = ({ indexValueMap, hash }: Pick<LTHashState, 'hash' |
|
|||||||
}
|
}
|
||||||
|
|
||||||
const generateSnapshotMac = (lthash: Uint8Array, version: number, name: WAPatchName, key: Buffer) => {
|
const generateSnapshotMac = (lthash: Uint8Array, version: number, name: WAPatchName, key: Buffer) => {
|
||||||
const total = Buffer.concat([
|
const total = Buffer.concat([lthash, to64BitNetworkOrder(version), Buffer.from(name, 'utf-8')])
|
||||||
lthash,
|
|
||||||
to64BitNetworkOrder(version),
|
|
||||||
Buffer.from(name, 'utf-8')
|
|
||||||
])
|
|
||||||
return hmacSign(total, key, 'sha256')
|
return hmacSign(total, key, 'sha256')
|
||||||
}
|
}
|
||||||
|
|
||||||
const generatePatchMac = (snapshotMac: Uint8Array, valueMacs: Uint8Array[], version: number, type: WAPatchName, key: Buffer) => {
|
const generatePatchMac = (
|
||||||
const total = Buffer.concat([
|
snapshotMac: Uint8Array,
|
||||||
snapshotMac,
|
valueMacs: Uint8Array[],
|
||||||
...valueMacs,
|
version: number,
|
||||||
to64BitNetworkOrder(version),
|
type: WAPatchName,
|
||||||
Buffer.from(type, 'utf-8')
|
key: Buffer
|
||||||
])
|
) => {
|
||||||
|
const total = Buffer.concat([snapshotMac, ...valueMacs, to64BitNetworkOrder(version), Buffer.from(type, 'utf-8')])
|
||||||
return hmacSign(total, key)
|
return hmacSign(total, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +214,8 @@ export const decodeSyncdMutations = async(
|
|||||||
// if it's a syncdmutation, get the operation property
|
// if it's a syncdmutation, get the operation property
|
||||||
// otherwise, if it's only a record -- it'll be a SET mutation
|
// otherwise, if it's only a record -- it'll be a SET mutation
|
||||||
const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET
|
const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET
|
||||||
const record = ('record' in msgMutation && !!msgMutation.record) ? msgMutation.record : msgMutation as proto.ISyncdRecord
|
const record =
|
||||||
|
'record' in msgMutation && !!msgMutation.record ? msgMutation.record : (msgMutation as proto.ISyncdRecord)
|
||||||
|
|
||||||
const key = await getKey(record.keyId!.id!)
|
const key = await getKey(record.keyId!.id!)
|
||||||
const content = Buffer.from(record.value!.blob!)
|
const content = Buffer.from(record.value!.blob!)
|
||||||
@@ -239,7 +254,10 @@ export const decodeSyncdMutations = async(
|
|||||||
const base64Key = Buffer.from(keyId).toString('base64')
|
const base64Key = Buffer.from(keyId).toString('base64')
|
||||||
const keyEnc = await getAppStateSyncKey(base64Key)
|
const keyEnc = await getAppStateSyncKey(base64Key)
|
||||||
if (!keyEnc) {
|
if (!keyEnc) {
|
||||||
throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 404, data: { msgMutations } })
|
throw new Boom(`failed to find key "${base64Key}" to decode mutation`, {
|
||||||
|
statusCode: 404,
|
||||||
|
data: { msgMutations }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return mutationKeys(keyEnc.keyData!)
|
return mutationKeys(keyEnc.keyData!)
|
||||||
@@ -264,7 +282,13 @@ export const decodeSyncdPatch = async(
|
|||||||
const mainKey = await mutationKeys(mainKeyObj.keyData!)
|
const mainKey = await 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')
|
||||||
}
|
}
|
||||||
@@ -274,17 +298,15 @@ export const decodeSyncdPatch = async(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extractSyncdPatches = async(
|
export const extractSyncdPatches = async (result: BinaryNode, options: AxiosRequestConfig<{}>) => {
|
||||||
result: BinaryNode,
|
|
||||||
options: AxiosRequestConfig<{}>
|
|
||||||
) => {
|
|
||||||
const syncNode = getBinaryNodeChild(result, 'sync')
|
const syncNode = getBinaryNodeChild(result, 'sync')
|
||||||
const collectionNodes = getBinaryNodeChildren(syncNode, 'collection')
|
const collectionNodes = getBinaryNodeChildren(syncNode, 'collection')
|
||||||
|
|
||||||
const final = {} as { [T in WAPatchName]: { patches: proto.ISyncdPatch[], hasMorePatches: boolean, snapshot?: proto.ISyncdSnapshot } }
|
const final = {} as {
|
||||||
|
[T in WAPatchName]: { patches: proto.ISyncdPatch[]; hasMorePatches: boolean; snapshot?: proto.ISyncdSnapshot }
|
||||||
|
}
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
collectionNodes.map(
|
collectionNodes.map(async collectionNode => {
|
||||||
async collectionNode => {
|
|
||||||
const patchesNode = getBinaryNodeChild(collectionNode, 'patches')
|
const patchesNode = getBinaryNodeChild(collectionNode, 'patches')
|
||||||
|
|
||||||
const patches = getBinaryNodeChildren(patchesNode || collectionNode, 'patch')
|
const patches = getBinaryNodeChildren(patchesNode || collectionNode, 'patch')
|
||||||
@@ -301,9 +323,7 @@ export const extractSyncdPatches = async(
|
|||||||
snapshotNode.content = Buffer.from(Object.values(snapshotNode.content))
|
snapshotNode.content = Buffer.from(Object.values(snapshotNode.content))
|
||||||
}
|
}
|
||||||
|
|
||||||
const blobRef = proto.ExternalBlobReference.decode(
|
const blobRef = proto.ExternalBlobReference.decode(snapshotNode.content as Buffer)
|
||||||
snapshotNode.content as Buffer
|
|
||||||
)
|
|
||||||
const data = await downloadExternalBlob(blobRef, options)
|
const data = await downloadExternalBlob(blobRef, options)
|
||||||
snapshot = proto.SyncdSnapshot.decode(data)
|
snapshot = proto.SyncdSnapshot.decode(data)
|
||||||
}
|
}
|
||||||
@@ -324,18 +344,13 @@ export const extractSyncdPatches = async(
|
|||||||
}
|
}
|
||||||
|
|
||||||
final[name] = { patches: syncds, hasMorePatches, snapshot }
|
final[name] = { patches: syncds, hasMorePatches, snapshot }
|
||||||
}
|
})
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return final
|
return final
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const downloadExternalBlob = async (blob: proto.IExternalBlobReference, options: AxiosRequestConfig<{}>) => {
|
||||||
export const downloadExternalBlob = async(
|
|
||||||
blob: proto.IExternalBlobReference,
|
|
||||||
options: AxiosRequestConfig<{}>
|
|
||||||
) => {
|
|
||||||
const stream = await downloadContentFromMessage(blob, 'md-app-state', { options })
|
const stream = await downloadContentFromMessage(blob, 'md-app-state', { options })
|
||||||
const bufferArray: Buffer[] = []
|
const bufferArray: Buffer[] = []
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
@@ -345,10 +360,7 @@ export const downloadExternalBlob = async(
|
|||||||
return Buffer.concat(bufferArray)
|
return Buffer.concat(bufferArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const downloadExternalPatch = async(
|
export const downloadExternalPatch = async (blob: proto.IExternalBlobReference, options: AxiosRequestConfig<{}>) => {
|
||||||
blob: proto.IExternalBlobReference,
|
|
||||||
options: AxiosRequestConfig<{}>
|
|
||||||
) => {
|
|
||||||
const buffer = await downloadExternalBlob(blob, options)
|
const buffer = await downloadExternalBlob(blob, options)
|
||||||
const syncData = proto.SyncdMutations.decode(buffer)
|
const syncData = proto.SyncdMutations.decode(buffer)
|
||||||
return syncData
|
return syncData
|
||||||
@@ -365,15 +377,14 @@ export const decodeSyncdSnapshot = async(
|
|||||||
newState.version = toNumber(snapshot.version!.version)
|
newState.version = toNumber(snapshot.version!.version)
|
||||||
|
|
||||||
const mutationMap: ChatMutationMap = {}
|
const mutationMap: ChatMutationMap = {}
|
||||||
const areMutationsRequired = typeof minimumVersionNumber === 'undefined'
|
const areMutationsRequired = typeof minimumVersionNumber === 'undefined' || newState.version > minimumVersionNumber
|
||||||
|| newState.version > minimumVersionNumber
|
|
||||||
|
|
||||||
const { hash, indexValueMap } = await decodeSyncdMutations(
|
const { hash, indexValueMap } = await decodeSyncdMutations(
|
||||||
snapshot.records!,
|
snapshot.records!,
|
||||||
newState,
|
newState,
|
||||||
getAppStateSyncKey,
|
getAppStateSyncKey,
|
||||||
areMutationsRequired
|
areMutationsRequired
|
||||||
? (mutation) => {
|
? mutation => {
|
||||||
const index = mutation.syncAction.index?.toString()
|
const index = mutation.syncAction.index?.toString()
|
||||||
mutationMap[index!] = mutation
|
mutationMap[index!] = mutation
|
||||||
}
|
}
|
||||||
@@ -444,7 +455,7 @@ export const decodePatches = async(
|
|||||||
const index = mutation.syncAction.index?.toString()
|
const index = mutation.syncAction.index?.toString()
|
||||||
mutationMap[index!] = mutation
|
mutationMap[index!] = mutation
|
||||||
}
|
}
|
||||||
: (() => { }),
|
: () => {},
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -472,10 +483,7 @@ export const decodePatches = async(
|
|||||||
return { state: newState, mutationMap }
|
return { state: newState, mutationMap }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const chatModificationToAppPatch = (
|
export const chatModificationToAppPatch = (mod: ChatModification, jid: string) => {
|
||||||
mod: ChatModification,
|
|
||||||
jid: string
|
|
||||||
) => {
|
|
||||||
const OP = proto.SyncdMutation.SyncdOperation
|
const OP = proto.SyncdMutation.SyncdOperation
|
||||||
const getMessageRange = (lastMessages: LastMessageList) => {
|
const getMessageRange = (lastMessages: LastMessageList) => {
|
||||||
let messageRange: proto.SyncActionValue.ISyncActionMessageRange
|
let messageRange: proto.SyncActionValue.ISyncActionMessageRange
|
||||||
@@ -483,8 +491,8 @@ export const chatModificationToAppPatch = (
|
|||||||
const lastMsg = lastMessages[lastMessages.length - 1]
|
const lastMsg = lastMessages[lastMessages.length - 1]
|
||||||
messageRange = {
|
messageRange = {
|
||||||
lastMessageTimestamp: lastMsg?.messageTimestamp,
|
lastMessageTimestamp: lastMsg?.messageTimestamp,
|
||||||
messages: lastMessages?.length ? lastMessages.map(
|
messages: lastMessages?.length
|
||||||
m => {
|
? lastMessages.map(m => {
|
||||||
if (!m.key?.id || !m.key?.remoteJid) {
|
if (!m.key?.id || !m.key?.remoteJid) {
|
||||||
throw new Boom('Incomplete key', { statusCode: 400, data: m })
|
throw new Boom('Incomplete key', { statusCode: 400, data: m })
|
||||||
}
|
}
|
||||||
@@ -502,8 +510,8 @@ export const chatModificationToAppPatch = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
})
|
||||||
) : undefined
|
: undefined
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
messageRange = lastMessages
|
messageRange = lastMessages
|
||||||
@@ -605,7 +613,7 @@ export const chatModificationToAppPatch = (
|
|||||||
patch = {
|
patch = {
|
||||||
syncAction: {
|
syncAction: {
|
||||||
deleteChatAction: {
|
deleteChatAction: {
|
||||||
messageRange: getMessageRange(mod.lastMessages),
|
messageRange: getMessageRange(mod.lastMessages)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
index: ['deleteChat', jid, '1'],
|
index: ['deleteChat', jid, '1'],
|
||||||
@@ -623,7 +631,7 @@ export const chatModificationToAppPatch = (
|
|||||||
index: ['setting_pushName'],
|
index: ['setting_pushName'],
|
||||||
type: 'critical_block',
|
type: 'critical_block',
|
||||||
apiVersion: 1,
|
apiVersion: 1,
|
||||||
operation: OP.SET,
|
operation: OP.SET
|
||||||
}
|
}
|
||||||
} else if ('addLabel' in mod) {
|
} else if ('addLabel' in mod) {
|
||||||
patch = {
|
patch = {
|
||||||
@@ -638,56 +646,49 @@ export const chatModificationToAppPatch = (
|
|||||||
index: ['label_edit', mod.addLabel.id],
|
index: ['label_edit', mod.addLabel.id],
|
||||||
type: 'regular',
|
type: 'regular',
|
||||||
apiVersion: 3,
|
apiVersion: 3,
|
||||||
operation: OP.SET,
|
operation: OP.SET
|
||||||
}
|
}
|
||||||
} else if ('addChatLabel' in mod) {
|
} else if ('addChatLabel' in mod) {
|
||||||
patch = {
|
patch = {
|
||||||
syncAction: {
|
syncAction: {
|
||||||
labelAssociationAction: {
|
labelAssociationAction: {
|
||||||
labeled: true,
|
labeled: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
index: [LabelAssociationType.Chat, mod.addChatLabel.labelId, jid],
|
index: [LabelAssociationType.Chat, mod.addChatLabel.labelId, jid],
|
||||||
type: 'regular',
|
type: 'regular',
|
||||||
apiVersion: 3,
|
apiVersion: 3,
|
||||||
operation: OP.SET,
|
operation: OP.SET
|
||||||
}
|
}
|
||||||
} else if ('removeChatLabel' in mod) {
|
} else if ('removeChatLabel' in mod) {
|
||||||
patch = {
|
patch = {
|
||||||
syncAction: {
|
syncAction: {
|
||||||
labelAssociationAction: {
|
labelAssociationAction: {
|
||||||
labeled: false,
|
labeled: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
index: [LabelAssociationType.Chat, mod.removeChatLabel.labelId, jid],
|
index: [LabelAssociationType.Chat, mod.removeChatLabel.labelId, jid],
|
||||||
type: 'regular',
|
type: 'regular',
|
||||||
apiVersion: 3,
|
apiVersion: 3,
|
||||||
operation: OP.SET,
|
operation: OP.SET
|
||||||
}
|
}
|
||||||
} else if ('addMessageLabel' in mod) {
|
} else if ('addMessageLabel' in mod) {
|
||||||
patch = {
|
patch = {
|
||||||
syncAction: {
|
syncAction: {
|
||||||
labelAssociationAction: {
|
labelAssociationAction: {
|
||||||
labeled: true,
|
labeled: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
index: [
|
index: [LabelAssociationType.Message, mod.addMessageLabel.labelId, jid, mod.addMessageLabel.messageId, '0', '0'],
|
||||||
LabelAssociationType.Message,
|
|
||||||
mod.addMessageLabel.labelId,
|
|
||||||
jid,
|
|
||||||
mod.addMessageLabel.messageId,
|
|
||||||
'0',
|
|
||||||
'0'
|
|
||||||
],
|
|
||||||
type: 'regular',
|
type: 'regular',
|
||||||
apiVersion: 3,
|
apiVersion: 3,
|
||||||
operation: OP.SET,
|
operation: OP.SET
|
||||||
}
|
}
|
||||||
} else if ('removeMessageLabel' in mod) {
|
} else if ('removeMessageLabel' in mod) {
|
||||||
patch = {
|
patch = {
|
||||||
syncAction: {
|
syncAction: {
|
||||||
labelAssociationAction: {
|
labelAssociationAction: {
|
||||||
labeled: false,
|
labeled: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
index: [
|
index: [
|
||||||
@@ -700,7 +701,7 @@ export const chatModificationToAppPatch = (
|
|||||||
],
|
],
|
||||||
type: 'regular',
|
type: 'regular',
|
||||||
apiVersion: 3,
|
apiVersion: 3,
|
||||||
operation: OP.SET,
|
operation: OP.SET
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Boom('not supported')
|
throw new Boom('not supported')
|
||||||
@@ -716,7 +717,7 @@ export const processSyncAction = (
|
|||||||
ev: BaileysEventEmitter,
|
ev: BaileysEventEmitter,
|
||||||
me: Contact,
|
me: Contact,
|
||||||
initialSyncOpts?: InitialAppStateSyncOptions,
|
initialSyncOpts?: InitialAppStateSyncOptions,
|
||||||
logger?: ILogger,
|
logger?: ILogger
|
||||||
) => {
|
) => {
|
||||||
const isInitialSync = !!initialSyncOpts
|
const isInitialSync = !!initialSyncOpts
|
||||||
const accountSettings = initialSyncOpts?.accountSettings
|
const accountSettings = initialSyncOpts?.accountSettings
|
||||||
@@ -729,18 +730,13 @@ export const processSyncAction = (
|
|||||||
} = syncAction
|
} = syncAction
|
||||||
|
|
||||||
if (action?.muteAction) {
|
if (action?.muteAction) {
|
||||||
ev.emit(
|
ev.emit('chats.update', [
|
||||||
'chats.update',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
muteEndTime: action.muteAction?.muted
|
muteEndTime: action.muteAction?.muted ? toNumber(action.muteAction.muteEndTimestamp) : null,
|
||||||
? toNumber(action.muteAction.muteEndTimestamp)
|
|
||||||
: null,
|
|
||||||
conditional: getChatUpdateConditional(id, undefined)
|
conditional: getChatUpdateConditional(id, undefined)
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
)
|
|
||||||
} else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
|
} else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
|
||||||
// okay so we've to do some annoying computation here
|
// okay so we've to do some annoying computation here
|
||||||
// when we're initially syncing the app state
|
// when we're initially syncing the app state
|
||||||
@@ -753,9 +749,7 @@ export const processSyncAction = (
|
|||||||
// 2. if the account unarchiveChats setting is false -- then it doesn't matter,
|
// 2. if the account unarchiveChats setting is false -- then it doesn't matter,
|
||||||
// it'll always take an app state action to mark in unarchived -- which we'll get anyway
|
// it'll always take an app state action to mark in unarchived -- which we'll get anyway
|
||||||
const archiveAction = action?.archiveChatAction
|
const archiveAction = action?.archiveChatAction
|
||||||
const isArchived = archiveAction
|
const isArchived = archiveAction ? archiveAction.archived : type === 'archive'
|
||||||
? archiveAction.archived
|
|
||||||
: type === 'archive'
|
|
||||||
// // 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
|
||||||
// if(isInitialSync && !isArchived) {
|
// if(isInitialSync && !isArchived) {
|
||||||
@@ -765,11 +759,13 @@ export const processSyncAction = (
|
|||||||
const msgRange = !accountSettings?.unarchiveChats ? undefined : archiveAction?.messageRange
|
const msgRange = !accountSettings?.unarchiveChats ? undefined : archiveAction?.messageRange
|
||||||
// logger?.debug({ chat: id, syncAction }, 'message range archive')
|
// logger?.debug({ chat: id, syncAction }, 'message range archive')
|
||||||
|
|
||||||
ev.emit('chats.update', [{
|
ev.emit('chats.update', [
|
||||||
|
{
|
||||||
id,
|
id,
|
||||||
archived: isArchived,
|
archived: isArchived,
|
||||||
conditional: getChatUpdateConditional(id, msgRange)
|
conditional: getChatUpdateConditional(id, msgRange)
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
} else if (action?.markChatAsReadAction) {
|
} else if (action?.markChatAsReadAction) {
|
||||||
const markReadAction = action.markChatAsReadAction
|
const markReadAction = action.markChatAsReadAction
|
||||||
// basically we don't need to fire an "read" update if the chat is being marked as read
|
// basically we don't need to fire an "read" update if the chat is being marked as read
|
||||||
@@ -777,11 +773,13 @@ export const processSyncAction = (
|
|||||||
// this only applies for the initial sync
|
// this only applies for the initial sync
|
||||||
const isNullUpdate = isInitialSync && markReadAction.read
|
const isNullUpdate = isInitialSync && markReadAction.read
|
||||||
|
|
||||||
ev.emit('chats.update', [{
|
ev.emit('chats.update', [
|
||||||
|
{
|
||||||
id,
|
id,
|
||||||
unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1,
|
unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1,
|
||||||
conditional: getChatUpdateConditional(id, markReadAction?.messageRange)
|
conditional: getChatUpdateConditional(id, markReadAction?.messageRange)
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
} else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
|
} else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
|
||||||
ev.emit('messages.delete', {
|
ev.emit('messages.delete', {
|
||||||
keys: [
|
keys: [
|
||||||
@@ -800,11 +798,13 @@ export const processSyncAction = (
|
|||||||
ev.emit('creds.update', { me: { ...me, name } })
|
ev.emit('creds.update', { me: { ...me, name } })
|
||||||
}
|
}
|
||||||
} else if (action?.pinAction) {
|
} else if (action?.pinAction) {
|
||||||
ev.emit('chats.update', [{
|
ev.emit('chats.update', [
|
||||||
|
{
|
||||||
id,
|
id,
|
||||||
pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null,
|
pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null,
|
||||||
conditional: getChatUpdateConditional(id, undefined)
|
conditional: getChatUpdateConditional(id, undefined)
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
} 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 } })
|
||||||
@@ -841,29 +841,31 @@ export const processSyncAction = (
|
|||||||
})
|
})
|
||||||
} else if (action?.labelAssociationAction) {
|
} else if (action?.labelAssociationAction) {
|
||||||
ev.emit('labels.association', {
|
ev.emit('labels.association', {
|
||||||
type: action.labelAssociationAction.labeled
|
type: action.labelAssociationAction.labeled ? 'add' : 'remove',
|
||||||
? 'add'
|
association:
|
||||||
: 'remove',
|
type === LabelAssociationType.Chat
|
||||||
association: type === LabelAssociationType.Chat
|
? ({
|
||||||
? {
|
|
||||||
type: LabelAssociationType.Chat,
|
type: LabelAssociationType.Chat,
|
||||||
chatId: syncAction.index[2],
|
chatId: syncAction.index[2],
|
||||||
labelId: syncAction.index[1]
|
labelId: syncAction.index[1]
|
||||||
} as ChatLabelAssociation
|
} as ChatLabelAssociation)
|
||||||
: {
|
: ({
|
||||||
type: LabelAssociationType.Message,
|
type: LabelAssociationType.Message,
|
||||||
chatId: syncAction.index[2],
|
chatId: syncAction.index[2],
|
||||||
messageId: syncAction.index[3],
|
messageId: syncAction.index[3],
|
||||||
labelId: syncAction.index[1]
|
labelId: syncAction.index[1]
|
||||||
} as MessageLabelAssociation
|
} as MessageLabelAssociation)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
logger?.debug({ syncAction, id }, 'unprocessable update')
|
logger?.debug({ syncAction, id }, 'unprocessable update')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChatUpdateConditional(id: string, msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined): ChatUpdate['conditional'] {
|
function getChatUpdateConditional(
|
||||||
|
id: string,
|
||||||
|
msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined
|
||||||
|
): ChatUpdate['conditional'] {
|
||||||
return isInitialSync
|
return isInitialSync
|
||||||
? (data) => {
|
? data => {
|
||||||
const chat = data.historySets.chats[id] || data.chatUpserts[id]
|
const chat = data.historySets.chats[id] || data.chatUpserts[id]
|
||||||
if (chat) {
|
if (chat) {
|
||||||
return msgRange ? isValidPatchBasedOnMessageRange(chat, msgRange) : true
|
return msgRange ? isValidPatchBasedOnMessageRange(chat, msgRange) : true
|
||||||
@@ -872,7 +874,10 @@ export const processSyncAction = (
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidPatchBasedOnMessageRange(chat: Chat, msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined) {
|
function isValidPatchBasedOnMessageRange(
|
||||||
|
chat: Chat,
|
||||||
|
msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined
|
||||||
|
) {
|
||||||
const lastMsgTimestamp = Number(msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0)
|
const lastMsgTimestamp = Number(msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0)
|
||||||
const chatLastMsgTimestamp = Number(chat?.lastMessageRecvTimestamp || 0)
|
const chatLastMsgTimestamp = Number(chat?.lastMessageRecvTimestamp || 0)
|
||||||
return lastMsgTimestamp >= chatLastMsgTimestamp
|
return lastMsgTimestamp >= chatLastMsgTimestamp
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ import { KeyPair } from '../Types'
|
|||||||
const { subtle } = globalThis.crypto
|
const { subtle } = globalThis.crypto
|
||||||
|
|
||||||
/** prefix version byte to the pub keys, required for some curve crypto functions */
|
/** prefix version byte to the pub keys, required for some curve crypto functions */
|
||||||
export const generateSignalPubKey = (pubKey: Uint8Array | Buffer) => (
|
export const generateSignalPubKey = (pubKey: Uint8Array | Buffer) =>
|
||||||
pubKey.length === 33
|
pubKey.length === 33 ? pubKey : Buffer.concat([KEY_BUNDLE_TYPE, pubKey])
|
||||||
? pubKey
|
|
||||||
: Buffer.concat([ KEY_BUNDLE_TYPE, pubKey ])
|
|
||||||
)
|
|
||||||
|
|
||||||
export const Curve = {
|
export const Curve = {
|
||||||
generateKeyPair: (): KeyPair => {
|
generateKeyPair: (): KeyPair => {
|
||||||
@@ -26,9 +23,7 @@ export const Curve = {
|
|||||||
const shared = libsignal.curve.calculateAgreement(generateSignalPubKey(publicKey), privateKey)
|
const shared = libsignal.curve.calculateAgreement(generateSignalPubKey(publicKey), privateKey)
|
||||||
return Buffer.from(shared)
|
return Buffer.from(shared)
|
||||||
},
|
},
|
||||||
sign: (privateKey: Uint8Array, buf: Uint8Array) => (
|
sign: (privateKey: Uint8Array, buf: Uint8Array) => libsignal.curve.calculateSignature(privateKey, buf),
|
||||||
libsignal.curve.calculateSignature(privateKey, buf)
|
|
||||||
),
|
|
||||||
verify: (pubKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => {
|
verify: (pubKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => {
|
||||||
try {
|
try {
|
||||||
libsignal.curve.verifySignature(generateSignalPubKey(pubKey), message, signature)
|
libsignal.curve.verifySignature(generateSignalPubKey(pubKey), message, signature)
|
||||||
@@ -111,7 +106,11 @@ export function aesEncrypWithIV(buffer: Buffer, key: Buffer, IV: Buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sign HMAC using SHA 256
|
// sign HMAC using SHA 256
|
||||||
export function hmacSign(buffer: Buffer | Uint8Array, key: Buffer | Uint8Array, variant: 'sha256' | 'sha512' = 'sha256') {
|
export function hmacSign(
|
||||||
|
buffer: Buffer | Uint8Array,
|
||||||
|
key: Buffer | Uint8Array,
|
||||||
|
variant: 'sha256' | 'sha512' = 'sha256'
|
||||||
|
) {
|
||||||
return createHmac(variant, key).update(buffer).digest()
|
return createHmac(variant, key).update(buffer).digest()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,27 +126,17 @@ export function md5(buffer: Buffer) {
|
|||||||
export async function hkdf(
|
export async function hkdf(
|
||||||
buffer: Uint8Array | Buffer,
|
buffer: Uint8Array | Buffer,
|
||||||
expandedLength: number,
|
expandedLength: number,
|
||||||
info: { salt?: Buffer, info?: string }
|
info: { salt?: Buffer; info?: string }
|
||||||
): Promise<Buffer> {
|
): Promise<Buffer> {
|
||||||
// Ensure we have a Uint8Array for the key material
|
// Ensure we have a Uint8Array for the key material
|
||||||
const inputKeyMaterial = buffer instanceof Uint8Array
|
const inputKeyMaterial = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
|
||||||
? buffer
|
|
||||||
: new Uint8Array(buffer)
|
|
||||||
|
|
||||||
// Set default values if not provided
|
// Set default values if not provided
|
||||||
const salt = info.salt ? new Uint8Array(info.salt) : new Uint8Array(0)
|
const salt = info.salt ? new Uint8Array(info.salt) : new Uint8Array(0)
|
||||||
const infoBytes = info.info
|
const infoBytes = info.info ? new TextEncoder().encode(info.info) : new Uint8Array(0)
|
||||||
? new TextEncoder().encode(info.info)
|
|
||||||
: new Uint8Array(0)
|
|
||||||
|
|
||||||
// Import the input key material
|
// Import the input key material
|
||||||
const importedKey = await subtle.importKey(
|
const importedKey = await subtle.importKey('raw', inputKeyMaterial, { name: 'HKDF' }, false, ['deriveBits'])
|
||||||
'raw',
|
|
||||||
inputKeyMaterial,
|
|
||||||
{ name: 'HKDF' },
|
|
||||||
false,
|
|
||||||
['deriveBits']
|
|
||||||
)
|
|
||||||
|
|
||||||
// Derive bits using HKDF
|
// Derive bits using HKDF
|
||||||
const derivedBits = await subtle.deriveBits(
|
const derivedBits = await subtle.deriveBits(
|
||||||
@@ -164,7 +153,6 @@ export async function hkdf(
|
|||||||
return Buffer.from(derivedBits)
|
return Buffer.from(derivedBits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function derivePairingCodeKey(pairingCode: string, salt: Buffer): Promise<Buffer> {
|
export async function derivePairingCodeKey(pairingCode: string, salt: Buffer): Promise<Buffer> {
|
||||||
// Convert inputs to formats Web Crypto API can work with
|
// Convert inputs to formats Web Crypto API can work with
|
||||||
const encoder = new TextEncoder()
|
const encoder = new TextEncoder()
|
||||||
@@ -172,13 +160,7 @@ export async function derivePairingCodeKey(pairingCode: string, salt: Buffer): P
|
|||||||
const saltBuffer = salt instanceof Uint8Array ? salt : new Uint8Array(salt)
|
const saltBuffer = salt instanceof Uint8Array ? salt : new Uint8Array(salt)
|
||||||
|
|
||||||
// Import the pairing code as key material
|
// Import the pairing code as key material
|
||||||
const keyMaterial = await subtle.importKey(
|
const keyMaterial = await subtle.importKey('raw', pairingCodeBuffer, { name: 'PBKDF2' }, false, ['deriveBits'])
|
||||||
'raw',
|
|
||||||
pairingCodeBuffer,
|
|
||||||
{ name: 'PBKDF2' },
|
|
||||||
false,
|
|
||||||
['deriveBits']
|
|
||||||
)
|
|
||||||
|
|
||||||
// Derive bits using PBKDF2 with the same parameters
|
// Derive bits using PBKDF2 with the same parameters
|
||||||
// 2 << 16 = 131,072 iterations
|
// 2 << 16 = 131,072 iterations
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { SignalRepository, WAMessageKey } from '../Types'
|
import { SignalRepository, WAMessageKey } from '../Types'
|
||||||
import { areJidsSameUser, BinaryNode, isJidBroadcast, isJidGroup, isJidMetaIa, isJidNewsletter, isJidStatusBroadcast, isJidUser, isLidUser } from '../WABinary'
|
import {
|
||||||
|
areJidsSameUser,
|
||||||
|
BinaryNode,
|
||||||
|
isJidBroadcast,
|
||||||
|
isJidGroup,
|
||||||
|
isJidMetaIa,
|
||||||
|
isJidNewsletter,
|
||||||
|
isJidStatusBroadcast,
|
||||||
|
isJidUser,
|
||||||
|
isLidUser
|
||||||
|
} from '../WABinary'
|
||||||
import { unpadRandomMax16 } from './generics'
|
import { unpadRandomMax16 } from './generics'
|
||||||
import { ILogger } from './logger'
|
import { ILogger } from './logger'
|
||||||
|
|
||||||
@@ -24,17 +34,20 @@ export const NACK_REASONS = {
|
|||||||
DBOperationFailed: 552
|
DBOperationFailed: 552
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageType = 'chat' | 'peer_broadcast' | 'other_broadcast' | 'group' | 'direct_peer_status' | 'other_status' | 'newsletter'
|
type MessageType =
|
||||||
|
| 'chat'
|
||||||
|
| 'peer_broadcast'
|
||||||
|
| 'other_broadcast'
|
||||||
|
| 'group'
|
||||||
|
| 'direct_peer_status'
|
||||||
|
| 'other_status'
|
||||||
|
| 'newsletter'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode the received node as a message.
|
* Decode the received node as a message.
|
||||||
* @note this will only parse the message, not decrypt it
|
* @note this will only parse the message, not decrypt it
|
||||||
*/
|
*/
|
||||||
export function decodeMessageNode(
|
export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: string) {
|
||||||
stanza: BinaryNode,
|
|
||||||
meId: string,
|
|
||||||
meLid: string
|
|
||||||
) {
|
|
||||||
let msgType: MessageType
|
let msgType: MessageType
|
||||||
let chatId: string
|
let chatId: string
|
||||||
let author: string
|
let author: string
|
||||||
@@ -178,7 +191,9 @@ export const decryptMessageNode = (
|
|||||||
throw new Error(`Unknown e2e type: ${e2eType}`)
|
throw new Error(`Unknown e2e type: ${e2eType}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg: proto.IMessage = proto.Message.decode(e2eType !== 'plaintext' ? unpadRandomMax16(msgBuffer) : msgBuffer)
|
let msg: proto.IMessage = proto.Message.decode(
|
||||||
|
e2eType !== 'plaintext' ? unpadRandomMax16(msgBuffer) : msgBuffer
|
||||||
|
)
|
||||||
msg = msg.deviceSentMessage?.message || msg
|
msg = msg.deviceSentMessage?.message || msg
|
||||||
if (msg.senderKeyDistributionMessage) {
|
if (msg.senderKeyDistributionMessage) {
|
||||||
//eslint-disable-next-line max-depth
|
//eslint-disable-next-line max-depth
|
||||||
@@ -198,10 +213,7 @@ export const decryptMessageNode = (
|
|||||||
fullMessage.message = msg
|
fullMessage.message = msg
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(
|
logger.error({ key: fullMessage.key, err }, 'failed to decrypt message')
|
||||||
{ key: fullMessage.key, err },
|
|
||||||
'failed to decrypt message'
|
|
||||||
)
|
|
||||||
fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
|
fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
|
||||||
fullMessage.messageStubParameters = [err.message]
|
fullMessage.messageStubParameters = [err.message]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { BaileysEvent, BaileysEventEmitter, BaileysEventMap, BufferedEventData, Chat, ChatUpdate, Contact, WAMessage, WAMessageStatus } from '../Types'
|
import {
|
||||||
|
BaileysEvent,
|
||||||
|
BaileysEventEmitter,
|
||||||
|
BaileysEventMap,
|
||||||
|
BufferedEventData,
|
||||||
|
Chat,
|
||||||
|
ChatUpdate,
|
||||||
|
Contact,
|
||||||
|
WAMessage,
|
||||||
|
WAMessageStatus
|
||||||
|
} from '../Types'
|
||||||
import { trimUndefined } from './generics'
|
import { trimUndefined } from './generics'
|
||||||
import { ILogger } from './logger'
|
import { ILogger } from './logger'
|
||||||
import { updateMessageWithReaction, updateMessageWithReceipt } from './messages'
|
import { updateMessageWithReaction, updateMessageWithReceipt } from './messages'
|
||||||
@@ -18,10 +28,10 @@ const BUFFERABLE_EVENT = [
|
|||||||
'messages.delete',
|
'messages.delete',
|
||||||
'messages.reaction',
|
'messages.reaction',
|
||||||
'message-receipt.update',
|
'message-receipt.update',
|
||||||
'groups.update',
|
'groups.update'
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
type BufferableEvent = typeof BUFFERABLE_EVENT[number]
|
type BufferableEvent = (typeof BUFFERABLE_EVENT)[number]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map that contains a list of all events that have been triggered
|
* A map that contains a list of all events that have been triggered
|
||||||
@@ -36,14 +46,14 @@ const BUFFERABLE_EVENT_SET = new Set<BaileysEvent>(BUFFERABLE_EVENT)
|
|||||||
|
|
||||||
type BaileysBufferableEventEmitter = BaileysEventEmitter & {
|
type BaileysBufferableEventEmitter = BaileysEventEmitter & {
|
||||||
/** Use to process events in a batch */
|
/** Use to process events in a batch */
|
||||||
process(handler: (events: BaileysEventData) => void | Promise<void>): (() => void)
|
process(handler: (events: BaileysEventData) => void | Promise<void>): () => void
|
||||||
/**
|
/**
|
||||||
* starts buffering events, call flush() to release them
|
* starts buffering events, call flush() to release them
|
||||||
* */
|
* */
|
||||||
buffer(): void
|
buffer(): void
|
||||||
/** buffers all events till the promise completes */
|
/** buffers all events till the promise completes */
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
createBufferedFunction<A extends any[], T>(work: (...args: A) => Promise<T>): ((...args: A) => Promise<T>)
|
createBufferedFunction<A extends any[], T>(work: (...args: A) => Promise<T>): (...args: A) => Promise<T>
|
||||||
/**
|
/**
|
||||||
* flushes all buffered events
|
* flushes all buffered events
|
||||||
* @param force if true, will flush all data regardless of any pending buffers
|
* @param force if true, will flush all data regardless of any pending buffers
|
||||||
@@ -112,10 +122,7 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
|
|||||||
|
|
||||||
data = newData
|
data = newData
|
||||||
|
|
||||||
logger.trace(
|
logger.trace({ conditionalChatUpdatesLeft }, 'released buffered events')
|
||||||
{ conditionalChatUpdatesLeft },
|
|
||||||
'released buffered events'
|
|
||||||
)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -157,7 +164,7 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter
|
|||||||
},
|
},
|
||||||
on: (...args) => ev.on(...args),
|
on: (...args) => ev.on(...args),
|
||||||
off: (...args) => ev.off(...args),
|
off: (...args) => ev.off(...args),
|
||||||
removeAllListeners: (...args) => ev.removeAllListeners(...args),
|
removeAllListeners: (...args) => ev.removeAllListeners(...args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +313,6 @@ function append<E extends BufferableEvent>(
|
|||||||
|
|
||||||
if (data.chatUpserts[chatId]) {
|
if (data.chatUpserts[chatId]) {
|
||||||
delete data.chatUpserts[chatId]
|
delete data.chatUpserts[chatId]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.historySets.chats[chatId]) {
|
if (data.historySets.chats[chatId]) {
|
||||||
@@ -382,9 +388,7 @@ function append<E extends BufferableEvent>(
|
|||||||
} else {
|
} else {
|
||||||
data.messageUpserts[key] = {
|
data.messageUpserts[key] = {
|
||||||
message,
|
message,
|
||||||
type: type === 'notify' || data.messageUpserts[key]?.type === 'notify'
|
type: type === 'notify' || data.messageUpserts[key]?.type === 'notify' ? 'notify' : type
|
||||||
? 'notify'
|
|
||||||
: type
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,7 +423,6 @@ function append<E extends BufferableEvent>(
|
|||||||
const keyStr = stringifyMessageKey(key)
|
const keyStr = stringifyMessageKey(key)
|
||||||
if (!data.messageDeletes[keyStr]) {
|
if (!data.messageDeletes[keyStr]) {
|
||||||
data.messageDeletes[keyStr] = key
|
data.messageDeletes[keyStr] = key
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.messageUpserts[keyStr]) {
|
if (data.messageUpserts[keyStr]) {
|
||||||
@@ -443,8 +446,7 @@ function append<E extends BufferableEvent>(
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
updateMessageWithReaction(existing.message, reaction)
|
updateMessageWithReaction(existing.message, reaction)
|
||||||
} else {
|
} else {
|
||||||
data.messageReactions[keyStr] = data.messageReactions[keyStr]
|
data.messageReactions[keyStr] = data.messageReactions[keyStr] || { key, reactions: [] }
|
||||||
|| { key, reactions: [] }
|
|
||||||
updateMessageWithReaction(data.messageReactions[keyStr], reaction)
|
updateMessageWithReaction(data.messageReactions[keyStr], reaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -458,8 +460,7 @@ function append<E extends BufferableEvent>(
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
updateMessageWithReceipt(existing.message, receipt)
|
updateMessageWithReceipt(existing.message, receipt)
|
||||||
} else {
|
} else {
|
||||||
data.messageReceipts[keyStr] = data.messageReceipts[keyStr]
|
data.messageReceipts[keyStr] = data.messageReceipts[keyStr] || { key, userReceipt: [] }
|
||||||
|| { key, userReceipt: [] }
|
|
||||||
updateMessageWithReceipt(data.messageReceipts[keyStr], receipt)
|
updateMessageWithReceipt(data.messageReceipts[keyStr], receipt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,7 +473,6 @@ function append<E extends BufferableEvent>(
|
|||||||
const groupUpdate = data.groupUpdates[id] || {}
|
const groupUpdate = data.groupUpdates[id] || {}
|
||||||
if (!data.groupUpdates[id]) {
|
if (!data.groupUpdates[id]) {
|
||||||
data.groupUpdates[id] = Object.assign(groupUpdate, update)
|
data.groupUpdates[id] = Object.assign(groupUpdate, update)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,10 +504,10 @@ function append<E extends BufferableEvent>(
|
|||||||
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 === 'number'
|
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')
|
||||||
chat.unreadCount -= 1
|
chat.unreadCount -= 1
|
||||||
@@ -567,15 +567,15 @@ function consolidateEvents(data: BufferedEventData) {
|
|||||||
map['messages.delete'] = { keys: messageDeleteList }
|
map['messages.delete'] = { keys: messageDeleteList }
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageReactionList = Object.values(data.messageReactions).flatMap(
|
const messageReactionList = Object.values(data.messageReactions).flatMap(({ key, reactions }) =>
|
||||||
({ key, reactions }) => reactions.flatMap(reaction => ({ key, reaction }))
|
reactions.flatMap(reaction => ({ key, reaction }))
|
||||||
)
|
)
|
||||||
if (messageReactionList.length) {
|
if (messageReactionList.length) {
|
||||||
map['messages.reaction'] = messageReactionList
|
map['messages.reaction'] = messageReactionList
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageReceiptList = Object.values(data.messageReceipts).flatMap(
|
const messageReceiptList = Object.values(data.messageReceipts).flatMap(({ key, userReceipt }) =>
|
||||||
({ key, userReceipt }) => userReceipt.flatMap(receipt => ({ key, receipt }))
|
userReceipt.flatMap(receipt => ({ key, receipt }))
|
||||||
)
|
)
|
||||||
if (messageReceiptList.length) {
|
if (messageReceiptList.length) {
|
||||||
map['message-receipt.update'] = messageReceiptList
|
map['message-receipt.update'] = messageReceiptList
|
||||||
@@ -600,8 +600,10 @@ function consolidateEvents(data: BufferedEventData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function concatChats<C extends Partial<Chat>>(a: C, b: Partial<Chat>) {
|
function concatChats<C extends Partial<Chat>>(a: C, b: Partial<Chat>) {
|
||||||
if(b.unreadCount === null && // neutralize unread counter
|
if (
|
||||||
a.unreadCount! < 0) {
|
b.unreadCount === null && // neutralize unread counter
|
||||||
|
a.unreadCount! < 0
|
||||||
|
) {
|
||||||
a.unreadCount = undefined
|
a.unreadCount = undefined
|
||||||
b.unreadCount = undefined
|
b.unreadCount = undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,34 @@ import { createHash, randomBytes } from 'crypto'
|
|||||||
import { platform, release } from 'os'
|
import { platform, release } from 'os'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { version as baileysVersion } from '../Defaults/baileys-version.json'
|
import { version as baileysVersion } from '../Defaults/baileys-version.json'
|
||||||
import { BaileysEventEmitter, BaileysEventMap, BrowsersMap, ConnectionState, DisconnectReason, WACallUpdateType, WAVersion } from '../Types'
|
import {
|
||||||
|
BaileysEventEmitter,
|
||||||
|
BaileysEventMap,
|
||||||
|
BrowsersMap,
|
||||||
|
ConnectionState,
|
||||||
|
DisconnectReason,
|
||||||
|
WACallUpdateType,
|
||||||
|
WAVersion
|
||||||
|
} from '../Types'
|
||||||
import { BinaryNode, getAllBinaryNodeChildren, jidDecode } from '../WABinary'
|
import { BinaryNode, getAllBinaryNodeChildren, jidDecode } from '../WABinary'
|
||||||
|
|
||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
'aix': 'AIX',
|
aix: 'AIX',
|
||||||
'darwin': 'Mac OS',
|
darwin: 'Mac OS',
|
||||||
'win32': 'Windows',
|
win32: 'Windows',
|
||||||
'android': 'Android',
|
android: 'Android',
|
||||||
'freebsd': 'FreeBSD',
|
freebsd: 'FreeBSD',
|
||||||
'openbsd': 'OpenBSD',
|
openbsd: 'OpenBSD',
|
||||||
'sunos': 'Solaris'
|
sunos: 'Solaris'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Browsers: BrowsersMap = {
|
export const Browsers: BrowsersMap = {
|
||||||
ubuntu: (browser) => ['Ubuntu', browser, '22.04.4'],
|
ubuntu: browser => ['Ubuntu', browser, '22.04.4'],
|
||||||
macOS: (browser) => ['Mac OS', browser, '14.4.1'],
|
macOS: browser => ['Mac OS', browser, '14.4.1'],
|
||||||
baileys: (browser) => ['Baileys', browser, '6.5.0'],
|
baileys: browser => ['Baileys', browser, '6.5.0'],
|
||||||
windows: (browser) => ['Windows', browser, '10.0.22631'],
|
windows: browser => ['Windows', browser, '10.0.22631'],
|
||||||
/** The appropriate browser based on your OS & release */
|
/** The appropriate browser based on your OS & release */
|
||||||
appropriate: (browser) => [ PLATFORM_MAP[platform()] || 'Ubuntu', browser, release() ]
|
appropriate: browser => [PLATFORM_MAP[platform()] || 'Ubuntu', browser, release()]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPlatformId = (browser: string) => {
|
export const getPlatformId = (browser: string) => {
|
||||||
@@ -52,12 +60,8 @@ export const BufferJSON = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getKeyAuthor = (
|
export const getKeyAuthor = (key: proto.IMessageKey | undefined | null, meId = 'me') =>
|
||||||
key: proto.IMessageKey | undefined | null,
|
|
||||||
meId = 'me'
|
|
||||||
) => (
|
|
||||||
(key?.fromMe ? meId : key?.participant || key?.remoteJid) || ''
|
(key?.fromMe ? meId : key?.participant || key?.remoteJid) || ''
|
||||||
)
|
|
||||||
|
|
||||||
export const writeRandomPadMax16 = (msg: Uint8Array) => {
|
export const writeRandomPadMax16 = (msg: Uint8Array) => {
|
||||||
const pad = randomBytes(1)
|
const pad = randomBytes(1)
|
||||||
@@ -83,11 +87,7 @@ export const unpadRandomMax16 = (e: Uint8Array | Buffer) => {
|
|||||||
return new Uint8Array(t.buffer, t.byteOffset, t.length - r)
|
return new Uint8Array(t.buffer, t.byteOffset, t.length - r)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encodeWAMessage = (message: proto.IMessage) => (
|
export const encodeWAMessage = (message: proto.IMessage) => writeRandomPadMax16(proto.Message.encode(message).finish())
|
||||||
writeRandomPadMax16(
|
|
||||||
proto.Message.encode(message).finish()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const generateRegistrationId = (): number => {
|
export const generateRegistrationId = (): number => {
|
||||||
return Uint16Array.from(randomBytes(2))[0] & 16383
|
return Uint16Array.from(randomBytes(2))[0] & 16383
|
||||||
@@ -104,7 +104,8 @@ export const encodeBigEndian = (e: number, t = 4) => {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toNumber = (t: Long | number | null | undefined): number => ((typeof t === 'object' && t) ? ('toNumber' in t ? t.toNumber() : (t as Long).low) : t || 0)
|
export const toNumber = (t: Long | number | null | undefined): number =>
|
||||||
|
typeof t === 'object' && t ? ('toNumber' in t ? t.toNumber() : (t as Long).low) : t || 0
|
||||||
|
|
||||||
/** 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)
|
||||||
@@ -124,8 +125,8 @@ export const debouncedTimeout = (intervalMs = 1000, task?: () => void) => {
|
|||||||
timeout && clearTimeout(timeout)
|
timeout && clearTimeout(timeout)
|
||||||
timeout = undefined
|
timeout = undefined
|
||||||
},
|
},
|
||||||
setTask: (newTask: () => void) => task = newTask,
|
setTask: (newTask: () => void) => (task = newTask),
|
||||||
setInterval: (newInterval: number) => intervalMs = newInterval
|
setInterval: (newInterval: number) => (intervalMs = newInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +155,10 @@ export const delayCancellable = (ms: number) => {
|
|||||||
return { delay, cancel }
|
return { delay, cancel }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function promiseTimeout<T>(ms: number | undefined, 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)
|
||||||
}
|
}
|
||||||
@@ -164,19 +168,20 @@ export async function promiseTimeout<T>(ms: number | undefined, promise: (resolv
|
|||||||
const { delay, cancel } = delayCancellable(ms)
|
const { delay, cancel } = delayCancellable(ms)
|
||||||
const p = new Promise((resolve, reject) => {
|
const p = new Promise((resolve, reject) => {
|
||||||
delay
|
delay
|
||||||
.then(() => reject(
|
.then(() =>
|
||||||
|
reject(
|
||||||
new Boom('Timed Out', {
|
new Boom('Timed Out', {
|
||||||
statusCode: DisconnectReason.timedOut,
|
statusCode: DisconnectReason.timedOut,
|
||||||
data: {
|
data: {
|
||||||
stack
|
stack
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
))
|
)
|
||||||
|
)
|
||||||
.catch(err => reject(err))
|
.catch(err => reject(err))
|
||||||
|
|
||||||
promise(resolve, reject)
|
promise(resolve, reject)
|
||||||
})
|
}).finally(cancel)
|
||||||
.finally (cancel)
|
|
||||||
return p as Promise<T>
|
return p as Promise<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,34 +213,27 @@ export function bindWaitForEvent<T extends keyof BaileysEventMap>(ev: BaileysEve
|
|||||||
return async (check: (u: BaileysEventMap[T]) => Promise<boolean | undefined>, timeoutMs?: number) => {
|
return async (check: (u: BaileysEventMap[T]) => Promise<boolean | undefined>, timeoutMs?: number) => {
|
||||||
let listener: (item: BaileysEventMap[T]) => void
|
let listener: (item: BaileysEventMap[T]) => void
|
||||||
let closeListener: (state: Partial<ConnectionState>) => void
|
let closeListener: (state: Partial<ConnectionState>) => void
|
||||||
await (
|
await promiseTimeout<void>(timeoutMs, (resolve, reject) => {
|
||||||
promiseTimeout<void>(
|
|
||||||
timeoutMs,
|
|
||||||
(resolve, reject) => {
|
|
||||||
closeListener = ({ connection, lastDisconnect }) => {
|
closeListener = ({ connection, lastDisconnect }) => {
|
||||||
if (connection === 'close') {
|
if (connection === 'close') {
|
||||||
reject(
|
reject(
|
||||||
lastDisconnect?.error
|
lastDisconnect?.error || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||||
|| new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.on('connection.update', closeListener)
|
ev.on('connection.update', closeListener)
|
||||||
listener = async(update) => {
|
listener = async update => {
|
||||||
if (await check(update)) {
|
if (await check(update)) {
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.on(event, listener)
|
ev.on(event, listener)
|
||||||
}
|
}).finally(() => {
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
ev.off(event, listener)
|
ev.off(event, listener)
|
||||||
ev.off('connection.update', closeListener)
|
ev.off('connection.update', closeListener)
|
||||||
})
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,13 +246,10 @@ export const bindWaitForConnectionUpdate = (ev: BaileysEventEmitter) => bindWait
|
|||||||
export const fetchLatestBaileysVersion = async (options: AxiosRequestConfig<{}> = {}) => {
|
export const fetchLatestBaileysVersion = async (options: AxiosRequestConfig<{}> = {}) => {
|
||||||
const URL = 'https://raw.githubusercontent.com/WhiskeySockets/Baileys/master/src/Defaults/baileys-version.json'
|
const URL = 'https://raw.githubusercontent.com/WhiskeySockets/Baileys/master/src/Defaults/baileys-version.json'
|
||||||
try {
|
try {
|
||||||
const result = await axios.get<{ version: WAVersion }>(
|
const result = await axios.get<{ version: WAVersion }>(URL, {
|
||||||
URL,
|
|
||||||
{
|
|
||||||
...options,
|
...options,
|
||||||
responseType: 'json'
|
responseType: 'json'
|
||||||
}
|
})
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
version: result.data.version,
|
version: result.data.version,
|
||||||
isLatest: true
|
isLatest: true
|
||||||
@@ -274,13 +269,10 @@ export const fetchLatestBaileysVersion = async(options: AxiosRequestConfig<{}> =
|
|||||||
*/
|
*/
|
||||||
export const fetchLatestWaWebVersion = async (options: AxiosRequestConfig<{}>) => {
|
export const fetchLatestWaWebVersion = async (options: AxiosRequestConfig<{}>) => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(
|
const { data } = await axios.get('https://web.whatsapp.com/sw.js', {
|
||||||
'https://web.whatsapp.com/sw.js',
|
|
||||||
{
|
|
||||||
...options,
|
...options,
|
||||||
responseType: 'json'
|
responseType: 'json'
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const regex = /\\?"client_revision\\?":\s*(\d+)/
|
const regex = /\\?"client_revision\\?":\s*(\d+)/
|
||||||
const match = data.match(regex)
|
const match = data.match(regex)
|
||||||
@@ -317,9 +309,9 @@ export const generateMdTagPrefix = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_MAP: { [_: string]: proto.WebMessageInfo.Status } = {
|
const STATUS_MAP: { [_: string]: proto.WebMessageInfo.Status } = {
|
||||||
'sender': proto.WebMessageInfo.Status.SERVER_ACK,
|
sender: proto.WebMessageInfo.Status.SERVER_ACK,
|
||||||
'played': proto.WebMessageInfo.Status.PLAYED,
|
played: proto.WebMessageInfo.Status.PLAYED,
|
||||||
'read': proto.WebMessageInfo.Status.READ,
|
read: proto.WebMessageInfo.Status.READ,
|
||||||
'read-self': proto.WebMessageInfo.Status.READ
|
'read-self': proto.WebMessageInfo.Status.READ
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -399,9 +391,10 @@ export const getCodeFromWSError = (error: Error) => {
|
|||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(error as any)?.code?.startsWith('E')
|
(error as any)?.code?.startsWith('E') ||
|
||||||
|| error?.message?.includes('timed out')
|
error?.message?.includes('timed out')
|
||||||
) { // handle ETIMEOUT, ENOTFOUND etc
|
) {
|
||||||
|
// handle ETIMEOUT, ENOTFOUND etc
|
||||||
statusCode = 408
|
statusCode = 408
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ import { downloadContentFromMessage } from './messages-media'
|
|||||||
|
|
||||||
const inflatePromise = promisify(inflate)
|
const inflatePromise = promisify(inflate)
|
||||||
|
|
||||||
export const downloadHistory = async(
|
export const downloadHistory = async (msg: proto.Message.IHistorySyncNotification, options: AxiosRequestConfig<{}>) => {
|
||||||
msg: proto.Message.IHistorySyncNotification,
|
|
||||||
options: AxiosRequestConfig<{}>
|
|
||||||
) => {
|
|
||||||
const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options })
|
const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options })
|
||||||
const bufferArray: Buffer[] = []
|
const bufferArray: Buffer[] = []
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
@@ -62,14 +59,13 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_BSP
|
(message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_BSP ||
|
||||||
|| message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_FB
|
message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_FB) &&
|
||||||
)
|
message.messageStubParameters?.[0]
|
||||||
&& message.messageStubParameters?.[0]
|
|
||||||
) {
|
) {
|
||||||
contacts.push({
|
contacts.push({
|
||||||
id: message.key.participant || message.key.remoteJid!,
|
id: message.key.participant || message.key.remoteJid!,
|
||||||
verifiedName: message.messageStubParameters?.[0],
|
verifiedName: message.messageStubParameters?.[0]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import { extractImageThumb, getHttpStream } from './messages-media'
|
|||||||
const THUMBNAIL_WIDTH_PX = 192
|
const THUMBNAIL_WIDTH_PX = 192
|
||||||
|
|
||||||
/** Fetches an image and generates a thumbnail for it */
|
/** Fetches an image and generates a thumbnail for it */
|
||||||
const getCompressedJpegThumbnail = async(
|
const getCompressedJpegThumbnail = async (url: string, { thumbnailWidth, fetchOpts }: URLGenerationOptions) => {
|
||||||
url: string,
|
|
||||||
{ thumbnailWidth, fetchOpts }: URLGenerationOptions
|
|
||||||
) => {
|
|
||||||
const stream = await getHttpStream(url, fetchOpts)
|
const stream = await getHttpStream(url, fetchOpts)
|
||||||
const result = await extractImageThumb(stream, thumbnailWidth)
|
const result = await extractImageThumb(stream, thumbnailWidth)
|
||||||
return result
|
return result
|
||||||
@@ -39,7 +36,7 @@ export const getUrlInfo = async(
|
|||||||
opts: URLGenerationOptions = {
|
opts: URLGenerationOptions = {
|
||||||
thumbnailWidth: THUMBNAIL_WIDTH_PX,
|
thumbnailWidth: THUMBNAIL_WIDTH_PX,
|
||||||
fetchOpts: { timeout: 3000 }
|
fetchOpts: { timeout: 3000 }
|
||||||
},
|
}
|
||||||
): Promise<WAUrlInfo | undefined> => {
|
): Promise<WAUrlInfo | undefined> => {
|
||||||
try {
|
try {
|
||||||
// retries
|
// retries
|
||||||
@@ -63,9 +60,9 @@ export const getUrlInfo = async(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
forwardedURLObj.hostname === urlObj.hostname
|
forwardedURLObj.hostname === urlObj.hostname ||
|
||||||
|| forwardedURLObj.hostname === 'www.' + urlObj.hostname
|
forwardedURLObj.hostname === 'www.' + urlObj.hostname ||
|
||||||
|| 'www.' + forwardedURLObj.hostname === urlObj.hostname
|
'www.' + forwardedURLObj.hostname === urlObj.hostname
|
||||||
) {
|
) {
|
||||||
retries + 1
|
retries + 1
|
||||||
return true
|
return true
|
||||||
@@ -95,20 +92,13 @@ export const getUrlInfo = async(
|
|||||||
options: opts.fetchOpts
|
options: opts.fetchOpts
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail
|
urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail ? Buffer.from(imageMessage.jpegThumbnail) : undefined
|
||||||
? Buffer.from(imageMessage.jpegThumbnail)
|
|
||||||
: undefined
|
|
||||||
urlInfo.highQualityThumbnail = imageMessage || undefined
|
urlInfo.highQualityThumbnail = imageMessage || undefined
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
urlInfo.jpegThumbnail = image
|
urlInfo.jpegThumbnail = image ? (await getCompressedJpegThumbnail(image, opts)).buffer : undefined
|
||||||
? (await getCompressedJpegThumbnail(image, opts)).buffer
|
|
||||||
: undefined
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
opts.logger?.debug(
|
opts.logger?.debug({ err: error.stack, url: previewLink }, 'error in generating thumbnail')
|
||||||
{ err: error.stack, url: previewLink },
|
|
||||||
'error in generating thumbnail'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { hkdf } from './crypto'
|
|||||||
const o = 128
|
const o = 128
|
||||||
|
|
||||||
class d {
|
class d {
|
||||||
|
|
||||||
salt: string
|
salt: string
|
||||||
|
|
||||||
constructor(e: string) {
|
constructor(e: string) {
|
||||||
@@ -38,19 +37,19 @@ class d {
|
|||||||
async _addSingle(e, t) {
|
async _addSingle(e, t) {
|
||||||
var r = this
|
var r = this
|
||||||
const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer
|
const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer
|
||||||
return r.performPointwiseWithOverflow(await e, n, ((e, t) => e + t))
|
return r.performPointwiseWithOverflow(await e, n, (e, t) => e + t)
|
||||||
}
|
}
|
||||||
async _subtractSingle(e, t) {
|
async _subtractSingle(e, t) {
|
||||||
var r = this
|
var r = this
|
||||||
|
|
||||||
const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer
|
const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer
|
||||||
return r.performPointwiseWithOverflow(await e, n, ((e, t) => e - t))
|
return r.performPointwiseWithOverflow(await e, n, (e, t) => e - t)
|
||||||
}
|
}
|
||||||
performPointwiseWithOverflow(e, t, r) {
|
performPointwiseWithOverflow(e, t, r) {
|
||||||
const n = new DataView(e)
|
const n = new DataView(e),
|
||||||
, i = new DataView(t)
|
i = new DataView(t),
|
||||||
, a = new ArrayBuffer(n.byteLength)
|
a = new ArrayBuffer(n.byteLength),
|
||||||
, s = new DataView(a)
|
s = new DataView(a)
|
||||||
for (let e = 0; e < n.byteLength; e += 2) {
|
for (let e = 0; e < n.byteLength; e += 2) {
|
||||||
s.setUint16(e, r(n.getUint16(e, !0), i.getUint16(e, !0)), !0)
|
s.setUint16(e, r(n.getUint16(e, !0), i.getUint16(e, !0)), !0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const makeMutex = () => {
|
|||||||
// we replace the existing task, appending the new piece of execution to it
|
// we replace the existing task, appending the new piece of execution to it
|
||||||
// so the next task will have to wait for this one to finish
|
// so the next task will have to wait for this one to finish
|
||||||
return task
|
return task
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,20 @@ import { Readable, Transform } from 'stream'
|
|||||||
import { URL } from 'url'
|
import { URL } from 'url'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from '../Defaults'
|
import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from '../Defaults'
|
||||||
import { BaileysEventMap, DownloadableMessage, MediaConnInfo, MediaDecryptionKeyInfo, MediaType, MessageType, SocketConfig, WAGenericMediaMessage, WAMediaPayloadURL, WAMediaUpload, WAMediaUploadFunction, WAMessageContent } from '../Types'
|
import {
|
||||||
|
BaileysEventMap,
|
||||||
|
DownloadableMessage,
|
||||||
|
MediaConnInfo,
|
||||||
|
MediaDecryptionKeyInfo,
|
||||||
|
MediaType,
|
||||||
|
MessageType,
|
||||||
|
SocketConfig,
|
||||||
|
WAGenericMediaMessage,
|
||||||
|
WAMediaPayloadURL,
|
||||||
|
WAMediaUpload,
|
||||||
|
WAMediaUploadFunction,
|
||||||
|
WAMessageContent
|
||||||
|
} from '../Types'
|
||||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary'
|
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary'
|
||||||
import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto'
|
import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto'
|
||||||
import { generateMessageIDV2 } from './generics'
|
import { generateMessageIDV2 } from './generics'
|
||||||
@@ -22,17 +35,11 @@ const getTmpFilesDirectory = () => tmpdir()
|
|||||||
const getImageProcessingLibrary = async () => {
|
const getImageProcessingLibrary = async () => {
|
||||||
const [_jimp, sharp] = await Promise.all([
|
const [_jimp, sharp] = await Promise.all([
|
||||||
(async () => {
|
(async () => {
|
||||||
const jimp = await (
|
const jimp = await import('jimp').catch(() => {})
|
||||||
import('jimp')
|
|
||||||
.catch(() => { })
|
|
||||||
)
|
|
||||||
return jimp
|
return jimp
|
||||||
})(),
|
})(),
|
||||||
(async () => {
|
(async () => {
|
||||||
const sharp = await (
|
const sharp = await import('sharp').catch(() => {})
|
||||||
import('sharp')
|
|
||||||
.catch(() => { })
|
|
||||||
)
|
|
||||||
return sharp
|
return sharp
|
||||||
})()
|
})()
|
||||||
])
|
])
|
||||||
@@ -55,7 +62,10 @@ 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 async function getMediaKeys(buffer: Uint8Array | string | null | undefined, mediaType: MediaType): Promise<MediaDecryptionKeyInfo> {
|
export async function getMediaKeys(
|
||||||
|
buffer: Uint8Array | string | null | undefined,
|
||||||
|
mediaType: MediaType
|
||||||
|
): Promise<MediaDecryptionKeyInfo> {
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
throw new Boom('Cannot derive from empty media key')
|
throw new Boom('Cannot derive from empty media key')
|
||||||
}
|
}
|
||||||
@@ -69,7 +79,7 @@ export async function getMediaKeys(buffer: Uint8Array | string | null | undefine
|
|||||||
return {
|
return {
|
||||||
iv: expandedMediaKey.slice(0, 16),
|
iv: expandedMediaKey.slice(0, 16),
|
||||||
cipherKey: expandedMediaKey.slice(16, 48),
|
cipherKey: expandedMediaKey.slice(16, 48),
|
||||||
macKey: expandedMediaKey.slice(48, 80),
|
macKey: expandedMediaKey.slice(48, 80)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,10 +88,11 @@ const extractVideoThumb = async(
|
|||||||
path: string,
|
path: string,
|
||||||
destPath: string,
|
destPath: string,
|
||||||
time: string,
|
time: string,
|
||||||
size: { width: number, height: number },
|
size: { width: number; height: number }
|
||||||
) => new Promise<void>((resolve, reject) => {
|
) =>
|
||||||
|
new Promise<void>((resolve, reject) => {
|
||||||
const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`
|
const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`
|
||||||
exec(cmd, (err) => {
|
exec(cmd, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
@@ -100,16 +111,13 @@ export const extractImageThumb = async(bufferOrFilePath: Readable | Buffer | str
|
|||||||
const img = lib.sharp.default(bufferOrFilePath)
|
const img = lib.sharp.default(bufferOrFilePath)
|
||||||
const dimensions = await img.metadata()
|
const dimensions = await img.metadata()
|
||||||
|
|
||||||
const buffer = await img
|
const buffer = await img.resize(width).jpeg({ quality: 50 }).toBuffer()
|
||||||
.resize(width)
|
|
||||||
.jpeg({ quality: 50 })
|
|
||||||
.toBuffer()
|
|
||||||
return {
|
return {
|
||||||
buffer,
|
buffer,
|
||||||
original: {
|
original: {
|
||||||
width: dimensions.width,
|
width: dimensions.width,
|
||||||
height: dimensions.height,
|
height: dimensions.height
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
} else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
|
} else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
|
||||||
const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp
|
const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp
|
||||||
@@ -119,10 +127,7 @@ export const extractImageThumb = async(bufferOrFilePath: Readable | Buffer | str
|
|||||||
width: jimp.getWidth(),
|
width: jimp.getWidth(),
|
||||||
height: jimp.getHeight()
|
height: jimp.getHeight()
|
||||||
}
|
}
|
||||||
const buffer = await jimp
|
const buffer = await jimp.quality(50).resize(width, AUTO, RESIZE_BILINEAR).getBufferAsync(MIME_JPEG)
|
||||||
.quality(50)
|
|
||||||
.resize(width, AUTO, RESIZE_BILINEAR)
|
|
||||||
.getBufferAsync(MIME_JPEG)
|
|
||||||
return {
|
return {
|
||||||
buffer,
|
buffer,
|
||||||
original: dimensions
|
original: dimensions
|
||||||
@@ -132,14 +137,8 @@ export const extractImageThumb = async(bufferOrFilePath: Readable | Buffer | str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encodeBase64EncodedStringForUpload = (b64: string) => (
|
export const encodeBase64EncodedStringForUpload = (b64: string) =>
|
||||||
encodeURIComponent(
|
encodeURIComponent(b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, ''))
|
||||||
b64
|
|
||||||
.replace(/\+/g, '-')
|
|
||||||
.replace(/\//g, '_')
|
|
||||||
.replace(/\=+$/, '')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const generateProfilePicture = async (mediaUpload: WAMediaUpload) => {
|
export const generateProfilePicture = async (mediaUpload: WAMediaUpload) => {
|
||||||
let bufferOrFilePath: Buffer | string
|
let bufferOrFilePath: Buffer | string
|
||||||
@@ -154,10 +153,11 @@ export const generateProfilePicture = async(mediaUpload: WAMediaUpload) => {
|
|||||||
const lib = await getImageProcessingLibrary()
|
const lib = await getImageProcessingLibrary()
|
||||||
let img: Promise<Buffer>
|
let img: Promise<Buffer>
|
||||||
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
||||||
img = lib.sharp.default(bufferOrFilePath)
|
img = lib.sharp
|
||||||
|
.default(bufferOrFilePath)
|
||||||
.resize(640, 640)
|
.resize(640, 640)
|
||||||
.jpeg({
|
.jpeg({
|
||||||
quality: 50,
|
quality: 50
|
||||||
})
|
})
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
} else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
|
} else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
|
||||||
@@ -166,16 +166,13 @@ export const generateProfilePicture = async(mediaUpload: WAMediaUpload) => {
|
|||||||
const min = Math.min(jimp.getWidth(), jimp.getHeight())
|
const min = Math.min(jimp.getWidth(), jimp.getHeight())
|
||||||
const cropped = jimp.crop(0, 0, min, min)
|
const cropped = jimp.crop(0, 0, min, min)
|
||||||
|
|
||||||
img = cropped
|
img = cropped.quality(50).resize(640, 640, RESIZE_BILINEAR).getBufferAsync(MIME_JPEG)
|
||||||
.quality(50)
|
|
||||||
.resize(640, 640, RESIZE_BILINEAR)
|
|
||||||
.getBufferAsync(MIME_JPEG)
|
|
||||||
} else {
|
} else {
|
||||||
throw new Boom('No image processing library available')
|
throw new Boom('No image processing library available')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
img: await img,
|
img: await img
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +206,7 @@ export async function getAudioDuration(buffer: Buffer | string | Readable) {
|
|||||||
*/
|
*/
|
||||||
export async function getAudioWaveform(buffer: Buffer | string | Readable, logger?: ILogger) {
|
export async function getAudioWaveform(buffer: Buffer | string | Readable, logger?: ILogger) {
|
||||||
try {
|
try {
|
||||||
const { default: decoder } = await eval('import(\'audio-decode\')')
|
const { default: decoder } = await eval("import('audio-decode')")
|
||||||
let audioData: Buffer
|
let audioData: Buffer
|
||||||
if (Buffer.isBuffer(buffer)) {
|
if (Buffer.isBuffer(buffer)) {
|
||||||
audioData = buffer
|
audioData = buffer
|
||||||
@@ -238,12 +235,10 @@ export async function getAudioWaveform(buffer: Buffer | string | Readable, logge
|
|||||||
|
|
||||||
// This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
|
// This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
|
||||||
const multiplier = Math.pow(Math.max(...filteredData), -1)
|
const multiplier = Math.pow(Math.max(...filteredData), -1)
|
||||||
const normalizedData = filteredData.map((n) => n * multiplier)
|
const normalizedData = filteredData.map(n => n * multiplier)
|
||||||
|
|
||||||
// Generate waveform like WhatsApp
|
// Generate waveform like WhatsApp
|
||||||
const waveform = new Uint8Array(
|
const waveform = new Uint8Array(normalizedData.map(n => Math.floor(100 * n)))
|
||||||
normalizedData.map((n) => Math.floor(100 * n))
|
|
||||||
)
|
|
||||||
|
|
||||||
return waveform
|
return waveform
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -251,7 +246,6 @@ export async function getAudioWaveform(buffer: Buffer | string | Readable, logge
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const toReadable = (buffer: Buffer) => {
|
export const toReadable = (buffer: Buffer) => {
|
||||||
const readable = new Readable({ read: () => {} })
|
const readable = new Readable({ read: () => {} })
|
||||||
readable.push(buffer)
|
readable.push(buffer)
|
||||||
@@ -294,14 +288,14 @@ export async function generateThumbnail(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
let thumbnail: string | undefined
|
let thumbnail: string | undefined
|
||||||
let originalImageDimensions: { width: number, height: number } | undefined
|
let originalImageDimensions: { width: number; height: number } | undefined
|
||||||
if (mediaType === 'image') {
|
if (mediaType === 'image') {
|
||||||
const { buffer, original } = await extractImageThumb(file)
|
const { buffer, original } = await extractImageThumb(file)
|
||||||
thumbnail = buffer.toString('base64')
|
thumbnail = buffer.toString('base64')
|
||||||
if (original.width && original.height) {
|
if (original.width && original.height) {
|
||||||
originalImageDimensions = {
|
originalImageDimensions = {
|
||||||
width: original.width,
|
width: original.width,
|
||||||
height: original.height,
|
height: original.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (mediaType === 'video') {
|
} else if (mediaType === 'video') {
|
||||||
@@ -368,17 +362,10 @@ export const encryptedStream = async(
|
|||||||
for await (const data of stream) {
|
for await (const data of stream) {
|
||||||
fileLength += data.length
|
fileLength += data.length
|
||||||
|
|
||||||
if(
|
if (type === 'remote' && opts?.maxContentLength && fileLength + data.length > opts.maxContentLength) {
|
||||||
type === 'remote'
|
throw new Boom(`content length exceeded when encrypting "${type}"`, {
|
||||||
&& opts?.maxContentLength
|
|
||||||
&& fileLength + data.length > opts.maxContentLength
|
|
||||||
) {
|
|
||||||
throw new Boom(
|
|
||||||
`content length exceeded when encrypting "${type}"`,
|
|
||||||
{
|
|
||||||
data: { media, type }
|
data: { media, type }
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sha256Plain = sha256Plain.update(data)
|
sha256Plain = sha256Plain.update(data)
|
||||||
@@ -495,8 +482,8 @@ export const downloadEncryptedContent = async(
|
|||||||
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined
|
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined
|
||||||
|
|
||||||
const headers: AxiosRequestConfig['headers'] = {
|
const headers: AxiosRequestConfig['headers'] = {
|
||||||
...options?.headers || { },
|
...(options?.headers || {}),
|
||||||
Origin: DEFAULT_ORIGIN,
|
Origin: DEFAULT_ORIGIN
|
||||||
}
|
}
|
||||||
if (startChunk || endChunk) {
|
if (startChunk || endChunk) {
|
||||||
headers.Range = `bytes=${startChunk}-`
|
headers.Range = `bytes=${startChunk}-`
|
||||||
@@ -506,15 +493,12 @@ export const downloadEncryptedContent = async(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// download the message
|
// download the message
|
||||||
const fetched = await getHttpStream(
|
const fetched = await getHttpStream(downloadUrl, {
|
||||||
downloadUrl,
|
...(options || {}),
|
||||||
{
|
|
||||||
...options || { },
|
|
||||||
headers,
|
headers,
|
||||||
maxBodyLength: Infinity,
|
maxBodyLength: Infinity,
|
||||||
maxContentLength: Infinity,
|
maxContentLength: Infinity
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
let remainingBytes = Buffer.from([])
|
let remainingBytes = Buffer.from([])
|
||||||
|
|
||||||
@@ -554,7 +538,6 @@ export const downloadEncryptedContent = async(
|
|||||||
if (endByte) {
|
if (endByte) {
|
||||||
aes.setAutoPadding(false)
|
aes.setAutoPadding(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -571,7 +554,7 @@ export const downloadEncryptedContent = async(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
callback(error)
|
callback(error)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
return fetched.pipe(output, { end: true })
|
return fetched.pipe(output, { end: true })
|
||||||
}
|
}
|
||||||
@@ -580,11 +563,7 @@ export function extensionForMediaMessage(message: WAMessageContent) {
|
|||||||
const getExtension = (mimetype: string) => mimetype.split(';')[0].split('/')[1]
|
const getExtension = (mimetype: string) => mimetype.split(';')[0].split('/')[1]
|
||||||
const type = Object.keys(message)[0] as MessageType
|
const type = Object.keys(message)[0] as MessageType
|
||||||
let extension: string
|
let extension: string
|
||||||
if(
|
if (type === 'locationMessage' || type === 'liveLocationMessage' || type === 'productMessage') {
|
||||||
type === 'locationMessage' ||
|
|
||||||
type === 'liveLocationMessage' ||
|
|
||||||
type === 'productMessage'
|
|
||||||
) {
|
|
||||||
extension = '.jpeg'
|
extension = '.jpeg'
|
||||||
} else {
|
} else {
|
||||||
const messageContent = message[type] as WAGenericMediaMessage
|
const messageContent = message[type] as WAGenericMediaMessage
|
||||||
@@ -596,13 +575,13 @@ export function extensionForMediaMessage(message: WAMessageContent) {
|
|||||||
|
|
||||||
export const getWAUploadToServer = (
|
export const getWAUploadToServer = (
|
||||||
{ customUploadHosts, fetchAgent, logger, options }: SocketConfig,
|
{ customUploadHosts, fetchAgent, logger, options }: SocketConfig,
|
||||||
refreshMediaConn: (force: boolean) => Promise<MediaConnInfo>,
|
refreshMediaConn: (force: boolean) => Promise<MediaConnInfo>
|
||||||
): WAMediaUploadFunction => {
|
): WAMediaUploadFunction => {
|
||||||
return async (stream, { mediaType, fileEncSha256B64, timeoutMs }) => {
|
return async (stream, { mediaType, fileEncSha256B64, timeoutMs }) => {
|
||||||
// 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 } | undefined
|
let urls: { mediaUrl: string; directPath: string } | undefined
|
||||||
const hosts = [...customUploadHosts, ...uploadInfo.hosts]
|
const hosts = [...customUploadHosts, ...uploadInfo.hosts]
|
||||||
|
|
||||||
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
|
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
|
||||||
@@ -615,24 +594,19 @@ export const getWAUploadToServer = (
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let result: any
|
let result: any
|
||||||
try {
|
try {
|
||||||
|
const body = await axios.post(url, stream, {
|
||||||
const body = await axios.post(
|
|
||||||
url,
|
|
||||||
stream,
|
|
||||||
{
|
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
...options.headers || { },
|
...(options.headers || {}),
|
||||||
'Content-Type': 'application/octet-stream',
|
'Content-Type': 'application/octet-stream',
|
||||||
'Origin': DEFAULT_ORIGIN
|
Origin: DEFAULT_ORIGIN
|
||||||
},
|
},
|
||||||
httpsAgent: fetchAgent,
|
httpsAgent: fetchAgent,
|
||||||
timeout: timeoutMs,
|
timeout: timeoutMs,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
maxBodyLength: Infinity,
|
maxBodyLength: Infinity,
|
||||||
maxContentLength: Infinity,
|
maxContentLength: Infinity
|
||||||
}
|
})
|
||||||
)
|
|
||||||
result = body.data
|
result = body.data
|
||||||
|
|
||||||
if (result?.url || result?.directPath) {
|
if (result?.url || result?.directPath) {
|
||||||
@@ -651,15 +625,15 @@ export const getWAUploadToServer = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
|
const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
|
||||||
logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`)
|
logger.warn(
|
||||||
|
{ trace: error.stack, uploadResult: result },
|
||||||
|
`Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!urls) {
|
if (!urls) {
|
||||||
throw new Boom(
|
throw new Boom('Media upload failed on all hosts', { statusCode: 500 })
|
||||||
'Media upload failed on all hosts',
|
|
||||||
{ statusCode: 500 }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return urls
|
return urls
|
||||||
@@ -673,11 +647,7 @@ const getMediaRetryKey = (mediaKey: Buffer | Uint8Array) => {
|
|||||||
/**
|
/**
|
||||||
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
||||||
*/
|
*/
|
||||||
export const encryptMediaRetryRequest = async(
|
export const encryptMediaRetryRequest = async (key: proto.IMessageKey, mediaKey: Buffer | Uint8Array, meId: string) => {
|
||||||
key: proto.IMessageKey,
|
|
||||||
mediaKey: Buffer | Uint8Array,
|
|
||||||
meId: string
|
|
||||||
) => {
|
|
||||||
const recp: proto.IServerErrorReceipt = { stanzaId: key.id }
|
const recp: proto.IServerErrorReceipt = { stanzaId: key.id }
|
||||||
const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish()
|
const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish()
|
||||||
|
|
||||||
@@ -708,7 +678,7 @@ export const encryptMediaRetryRequest = async(
|
|||||||
tag: 'rmr',
|
tag: 'rmr',
|
||||||
attrs: {
|
attrs: {
|
||||||
jid: key.remoteJid!,
|
jid: key.remoteJid!,
|
||||||
'from_me': (!!key.fromMe).toString(),
|
from_me: (!!key.fromMe).toString(),
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
participant: key.participant || undefined
|
participant: key.participant || undefined
|
||||||
}
|
}
|
||||||
@@ -734,10 +704,10 @@ export const decodeMediaRetryNode = (node: BinaryNode) => {
|
|||||||
const errorNode = getBinaryNodeChild(node, 'error')
|
const errorNode = getBinaryNodeChild(node, 'error')
|
||||||
if (errorNode) {
|
if (errorNode) {
|
||||||
const errorCode = +errorNode.attrs.code
|
const errorCode = +errorNode.attrs.code
|
||||||
event.error = new Boom(
|
event.error = new Boom(`Failed to re-upload media (${errorCode})`, {
|
||||||
`Failed to re-upload media (${errorCode})`,
|
data: errorNode.attrs,
|
||||||
{ data: errorNode.attrs, statusCode: getStatusCodeForMediaRetry(errorCode) }
|
statusCode: getStatusCodeForMediaRetry(errorCode)
|
||||||
)
|
})
|
||||||
} else {
|
} else {
|
||||||
const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt')
|
const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt')
|
||||||
const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p')
|
const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p')
|
||||||
@@ -753,7 +723,7 @@ export const decodeMediaRetryNode = (node: BinaryNode) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const decryptMediaRetryData = async (
|
export const decryptMediaRetryData = async (
|
||||||
{ ciphertext, iv }: { ciphertext: Uint8Array, iv: Uint8Array },
|
{ ciphertext, iv }: { ciphertext: Uint8Array; iv: Uint8Array },
|
||||||
mediaKey: Uint8Array,
|
mediaKey: Uint8Array,
|
||||||
msgId: string
|
msgId: string
|
||||||
) => {
|
) => {
|
||||||
@@ -768,5 +738,5 @@ const MEDIA_RETRY_STATUS_MAP = {
|
|||||||
[proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
|
[proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
|
||||||
[proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
|
[proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
|
||||||
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
||||||
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
|
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
|
||||||
} as const
|
} as const
|
||||||
@@ -21,13 +21,20 @@ import {
|
|||||||
WAMessageContent,
|
WAMessageContent,
|
||||||
WAMessageStatus,
|
WAMessageStatus,
|
||||||
WAProto,
|
WAProto,
|
||||||
WATextMessage,
|
WATextMessage
|
||||||
} from '../Types'
|
} from '../Types'
|
||||||
import { isJidGroup, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary'
|
import { isJidGroup, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary'
|
||||||
import { sha256 } from './crypto'
|
import { sha256 } from './crypto'
|
||||||
import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics'
|
import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics'
|
||||||
import { ILogger } from './logger'
|
import { ILogger } from './logger'
|
||||||
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, MediaDownloadOptions } from './messages-media'
|
import {
|
||||||
|
downloadContentFromMessage,
|
||||||
|
encryptedStream,
|
||||||
|
generateThumbnail,
|
||||||
|
getAudioDuration,
|
||||||
|
getAudioWaveform,
|
||||||
|
MediaDownloadOptions
|
||||||
|
} from './messages-media'
|
||||||
|
|
||||||
type MediaUploadData = {
|
type MediaUploadData = {
|
||||||
media: WAMediaUpload
|
media: WAMediaUpload
|
||||||
@@ -51,15 +58,15 @@ const MIMETYPE_MAP: { [T in MediaType]?: string } = {
|
|||||||
document: 'application/pdf',
|
document: 'application/pdf',
|
||||||
audio: 'audio/ogg; codecs=opus',
|
audio: 'audio/ogg; codecs=opus',
|
||||||
sticker: 'image/webp',
|
sticker: 'image/webp',
|
||||||
'product-catalog-image': 'image/jpeg',
|
'product-catalog-image': 'image/jpeg'
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageTypeProto = {
|
const MessageTypeProto = {
|
||||||
'image': WAProto.Message.ImageMessage,
|
image: WAProto.Message.ImageMessage,
|
||||||
'video': WAProto.Message.VideoMessage,
|
video: WAProto.Message.VideoMessage,
|
||||||
'audio': WAProto.Message.AudioMessage,
|
audio: WAProto.Message.AudioMessage,
|
||||||
'sticker': WAProto.Message.StickerMessage,
|
sticker: WAProto.Message.StickerMessage,
|
||||||
'document': WAProto.Message.DocumentMessage,
|
document: WAProto.Message.DocumentMessage
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,19 +76,24 @@ const MessageTypeProto = {
|
|||||||
*/
|
*/
|
||||||
export const extractUrlFromText = (text: string) => text.match(URL_REGEX)?.[0]
|
export const extractUrlFromText = (text: string) => text.match(URL_REGEX)?.[0]
|
||||||
|
|
||||||
export const generateLinkPreviewIfRequired = async(text: string, getUrlInfo: MessageGenerationOptions['getUrlInfo'], logger: MessageGenerationOptions['logger']) => {
|
export const generateLinkPreviewIfRequired = async (
|
||||||
|
text: string,
|
||||||
|
getUrlInfo: MessageGenerationOptions['getUrlInfo'],
|
||||||
|
logger: MessageGenerationOptions['logger']
|
||||||
|
) => {
|
||||||
const url = extractUrlFromText(text)
|
const url = extractUrlFromText(text)
|
||||||
if (!!getUrlInfo && url) {
|
if (!!getUrlInfo && url) {
|
||||||
try {
|
try {
|
||||||
const urlInfo = await getUrlInfo(url)
|
const urlInfo = await getUrlInfo(url)
|
||||||
return urlInfo
|
return urlInfo
|
||||||
} catch(error) { // ignore if fails
|
} catch (error) {
|
||||||
|
// ignore if fails
|
||||||
logger?.warn({ trace: error.stack }, 'url generation failed')
|
logger?.warn({ trace: error.stack }, 'url generation failed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const assertColor = async(color) => {
|
const assertColor = async color => {
|
||||||
let assertedColor
|
let assertedColor
|
||||||
if (typeof color === 'number') {
|
if (typeof color === 'number') {
|
||||||
assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1
|
assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1
|
||||||
@@ -96,13 +108,10 @@ const assertColor = async(color) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const prepareWAMessageMedia = async(
|
export const prepareWAMessageMedia = async (message: AnyMediaMessageContent, options: MediaGenerationOptions) => {
|
||||||
message: AnyMediaMessageContent,
|
|
||||||
options: MediaGenerationOptions
|
|
||||||
) => {
|
|
||||||
const logger = options.logger
|
const logger = options.logger
|
||||||
|
|
||||||
let mediaType: typeof MEDIA_KEYS[number] | undefined
|
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
|
||||||
@@ -119,13 +128,13 @@ export const prepareWAMessageMedia = async(
|
|||||||
}
|
}
|
||||||
delete uploadData[mediaType]
|
delete uploadData[mediaType]
|
||||||
// check if cacheable + generate cache key
|
// check if cacheable + generate cache key
|
||||||
const cacheableKey = typeof uploadData.media === 'object' &&
|
const cacheableKey =
|
||||||
('url' in uploadData.media) &&
|
typeof uploadData.media === 'object' &&
|
||||||
|
'url' in uploadData.media &&
|
||||||
!!uploadData.media.url &&
|
!!uploadData.media.url &&
|
||||||
!!options.mediaCache && (
|
!!options.mediaCache &&
|
||||||
// generate the key
|
// generate the key
|
||||||
mediaType + ':' + uploadData.media.url.toString()
|
mediaType + ':' + uploadData.media.url.toString()
|
||||||
)
|
|
||||||
|
|
||||||
if (mediaType === 'document' && !uploadData.fileName) {
|
if (mediaType === 'document' && !uploadData.fileName) {
|
||||||
uploadData.fileName = 'file'
|
uploadData.fileName = 'file'
|
||||||
@@ -151,46 +160,37 @@ export const prepareWAMessageMedia = async(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
|
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
|
||||||
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') &&
|
const requiresThumbnailComputation =
|
||||||
(typeof uploadData['jpegThumbnail'] === 'undefined')
|
(mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined'
|
||||||
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true
|
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true
|
||||||
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
|
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
|
||||||
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
|
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
|
||||||
const {
|
const { mediaKey, encWriteStream, bodyPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath } =
|
||||||
mediaKey,
|
await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
|
||||||
encWriteStream,
|
|
||||||
bodyPath,
|
|
||||||
fileEncSha256,
|
|
||||||
fileSha256,
|
|
||||||
fileLength,
|
|
||||||
didSaveToTmpPath
|
|
||||||
} = await encryptedStream(
|
|
||||||
uploadData.media,
|
|
||||||
options.mediaTypeOverride || mediaType,
|
|
||||||
{
|
|
||||||
logger,
|
logger,
|
||||||
saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
|
saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
|
||||||
opts: options.options
|
opts: options.options
|
||||||
}
|
})
|
||||||
)
|
|
||||||
// url safe Base64 encode the SHA256 hash of the body
|
// url safe Base64 encode the SHA256 hash of the body
|
||||||
const fileEncSha256B64 = fileEncSha256.toString('base64')
|
const fileEncSha256B64 = fileEncSha256.toString('base64')
|
||||||
const [{ mediaUrl, directPath }] = await Promise.all([
|
const [{ mediaUrl, directPath }] = await Promise.all([
|
||||||
(async () => {
|
(async () => {
|
||||||
const result = await options.upload(
|
const result = await options.upload(encWriteStream, {
|
||||||
encWriteStream,
|
fileEncSha256B64,
|
||||||
{ fileEncSha256B64, mediaType, timeoutMs: options.mediaUploadTimeoutMs }
|
mediaType,
|
||||||
)
|
timeoutMs: options.mediaUploadTimeoutMs
|
||||||
|
})
|
||||||
logger?.debug({ mediaType, cacheableKey }, 'uploaded media')
|
logger?.debug({ mediaType, cacheableKey }, 'uploaded media')
|
||||||
return result
|
return result
|
||||||
})(),
|
})(),
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
if (requiresThumbnailComputation) {
|
if (requiresThumbnailComputation) {
|
||||||
const {
|
const { thumbnail, originalImageDimensions } = await generateThumbnail(
|
||||||
thumbnail,
|
bodyPath!,
|
||||||
originalImageDimensions
|
mediaType as 'image' | 'video',
|
||||||
} = await generateThumbnail(bodyPath!, mediaType as 'image' | 'video', options)
|
options
|
||||||
|
)
|
||||||
uploadData.jpegThumbnail = thumbnail
|
uploadData.jpegThumbnail = thumbnail
|
||||||
if (!uploadData.width && originalImageDimensions) {
|
if (!uploadData.width && originalImageDimensions) {
|
||||||
uploadData.width = originalImageDimensions.width
|
uploadData.width = originalImageDimensions.width
|
||||||
@@ -218,10 +218,8 @@ export const prepareWAMessageMedia = async(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger?.warn({ trace: error.stack }, 'failed to obtain extra info')
|
logger?.warn({ trace: error.stack }, 'failed to obtain extra info')
|
||||||
}
|
}
|
||||||
})(),
|
})()
|
||||||
])
|
]).finally(async () => {
|
||||||
.finally(
|
|
||||||
async() => {
|
|
||||||
encWriteStream.destroy()
|
encWriteStream.destroy()
|
||||||
// remove tmp files
|
// remove tmp files
|
||||||
if (didSaveToTmpPath && bodyPath) {
|
if (didSaveToTmpPath && bodyPath) {
|
||||||
@@ -233,12 +231,10 @@ export const prepareWAMessageMedia = async(
|
|||||||
logger?.warn('failed to remove tmp file')
|
logger?.warn('failed to remove tmp file')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const obj = WAProto.Message.fromObject({
|
const obj = WAProto.Message.fromObject({
|
||||||
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject(
|
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
|
||||||
{
|
|
||||||
url: mediaUrl,
|
url: mediaUrl,
|
||||||
directPath,
|
directPath,
|
||||||
mediaKey,
|
mediaKey,
|
||||||
@@ -248,8 +244,7 @@ export const prepareWAMessageMedia = async(
|
|||||||
mediaKeyTimestamp: unixTimestampSeconds(),
|
mediaKeyTimestamp: unixTimestampSeconds(),
|
||||||
...uploadData,
|
...uploadData,
|
||||||
media: undefined
|
media: undefined
|
||||||
}
|
})
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (uploadData.ptv) {
|
if (uploadData.ptv) {
|
||||||
@@ -285,10 +280,7 @@ export const prepareDisappearingMessageSettingContent = (ephemeralExpiration?: n
|
|||||||
* @param message the message to forward
|
* @param message the message to forward
|
||||||
* @param options.forceForward will show the message as forwarded even if it is from you
|
* @param options.forceForward will show the message as forwarded even if it is from you
|
||||||
*/
|
*/
|
||||||
export const generateForwardMessageContent = (
|
export const generateForwardMessageContent = (message: WAMessage, forceForward?: boolean) => {
|
||||||
message: WAMessage,
|
|
||||||
forceForward?: boolean
|
|
||||||
) => {
|
|
||||||
let content = message.message
|
let content = message.message
|
||||||
if (!content) {
|
if (!content) {
|
||||||
throw new Boom('no content in message', { statusCode: 400 })
|
throw new Boom('no content in message', { statusCode: 400 })
|
||||||
@@ -384,14 +376,14 @@ export const generateWAMessageContent = async(
|
|||||||
type: WAProto.Message.ProtocolMessage.Type.REVOKE
|
type: WAProto.Message.ProtocolMessage.Type.REVOKE
|
||||||
}
|
}
|
||||||
} else if ('forward' in message) {
|
} else if ('forward' in message) {
|
||||||
m = generateForwardMessageContent(
|
m = generateForwardMessageContent(message.forward, message.force)
|
||||||
message.forward,
|
|
||||||
message.force
|
|
||||||
)
|
|
||||||
} else if ('disappearingMessagesInChat' in message) {
|
} else if ('disappearingMessagesInChat' in message) {
|
||||||
const exp = typeof message.disappearingMessagesInChat === 'boolean' ?
|
const exp =
|
||||||
(message.disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
|
typeof message.disappearingMessagesInChat === 'boolean'
|
||||||
message.disappearingMessagesInChat
|
? message.disappearingMessagesInChat
|
||||||
|
? WA_DEFAULT_EPHEMERAL
|
||||||
|
: 0
|
||||||
|
: message.disappearingMessagesInChat
|
||||||
m = prepareDisappearingMessageSettingContent(exp)
|
m = prepareDisappearingMessageSettingContent(exp)
|
||||||
} else if ('groupInvite' in message) {
|
} else if ('groupInvite' in message) {
|
||||||
m.groupInviteMessage = {}
|
m.groupInviteMessage = {}
|
||||||
@@ -427,33 +419,27 @@ export const generateWAMessageContent = async(
|
|||||||
m.templateButtonReplyMessage = {
|
m.templateButtonReplyMessage = {
|
||||||
selectedDisplayText: message.buttonReply.displayText,
|
selectedDisplayText: message.buttonReply.displayText,
|
||||||
selectedId: message.buttonReply.id,
|
selectedId: message.buttonReply.id,
|
||||||
selectedIndex: message.buttonReply.index,
|
selectedIndex: message.buttonReply.index
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'plain':
|
case 'plain':
|
||||||
m.buttonsResponseMessage = {
|
m.buttonsResponseMessage = {
|
||||||
selectedButtonId: message.buttonReply.id,
|
selectedButtonId: message.buttonReply.id,
|
||||||
selectedDisplayText: message.buttonReply.displayText,
|
selectedDisplayText: message.buttonReply.displayText,
|
||||||
type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT,
|
type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if ('ptv' in message && message.ptv) {
|
} else if ('ptv' in message && message.ptv) {
|
||||||
const { videoMessage } = await prepareWAMessageMedia(
|
const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options)
|
||||||
{ video: message.video },
|
|
||||||
options
|
|
||||||
)
|
|
||||||
m.ptvMessage = videoMessage
|
m.ptvMessage = videoMessage
|
||||||
} else if ('product' in message) {
|
} else if ('product' in message) {
|
||||||
const { imageMessage } = await prepareWAMessageMedia(
|
const { imageMessage } = await prepareWAMessageMedia({ image: message.product.productImage }, options)
|
||||||
{ image: message.product.productImage },
|
|
||||||
options
|
|
||||||
)
|
|
||||||
m.productMessage = WAProto.Message.ProductMessage.fromObject({
|
m.productMessage = WAProto.Message.ProductMessage.fromObject({
|
||||||
...message,
|
...message,
|
||||||
product: {
|
product: {
|
||||||
...message.product,
|
...message.product,
|
||||||
productImage: imageMessage,
|
productImage: imageMessage
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if ('listReply' in message) {
|
} else if ('listReply' in message) {
|
||||||
@@ -466,25 +452,21 @@ export const generateWAMessageContent = async(
|
|||||||
throw new Boom('Invalid poll values', { statusCode: 400 })
|
throw new Boom('Invalid poll values', { statusCode: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
if(
|
if (message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) {
|
||||||
message.poll.selectableCount < 0
|
throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
|
||||||
|| message.poll.selectableCount > message.poll.values.length
|
statusCode: 400
|
||||||
) {
|
})
|
||||||
throw new Boom(
|
|
||||||
`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`,
|
|
||||||
{ statusCode: 400 }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.messageContextInfo = {
|
m.messageContextInfo = {
|
||||||
// encKey
|
// encKey
|
||||||
messageSecret: message.poll.messageSecret || randomBytes(32),
|
messageSecret: message.poll.messageSecret || randomBytes(32)
|
||||||
}
|
}
|
||||||
|
|
||||||
const pollCreationMessage = {
|
const pollCreationMessage = {
|
||||||
name: message.poll.name,
|
name: message.poll.name,
|
||||||
selectableOptionsCount: message.poll.selectableCount,
|
selectableOptionsCount: message.poll.selectableCount,
|
||||||
options: message.poll.values.map(optionName => ({ optionName })),
|
options: message.poll.values.map(optionName => ({ optionName }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.poll.toAnnouncementGroup) {
|
if (message.poll.toAnnouncementGroup) {
|
||||||
@@ -506,10 +488,7 @@ export const generateWAMessageContent = async(
|
|||||||
} else if ('requestPhoneNumber' in message) {
|
} else if ('requestPhoneNumber' in message) {
|
||||||
m.requestPhoneNumberMessage = {}
|
m.requestPhoneNumberMessage = {}
|
||||||
} else {
|
} else {
|
||||||
m = await prepareWAMessageMedia(
|
m = await prepareWAMessageMedia(message, options)
|
||||||
message,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('viewOnce' in message && !!message.viewOnce) {
|
if ('viewOnce' in message && !!message.viewOnce) {
|
||||||
@@ -559,7 +538,9 @@ export const generateWAMessageFromContent = (
|
|||||||
const { quoted, userJid } = options
|
const { quoted, userJid } = options
|
||||||
|
|
||||||
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)!
|
||||||
@@ -595,7 +576,7 @@ export const generateWAMessageFromContent = (
|
|||||||
) {
|
) {
|
||||||
innerMessage[key].contextInfo = {
|
innerMessage[key].contextInfo = {
|
||||||
...(innerMessage[key].contextInfo || {}),
|
...(innerMessage[key].contextInfo || {}),
|
||||||
expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL,
|
expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL
|
||||||
//ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
|
//ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -606,7 +587,7 @@ export const generateWAMessageFromContent = (
|
|||||||
key: {
|
key: {
|
||||||
remoteJid: jid,
|
remoteJid: jid,
|
||||||
fromMe: true,
|
fromMe: true,
|
||||||
id: options?.messageId || generateMessageIDV2(),
|
id: options?.messageId || generateMessageIDV2()
|
||||||
},
|
},
|
||||||
message: message,
|
message: message,
|
||||||
messageTimestamp: timestamp,
|
messageTimestamp: timestamp,
|
||||||
@@ -617,21 +598,10 @@ export const generateWAMessageFromContent = (
|
|||||||
return WAProto.WebMessageInfo.fromObject(messageJSON)
|
return WAProto.WebMessageInfo.fromObject(messageJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateWAMessage = async(
|
export const generateWAMessage = async (jid: string, content: AnyMessageContent, options: MessageGenerationOptions) => {
|
||||||
jid: string,
|
|
||||||
content: AnyMessageContent,
|
|
||||||
options: MessageGenerationOptions,
|
|
||||||
) => {
|
|
||||||
// ensure msg ID is with every log
|
// ensure msg ID is with every log
|
||||||
options.logger = options?.logger?.child({ msgId: options.messageId })
|
options.logger = options?.logger?.child({ msgId: options.messageId })
|
||||||
return generateWAMessageFromContent(
|
return generateWAMessageFromContent(jid, await generateWAMessageContent(content, options), options)
|
||||||
jid,
|
|
||||||
await generateWAMessageContent(
|
|
||||||
content,
|
|
||||||
options
|
|
||||||
),
|
|
||||||
options
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the key to access the true type of content */
|
/** Get the key to access the true type of content */
|
||||||
@@ -668,12 +638,12 @@ export const normalizeMessageContent = (content: WAMessageContent | null | undef
|
|||||||
|
|
||||||
function getFutureProofMessage(message: typeof content) {
|
function getFutureProofMessage(message: typeof content) {
|
||||||
return (
|
return (
|
||||||
message?.ephemeralMessage
|
message?.ephemeralMessage ||
|
||||||
|| message?.viewOnceMessage
|
message?.viewOnceMessage ||
|
||||||
|| message?.documentWithCaptionMessage
|
message?.documentWithCaptionMessage ||
|
||||||
|| message?.viewOnceMessageV2
|
message?.viewOnceMessageV2 ||
|
||||||
|| message?.viewOnceMessageV2Extension
|
message?.viewOnceMessageV2Extension ||
|
||||||
|| message?.editedMessage
|
message?.editedMessage
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -683,7 +653,9 @@ export const normalizeMessageContent = (content: WAMessageContent | null | undef
|
|||||||
* Eg. extracts the inner message from a disappearing message/view once message
|
* Eg. extracts the inner message from a disappearing message/view once message
|
||||||
*/
|
*/
|
||||||
export const extractMessageContent = (content: WAMessageContent | undefined | null): WAMessageContent | undefined => {
|
export const extractMessageContent = (content: WAMessageContent | undefined | null): WAMessageContent | undefined => {
|
||||||
const extractFromTemplateMessage = (msg: proto.Message.TemplateMessage.IHydratedFourRowTemplate | proto.Message.IButtonsMessage) => {
|
const extractFromTemplateMessage = (
|
||||||
|
msg: proto.Message.TemplateMessage.IHydratedFourRowTemplate | proto.Message.IButtonsMessage
|
||||||
|
) => {
|
||||||
if (msg.imageMessage) {
|
if (msg.imageMessage) {
|
||||||
return { imageMessage: msg.imageMessage }
|
return { imageMessage: msg.imageMessage }
|
||||||
} else if (msg.documentMessage) {
|
} else if (msg.documentMessage) {
|
||||||
@@ -695,9 +667,7 @@ export const extractMessageContent = (content: WAMessageContent | undefined | nu
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
conversation:
|
conversation:
|
||||||
'contentText' in msg
|
'contentText' in msg ? msg.contentText : 'hydratedContentText' in msg ? msg.hydratedContentText : ''
|
||||||
? msg.contentText
|
|
||||||
: ('hydratedContentText' in msg ? msg.hydratedContentText : '')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -726,11 +696,16 @@ export const extractMessageContent = (content: WAMessageContent | undefined | nu
|
|||||||
/**
|
/**
|
||||||
* Returns the device predicted by message ID
|
* Returns the device predicted by message ID
|
||||||
*/
|
*/
|
||||||
export const getDevice = (id: string) => /^3A.{18}$/.test(id) ? 'ios' :
|
export const getDevice = (id: string) =>
|
||||||
/^3E.{20}$/.test(id) ? 'web' :
|
/^3A.{18}$/.test(id)
|
||||||
/^(.{21}|.{32})$/.test(id) ? 'android' :
|
? 'ios'
|
||||||
/^(3F|.{18}$)/.test(id) ? 'desktop' :
|
: /^3E.{20}$/.test(id)
|
||||||
'unknown'
|
? 'web'
|
||||||
|
: /^(.{21}|.{32})$/.test(id)
|
||||||
|
? 'android'
|
||||||
|
: /^(3F|.{18}$)/.test(id)
|
||||||
|
? 'desktop'
|
||||||
|
: 'unknown'
|
||||||
|
|
||||||
/** Upserts a receipt in the message */
|
/** Upserts a receipt in the message */
|
||||||
export const updateMessageWithReceipt = (msg: Pick<WAMessage, 'userReceipt'>, receipt: MessageUserReceipt) => {
|
export const updateMessageWithReceipt = (msg: Pick<WAMessage, 'userReceipt'>, receipt: MessageUserReceipt) => {
|
||||||
@@ -747,8 +722,7 @@ export const updateMessageWithReceipt = (msg: Pick<WAMessage, 'userReceipt'>, re
|
|||||||
export const updateMessageWithReaction = (msg: Pick<WAMessage, 'reactions'>, reaction: proto.IReaction) => {
|
export const updateMessageWithReaction = (msg: Pick<WAMessage, 'reactions'>, reaction: proto.IReaction) => {
|
||||||
const authorID = getKeyAuthor(reaction.key)
|
const authorID = getKeyAuthor(reaction.key)
|
||||||
|
|
||||||
const reactions = (msg.reactions || [])
|
const reactions = (msg.reactions || []).filter(r => getKeyAuthor(r.key) !== authorID)
|
||||||
.filter(r => getKeyAuthor(r.key) !== authorID)
|
|
||||||
if (reaction.text) {
|
if (reaction.text) {
|
||||||
reactions.push(reaction)
|
reactions.push(reaction)
|
||||||
}
|
}
|
||||||
@@ -757,14 +731,10 @@ export const updateMessageWithReaction = (msg: Pick<WAMessage, 'reactions'>, rea
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Update the message with a new poll update */
|
/** Update the message with a new poll update */
|
||||||
export const updateMessageWithPollUpdate = (
|
export const updateMessageWithPollUpdate = (msg: Pick<WAMessage, 'pollUpdates'>, update: proto.IPollUpdate) => {
|
||||||
msg: Pick<WAMessage, 'pollUpdates'>,
|
|
||||||
update: proto.IPollUpdate
|
|
||||||
) => {
|
|
||||||
const authorID = getKeyAuthor(update.pollUpdateMessageKey)
|
const authorID = getKeyAuthor(update.pollUpdateMessageKey)
|
||||||
|
|
||||||
const reactions = (msg.pollUpdates || [])
|
const reactions = (msg.pollUpdates || []).filter(r => getKeyAuthor(r.pollUpdateMessageKey) !== authorID)
|
||||||
.filter(r => getKeyAuthor(r.pollUpdateMessageKey) !== authorID)
|
|
||||||
if (update.vote?.selectedOptions?.length) {
|
if (update.vote?.selectedOptions?.length) {
|
||||||
reactions.push(update)
|
reactions.push(update)
|
||||||
}
|
}
|
||||||
@@ -787,15 +757,22 @@ export function getAggregateVotesInPollMessage(
|
|||||||
{ message, pollUpdates }: Pick<WAMessage, 'pollUpdates' | 'message'>,
|
{ message, pollUpdates }: Pick<WAMessage, 'pollUpdates' | 'message'>,
|
||||||
meId?: string
|
meId?: string
|
||||||
) {
|
) {
|
||||||
const opts = message?.pollCreationMessage?.options || message?.pollCreationMessageV2?.options || message?.pollCreationMessageV3?.options || []
|
const opts =
|
||||||
const voteHashMap = opts.reduce((acc, opt) => {
|
message?.pollCreationMessage?.options ||
|
||||||
|
message?.pollCreationMessageV2?.options ||
|
||||||
|
message?.pollCreationMessageV3?.options ||
|
||||||
|
[]
|
||||||
|
const voteHashMap = opts.reduce(
|
||||||
|
(acc, opt) => {
|
||||||
const hash = sha256(Buffer.from(opt.optionName || '')).toString()
|
const hash = sha256(Buffer.from(opt.optionName || '')).toString()
|
||||||
acc[hash] = {
|
acc[hash] = {
|
||||||
name: opt.optionName || '',
|
name: opt.optionName || '',
|
||||||
voters: []
|
voters: []
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
}, {} as { [_: string]: VoteAggregation })
|
},
|
||||||
|
{} as { [_: string]: VoteAggregation }
|
||||||
|
)
|
||||||
|
|
||||||
for (const update of pollUpdates || []) {
|
for (const update of pollUpdates || []) {
|
||||||
const { vote } = update
|
const { vote } = update
|
||||||
@@ -814,9 +791,7 @@ export function getAggregateVotesInPollMessage(
|
|||||||
data = voteHashMap[hash]
|
data = voteHashMap[hash]
|
||||||
}
|
}
|
||||||
|
|
||||||
voteHashMap[hash].voters.push(
|
voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId))
|
||||||
getKeyAuthor(update.pollUpdateMessageKey, meId)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,7 +800,7 @@ export function getAggregateVotesInPollMessage(
|
|||||||
|
|
||||||
/** Given a list of message keys, aggregates them by chat & sender. Useful for sending read receipts in bulk */
|
/** Given a list of message keys, aggregates them by chat & sender. Useful for sending read receipts in bulk */
|
||||||
export const aggregateMessageKeysNotFromMe = (keys: proto.IMessageKey[]) => {
|
export const aggregateMessageKeysNotFromMe = (keys: proto.IMessageKey[]) => {
|
||||||
const keyMap: { [id: string]: { jid: string, participant: string | undefined, messageIds: string[] } } = { }
|
const keyMap: { [id: string]: { jid: string; participant: string | undefined; messageIds: string[] } } = {}
|
||||||
for (const { remoteJid, id, participant, fromMe } of keys) {
|
for (const { remoteJid, id, participant, fromMe } of keys) {
|
||||||
if (!fromMe) {
|
if (!fromMe) {
|
||||||
const uqKey = `${remoteJid}:${participant || ''}`
|
const uqKey = `${remoteJid}:${participant || ''}`
|
||||||
@@ -860,10 +835,12 @@ export const downloadMediaMessage = async<Type extends 'buffer' | 'stream'>(
|
|||||||
options: MediaDownloadOptions,
|
options: MediaDownloadOptions,
|
||||||
ctx?: DownloadMediaMessageContext
|
ctx?: DownloadMediaMessageContext
|
||||||
) => {
|
) => {
|
||||||
const result = await downloadMsg()
|
const result = await downloadMsg().catch(async error => {
|
||||||
.catch(async(error) => {
|
if (
|
||||||
if(ctx && axios.isAxiosError(error) && // check if the message requires a reupload
|
ctx &&
|
||||||
REUPLOAD_REQUIRED_STATUS.includes(error.response?.status!)) {
|
axios.isAxiosError(error) && // check if the message requires a reupload
|
||||||
|
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)
|
||||||
@@ -918,16 +895,14 @@ export const downloadMediaMessage = async<Type extends 'buffer' | 'stream'>(
|
|||||||
/** 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 | null | undefined) => {
|
export const assertMediaContent = (content: proto.IMessage | null | undefined) => {
|
||||||
content = extractMessageContent(content)
|
content = extractMessageContent(content)
|
||||||
const mediaContent = content?.documentMessage
|
const mediaContent =
|
||||||
|| content?.imageMessage
|
content?.documentMessage ||
|
||||||
|| content?.videoMessage
|
content?.imageMessage ||
|
||||||
|| content?.audioMessage
|
content?.videoMessage ||
|
||||||
|| content?.stickerMessage
|
content?.audioMessage ||
|
||||||
|
content?.stickerMessage
|
||||||
if (!mediaContent) {
|
if (!mediaContent) {
|
||||||
throw new Boom(
|
throw new Boom('given message is not a media message', { statusCode: 400, data: content })
|
||||||
'given message is not a media message',
|
|
||||||
{ statusCode: 400, data: content }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mediaContent
|
return mediaContent
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
import { AxiosRequestConfig } from 'axios'
|
import { AxiosRequestConfig } from 'axios'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { AuthenticationCreds, BaileysEventEmitter, CacheStore, Chat, GroupMetadata, ParticipantAction, RequestJoinAction, RequestJoinMethod, SignalKeyStoreWithTransaction, WAMessageStubType } from '../Types'
|
import {
|
||||||
|
AuthenticationCreds,
|
||||||
|
BaileysEventEmitter,
|
||||||
|
CacheStore,
|
||||||
|
Chat,
|
||||||
|
GroupMetadata,
|
||||||
|
ParticipantAction,
|
||||||
|
RequestJoinAction,
|
||||||
|
RequestJoinMethod,
|
||||||
|
SignalKeyStoreWithTransaction,
|
||||||
|
WAMessageStubType
|
||||||
|
} from '../Types'
|
||||||
import { getContentType, normalizeMessageContent } from '../Utils/messages'
|
import { getContentType, normalizeMessageContent } from '../Utils/messages'
|
||||||
import { areJidsSameUser, isJidBroadcast, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary'
|
import { areJidsSameUser, isJidBroadcast, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary'
|
||||||
import { aesDecryptGCM, hmacSign } from './crypto'
|
import { aesDecryptGCM, hmacSign } from './crypto'
|
||||||
@@ -25,9 +36,7 @@ const REAL_MSG_STUB_TYPES = new Set([
|
|||||||
WAMessageStubType.CALL_MISSED_VOICE
|
WAMessageStubType.CALL_MISSED_VOICE
|
||||||
])
|
])
|
||||||
|
|
||||||
const REAL_MSG_REQ_ME_STUB_TYPES = new Set([
|
const REAL_MSG_REQ_ME_STUB_TYPES = new Set([WAMessageStubType.GROUP_PARTICIPANT_ADD])
|
||||||
WAMessageStubType.GROUP_PARTICIPANT_ADD
|
|
||||||
])
|
|
||||||
|
|
||||||
/** Cleans a received message to further processing */
|
/** Cleans a received message to further processing */
|
||||||
export const cleanMessage = (message: proto.IWebMessageInfo, meId: string) => {
|
export const cleanMessage = (message: proto.IWebMessageInfo, meId: string) => {
|
||||||
@@ -52,9 +61,9 @@ export const cleanMessage = (message: proto.IWebMessageInfo, meId: string) => {
|
|||||||
// we've to correct the key to be from them, or some other participant
|
// we've to correct the key to be from them, or some other participant
|
||||||
msgKey.fromMe = !msgKey.fromMe
|
msgKey.fromMe = !msgKey.fromMe
|
||||||
? areJidsSameUser(msgKey.participant || msgKey.remoteJid!, meId)
|
? areJidsSameUser(msgKey.participant || msgKey.remoteJid!, meId)
|
||||||
// if the message being reacted to, was from them
|
: // if the message being reacted to, was from them
|
||||||
// fromMe automatically becomes false
|
// fromMe automatically becomes false
|
||||||
: false
|
false
|
||||||
// set the remoteJid to being the same as the chat the message came from
|
// set the remoteJid to being the same as the chat the message came from
|
||||||
msgKey.remoteJid = message.key.remoteJid
|
msgKey.remoteJid = message.key.remoteJid
|
||||||
// set participant of the message
|
// set participant of the message
|
||||||
@@ -67,33 +76,26 @@ export const isRealMessage = (message: proto.IWebMessageInfo, meId: string) => {
|
|||||||
const normalizedContent = normalizeMessageContent(message.message)
|
const normalizedContent = normalizeMessageContent(message.message)
|
||||||
const hasSomeContent = !!getContentType(normalizedContent)
|
const hasSomeContent = !!getContentType(normalizedContent)
|
||||||
return (
|
return (
|
||||||
!!normalizedContent
|
(!!normalizedContent ||
|
||||||
|| REAL_MSG_STUB_TYPES.has(message.messageStubType!)
|
REAL_MSG_STUB_TYPES.has(message.messageStubType!) ||
|
||||||
|| (
|
(REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType!) &&
|
||||||
REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType!)
|
message.messageStubParameters?.some(p => areJidsSameUser(meId, p)))) &&
|
||||||
&& message.messageStubParameters?.some(p => areJidsSameUser(meId, p))
|
hasSomeContent &&
|
||||||
|
!normalizedContent?.protocolMessage &&
|
||||||
|
!normalizedContent?.reactionMessage &&
|
||||||
|
!normalizedContent?.pollUpdateMessage
|
||||||
)
|
)
|
||||||
)
|
|
||||||
&& hasSomeContent
|
|
||||||
&& !normalizedContent?.protocolMessage
|
|
||||||
&& !normalizedContent?.reactionMessage
|
|
||||||
&& !normalizedContent?.pollUpdateMessage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shouldIncrementChatUnread = (message: proto.IWebMessageInfo) => (
|
export const shouldIncrementChatUnread = (message: proto.IWebMessageInfo) =>
|
||||||
!message.key.fromMe && !message.messageStubType
|
!message.key.fromMe && !message.messageStubType
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the ID of the chat from the given key.
|
* Get the ID of the chat from the given key.
|
||||||
* Typically -- that'll be the remoteJid, but for broadcasts, it'll be the participant
|
* Typically -- that'll be the remoteJid, but for broadcasts, it'll be the participant
|
||||||
*/
|
*/
|
||||||
export const getChatId = ({ remoteJid, participant, fromMe }: proto.IMessageKey) => {
|
export const getChatId = ({ remoteJid, participant, fromMe }: proto.IMessageKey) => {
|
||||||
if(
|
if (isJidBroadcast(remoteJid!) && !isJidStatusBroadcast(remoteJid!) && !fromMe) {
|
||||||
isJidBroadcast(remoteJid!)
|
|
||||||
&& !isJidStatusBroadcast(remoteJid!)
|
|
||||||
&& !fromMe
|
|
||||||
) {
|
|
||||||
return participant!
|
return participant!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,22 +121,15 @@ type PollContext = {
|
|||||||
*/
|
*/
|
||||||
export function decryptPollVote(
|
export function decryptPollVote(
|
||||||
{ encPayload, encIv }: proto.Message.IPollEncValue,
|
{ encPayload, encIv }: proto.Message.IPollEncValue,
|
||||||
{
|
{ pollCreatorJid, pollMsgId, pollEncKey, voterJid }: PollContext
|
||||||
pollCreatorJid,
|
|
||||||
pollMsgId,
|
|
||||||
pollEncKey,
|
|
||||||
voterJid,
|
|
||||||
}: PollContext
|
|
||||||
) {
|
) {
|
||||||
const sign = Buffer.concat(
|
const sign = Buffer.concat([
|
||||||
[
|
|
||||||
toBinary(pollMsgId),
|
toBinary(pollMsgId),
|
||||||
toBinary(pollCreatorJid),
|
toBinary(pollCreatorJid),
|
||||||
toBinary(voterJid),
|
toBinary(voterJid),
|
||||||
toBinary('Poll Vote'),
|
toBinary('Poll Vote'),
|
||||||
new Uint8Array([1])
|
new Uint8Array([1])
|
||||||
]
|
])
|
||||||
)
|
|
||||||
|
|
||||||
const key0 = hmacSign(pollEncKey, new Uint8Array(32), 'sha256')
|
const key0 = hmacSign(pollEncKey, new Uint8Array(32), 'sha256')
|
||||||
const decKey = hmacSign(sign, key0, 'sha256')
|
const decKey = hmacSign(sign, key0, 'sha256')
|
||||||
@@ -150,15 +145,7 @@ export function decryptPollVote(
|
|||||||
|
|
||||||
const processMessage = async (
|
const processMessage = async (
|
||||||
message: proto.IWebMessageInfo,
|
message: proto.IWebMessageInfo,
|
||||||
{
|
{ shouldProcessHistoryMsg, placeholderResendCache, ev, creds, keyStore, logger, options }: ProcessMessageContext
|
||||||
shouldProcessHistoryMsg,
|
|
||||||
placeholderResendCache,
|
|
||||||
ev,
|
|
||||||
creds,
|
|
||||||
keyStore,
|
|
||||||
logger,
|
|
||||||
options
|
|
||||||
}: ProcessMessageContext
|
|
||||||
) => {
|
) => {
|
||||||
const meId = creds.me!.id
|
const meId = creds.me!.id
|
||||||
const { accountSettings } = creds
|
const { accountSettings } = creds
|
||||||
@@ -179,10 +166,7 @@ const processMessage = async(
|
|||||||
|
|
||||||
// unarchive chat if it's a real message, or someone reacted to our message
|
// unarchive chat if it's a real message, or someone reacted to our message
|
||||||
// and we've the unarchive chats setting on
|
// and we've the unarchive chats setting on
|
||||||
if(
|
if ((isRealMsg || content?.reactionMessage?.key?.fromMe) && accountSettings?.unarchiveChats) {
|
||||||
(isRealMsg || content?.reactionMessage?.key?.fromMe)
|
|
||||||
&& accountSettings?.unarchiveChats
|
|
||||||
) {
|
|
||||||
chat.archived = false
|
chat.archived = false
|
||||||
chat.readOnly = false
|
chat.readOnly = false
|
||||||
}
|
}
|
||||||
@@ -195,12 +179,15 @@ const processMessage = async(
|
|||||||
const process = shouldProcessHistoryMsg
|
const process = shouldProcessHistoryMsg
|
||||||
const isLatest = !creds.processedHistoryMessages?.length
|
const isLatest = !creds.processedHistoryMessages?.length
|
||||||
|
|
||||||
logger?.info({
|
logger?.info(
|
||||||
|
{
|
||||||
histNotification,
|
histNotification,
|
||||||
process,
|
process,
|
||||||
id: message.key.id,
|
id: message.key.id,
|
||||||
isLatest,
|
isLatest
|
||||||
}, 'got history notification')
|
},
|
||||||
|
'got history notification'
|
||||||
|
)
|
||||||
|
|
||||||
if (process) {
|
if (process) {
|
||||||
if (histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND) {
|
if (histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND) {
|
||||||
@@ -212,17 +199,11 @@ const processMessage = async(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await downloadAndProcessHistorySyncNotification(
|
const data = await downloadAndProcessHistorySyncNotification(histNotification, options)
|
||||||
histNotification,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
|
|
||||||
ev.emit('messaging-history.set', {
|
ev.emit('messaging-history.set', {
|
||||||
...data,
|
...data,
|
||||||
isLatest:
|
isLatest: histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND ? isLatest : undefined,
|
||||||
histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND
|
|
||||||
? isLatest
|
|
||||||
: undefined,
|
|
||||||
peerDataRequestSessionId: histNotification.peerDataRequestSessionId
|
peerDataRequestSessionId: histNotification.peerDataRequestSessionId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -232,8 +213,7 @@ const processMessage = async(
|
|||||||
const keys = protocolMsg.appStateSyncKeyShare!.keys
|
const keys = protocolMsg.appStateSyncKeyShare!.keys
|
||||||
if (keys?.length) {
|
if (keys?.length) {
|
||||||
let newAppStateSyncKeyId = ''
|
let newAppStateSyncKeyId = ''
|
||||||
await keyStore.transaction(
|
await keyStore.transaction(async () => {
|
||||||
async() => {
|
|
||||||
const newKeys: string[] = []
|
const newKeys: string[] = []
|
||||||
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')
|
||||||
@@ -244,12 +224,8 @@ const processMessage = async(
|
|||||||
newAppStateSyncKeyId = strKeyId
|
newAppStateSyncKeyId = strKeyId
|
||||||
}
|
}
|
||||||
|
|
||||||
logger?.info(
|
logger?.info({ newAppStateSyncKeyId, newKeys }, 'injecting new app state sync keys')
|
||||||
{ newAppStateSyncKeyId, newKeys },
|
})
|
||||||
'injecting new app state sync keys'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId })
|
ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId })
|
||||||
} else {
|
} else {
|
||||||
@@ -298,9 +274,7 @@ const processMessage = async(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case proto.Message.ProtocolMessage.Type.MESSAGE_EDIT:
|
case proto.Message.ProtocolMessage.Type.MESSAGE_EDIT:
|
||||||
ev.emit(
|
ev.emit('messages.update', [
|
||||||
'messages.update',
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
// flip the sender / fromMe properties because they're in the perspective of the sender
|
// flip the sender / fromMe properties because they're in the perspective of the sender
|
||||||
key: { ...message.key, id: protocolMsg.key?.id },
|
key: { ...message.key, id: protocolMsg.key?.id },
|
||||||
@@ -315,26 +289,26 @@ const processMessage = async(
|
|||||||
: message.messageTimestamp
|
: message.messageTimestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if (content?.reactionMessage) {
|
} else if (content?.reactionMessage) {
|
||||||
const reaction: proto.IReaction = {
|
const reaction: proto.IReaction = {
|
||||||
...content.reactionMessage,
|
...content.reactionMessage,
|
||||||
key: message.key,
|
key: message.key
|
||||||
}
|
}
|
||||||
ev.emit('messages.reaction', [{
|
ev.emit('messages.reaction', [
|
||||||
|
{
|
||||||
reaction,
|
reaction,
|
||||||
key: content.reactionMessage?.key!,
|
key: content.reactionMessage?.key!
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
} else if (message.messageStubType) {
|
} else if (message.messageStubType) {
|
||||||
const jid = message.key?.remoteJid!
|
const jid = message.key?.remoteJid!
|
||||||
//let actor = whatsappID (message.participant)
|
//let actor = whatsappID (message.participant)
|
||||||
let participants: string[]
|
let participants: string[]
|
||||||
const emitParticipantsUpdate = (action: ParticipantAction) => (
|
const emitParticipantsUpdate = (action: ParticipantAction) =>
|
||||||
ev.emit('group-participants.update', { id: jid, author: message.participant!, participants, action })
|
ev.emit('group-participants.update', { id: jid, author: message.participant!, participants, action })
|
||||||
)
|
|
||||||
const emitGroupUpdate = (update: Partial<GroupMetadata>) => {
|
const emitGroupUpdate = (update: Partial<GroupMetadata>) => {
|
||||||
ev.emit('groups.update', [{ id: jid, ...update, author: message.participant ?? undefined }])
|
ev.emit('groups.update', [{ id: jid, ...update, author: message.participant ?? undefined }])
|
||||||
}
|
}
|
||||||
@@ -415,7 +389,6 @@ const processMessage = async(
|
|||||||
emitGroupRequestJoin(participant, action, method)
|
emitGroupRequestJoin(participant, action, method)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* else if(content?.pollUpdateMessage) {
|
} /* else if(content?.pollUpdateMessage) {
|
||||||
const creationMsgKey = content.pollUpdateMessage.pollCreationMessageKey!
|
const creationMsgKey = content.pollUpdateMessage.pollCreationMessageKey!
|
||||||
// we need to fetch the poll creation message to get the poll enc key
|
// we need to fetch the poll creation message to get the poll enc key
|
||||||
|
|||||||
@@ -1,16 +1,30 @@
|
|||||||
import { chunk } from 'lodash'
|
import { chunk } from 'lodash'
|
||||||
import { KEY_BUNDLE_TYPE } from '../Defaults'
|
import { KEY_BUNDLE_TYPE } from '../Defaults'
|
||||||
import { SignalRepository } from '../Types'
|
import { SignalRepository } from '../Types'
|
||||||
import { AuthenticationCreds, AuthenticationState, KeyPair, SignalIdentity, SignalKeyStore, SignedKeyPair } from '../Types/Auth'
|
import {
|
||||||
import { assertNodeErrorFree, BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildUInt, jidDecode, JidWithDevice, S_WHATSAPP_NET } from '../WABinary'
|
AuthenticationCreds,
|
||||||
|
AuthenticationState,
|
||||||
|
KeyPair,
|
||||||
|
SignalIdentity,
|
||||||
|
SignalKeyStore,
|
||||||
|
SignedKeyPair
|
||||||
|
} from '../Types/Auth'
|
||||||
|
import {
|
||||||
|
assertNodeErrorFree,
|
||||||
|
BinaryNode,
|
||||||
|
getBinaryNodeChild,
|
||||||
|
getBinaryNodeChildBuffer,
|
||||||
|
getBinaryNodeChildren,
|
||||||
|
getBinaryNodeChildUInt,
|
||||||
|
jidDecode,
|
||||||
|
JidWithDevice,
|
||||||
|
S_WHATSAPP_NET
|
||||||
|
} from '../WABinary'
|
||||||
import { DeviceListData, ParsedDeviceInfo, USyncQueryResultList } from '../WAUSync'
|
import { DeviceListData, ParsedDeviceInfo, USyncQueryResultList } from '../WAUSync'
|
||||||
import { Curve, generateSignalPubKey } from './crypto'
|
import { Curve, generateSignalPubKey } from './crypto'
|
||||||
import { encodeBigEndian } from './generics'
|
import { encodeBigEndian } from './generics'
|
||||||
|
|
||||||
export const createSignalIdentity = (
|
export const createSignalIdentity = (wid: string, accountSignatureKey: Uint8Array): SignalIdentity => {
|
||||||
wid: string,
|
|
||||||
accountSignatureKey: Uint8Array
|
|
||||||
): SignalIdentity => {
|
|
||||||
return {
|
return {
|
||||||
identifier: { name: wid, deviceId: 0 },
|
identifier: { name: wid, deviceId: 0 },
|
||||||
identifierKey: generateSignalPubKey(accountSignatureKey)
|
identifierKey: generateSignalPubKey(accountSignatureKey)
|
||||||
@@ -40,12 +54,11 @@ export const generateOrGetPreKeys = (creds: AuthenticationCreds, range: number)
|
|||||||
return {
|
return {
|
||||||
newPreKeys,
|
newPreKeys,
|
||||||
lastPreKeyId,
|
lastPreKeyId,
|
||||||
preKeysRange: [creds.firstUnuploadedPreKeyId, range] as const,
|
preKeysRange: [creds.firstUnuploadedPreKeyId, range] as const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const xmppSignedPreKey = (key: SignedKeyPair): BinaryNode => (
|
export const xmppSignedPreKey = (key: SignedKeyPair): BinaryNode => ({
|
||||||
{
|
|
||||||
tag: 'skey',
|
tag: 'skey',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: [
|
content: [
|
||||||
@@ -53,31 +66,26 @@ export const xmppSignedPreKey = (key: SignedKeyPair): BinaryNode => (
|
|||||||
{ tag: 'value', attrs: {}, content: key.keyPair.public },
|
{ tag: 'value', attrs: {}, content: key.keyPair.public },
|
||||||
{ tag: 'signature', attrs: {}, content: key.signature }
|
{ tag: 'signature', attrs: {}, content: key.signature }
|
||||||
]
|
]
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
export const xmppPreKey = (pair: KeyPair, id: number): BinaryNode => (
|
export const xmppPreKey = (pair: KeyPair, id: number): BinaryNode => ({
|
||||||
{
|
|
||||||
tag: 'key',
|
tag: 'key',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: [
|
content: [
|
||||||
{ tag: 'id', attrs: {}, content: encodeBigEndian(id, 3) },
|
{ tag: 'id', attrs: {}, content: encodeBigEndian(id, 3) },
|
||||||
{ tag: 'value', attrs: {}, content: pair.public }
|
{ tag: 'value', attrs: {}, content: pair.public }
|
||||||
]
|
]
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
export const parseAndInjectE2ESessions = async(
|
export const parseAndInjectE2ESessions = async (node: BinaryNode, repository: SignalRepository) => {
|
||||||
node: BinaryNode,
|
const extractKey = (key: BinaryNode) =>
|
||||||
repository: SignalRepository
|
key
|
||||||
) => {
|
? {
|
||||||
const extractKey = (key: BinaryNode) => (
|
|
||||||
key ? ({
|
|
||||||
keyId: getBinaryNodeChildUInt(key, 'id', 3)!,
|
keyId: getBinaryNodeChildUInt(key, 'id', 3)!,
|
||||||
publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')!),
|
publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')!),
|
||||||
signature: getBinaryNodeChildBuffer(key, 'signature')!,
|
signature: getBinaryNodeChildBuffer(key, 'signature')!
|
||||||
}) : undefined
|
}
|
||||||
)
|
: undefined
|
||||||
const nodes = getBinaryNodeChildren(getBinaryNodeChild(node, 'list'), 'user')
|
const nodes = getBinaryNodeChildren(getBinaryNodeChild(node, 'list'), 'user')
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
assertNodeErrorFree(node)
|
assertNodeErrorFree(node)
|
||||||
@@ -92,8 +100,7 @@ export const parseAndInjectE2ESessions = async(
|
|||||||
const chunks = chunk(nodes, chunkSize)
|
const chunks = chunk(nodes, chunkSize)
|
||||||
for (const nodesChunk of chunks) {
|
for (const nodesChunk of chunks) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
nodesChunk.map(
|
nodesChunk.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')!
|
||||||
@@ -109,8 +116,7 @@ export const parseAndInjectE2ESessions = async(
|
|||||||
preKey: extractKey(key)!
|
preKey: extractKey(key)!
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,9 +126,8 @@ export const extractDeviceJids = (result: USyncQueryResultList[], myJid: string,
|
|||||||
|
|
||||||
const extracted: JidWithDevice[] = []
|
const extracted: JidWithDevice[] = []
|
||||||
|
|
||||||
|
|
||||||
for (const userResult of result) {
|
for (const userResult of result) {
|
||||||
const { devices, id } = userResult as { devices: ParsedDeviceInfo, id: string }
|
const { devices, id } = userResult as { devices: ParsedDeviceInfo; id: string }
|
||||||
const { user } = jidDecode(id)!
|
const { user } = jidDecode(id)!
|
||||||
const deviceList = devices?.deviceList as DeviceListData[]
|
const deviceList = devices?.deviceList as DeviceListData[]
|
||||||
if (Array.isArray(deviceList)) {
|
if (Array.isArray(deviceList)) {
|
||||||
@@ -169,7 +174,7 @@ export const getNextPreKeysNode = async(state: AuthenticationState, count: numbe
|
|||||||
attrs: {
|
attrs: {
|
||||||
xmlns: 'encrypt',
|
xmlns: 'encrypt',
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{ tag: 'registration', attrs: {}, content: encodeBigEndian(creds.registrationId) },
|
{ tag: 'registration', attrs: {}, content: encodeBigEndian(creds.registrationId) },
|
||||||
|
|||||||
@@ -30,13 +30,15 @@ const getFileLock = (path: string): Mutex => {
|
|||||||
* Again, I wouldn't endorse this for any production level use other than perhaps a bot.
|
* Again, I wouldn't endorse this for any production level use other than perhaps a bot.
|
||||||
* Would recommend writing an auth state for use with a proper SQL or No-SQL DB
|
* Would recommend writing an auth state for use with a proper SQL or No-SQL DB
|
||||||
* */
|
* */
|
||||||
export const useMultiFileAuthState = async(folder: string): Promise<{ state: AuthenticationState, saveCreds: () => Promise<void> }> => {
|
export const useMultiFileAuthState = async (
|
||||||
|
folder: string
|
||||||
|
): Promise<{ state: AuthenticationState; saveCreds: () => Promise<void> }> => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const writeData = async (data: any, file: string) => {
|
const writeData = async (data: any, file: string) => {
|
||||||
const filePath = join(folder, fixFileName(file)!)
|
const filePath = join(folder, fixFileName(file)!)
|
||||||
const mutex = getFileLock(filePath)
|
const mutex = getFileLock(filePath)
|
||||||
|
|
||||||
return mutex.acquire().then(async(release) => {
|
return mutex.acquire().then(async release => {
|
||||||
try {
|
try {
|
||||||
await writeFile(filePath, JSON.stringify(data, BufferJSON.replacer))
|
await writeFile(filePath, JSON.stringify(data, BufferJSON.replacer))
|
||||||
} finally {
|
} finally {
|
||||||
@@ -50,7 +52,7 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut
|
|||||||
const filePath = join(folder, fixFileName(file)!)
|
const filePath = join(folder, fixFileName(file)!)
|
||||||
const mutex = getFileLock(filePath)
|
const mutex = getFileLock(filePath)
|
||||||
|
|
||||||
return await mutex.acquire().then(async(release) => {
|
return await mutex.acquire().then(async release => {
|
||||||
try {
|
try {
|
||||||
const data = await readFile(filePath, { encoding: 'utf-8' })
|
const data = await readFile(filePath, { encoding: 'utf-8' })
|
||||||
return JSON.parse(data, BufferJSON.reviver)
|
return JSON.parse(data, BufferJSON.reviver)
|
||||||
@@ -68,7 +70,7 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut
|
|||||||
const filePath = join(folder, fixFileName(file)!)
|
const filePath = join(folder, fixFileName(file)!)
|
||||||
const mutex = getFileLock(filePath)
|
const mutex = getFileLock(filePath)
|
||||||
|
|
||||||
return mutex.acquire().then(async(release) => {
|
return mutex.acquire().then(async release => {
|
||||||
try {
|
try {
|
||||||
await unlink(filePath)
|
await unlink(filePath)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -76,14 +78,15 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut
|
|||||||
release()
|
release()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch{
|
} catch {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const folderInfo = await stat(folder).catch(() => {})
|
const folderInfo = await stat(folder).catch(() => {})
|
||||||
if (folderInfo) {
|
if (folderInfo) {
|
||||||
if (!folderInfo.isDirectory()) {
|
if (!folderInfo.isDirectory()) {
|
||||||
throw new Error(`found something that is not a directory at ${folder}, either delete it or specify a different location`)
|
throw new Error(
|
||||||
|
`found something that is not a directory at ${folder}, either delete it or specify a different location`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await mkdir(folder, { recursive: true })
|
await mkdir(folder, { recursive: true })
|
||||||
@@ -91,7 +94,7 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut
|
|||||||
|
|
||||||
const fixFileName = (file?: string) => file?.replace(/\//g, '__')?.replace(/:/g, '-')
|
const fixFileName = (file?: string) => file?.replace(/\//g, '__')?.replace(/:/g, '-')
|
||||||
|
|
||||||
const creds: AuthenticationCreds = await readData('creds.json') || initAuthCreds()
|
const creds: AuthenticationCreds = (await readData('creds.json')) || initAuthCreds()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: {
|
state: {
|
||||||
@@ -100,21 +103,19 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut
|
|||||||
get: async (type, ids) => {
|
get: async (type, ids) => {
|
||||||
const data: { [_: string]: SignalDataTypeMap[typeof type] } = {}
|
const data: { [_: string]: SignalDataTypeMap[typeof type] } = {}
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
ids.map(
|
ids.map(async id => {
|
||||||
async id => {
|
|
||||||
let value = await readData(`${type}-${id}.json`)
|
let value = await readData(`${type}-${id}.json`)
|
||||||
if (type === 'app-state-sync-key' && value) {
|
if (type === 'app-state-sync-key' && value) {
|
||||||
value = proto.Message.AppStateSyncKeyData.fromObject(value)
|
value = proto.Message.AppStateSyncKeyData.fromObject(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
data[id] = value
|
data[id] = value
|
||||||
}
|
})
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
set: async(data) => {
|
set: async data => {
|
||||||
const tasks: Promise<void>[] = []
|
const tasks: Promise<void>[] = []
|
||||||
for (const category in data) {
|
for (const category in data) {
|
||||||
for (const id in data[category]) {
|
for (const id in data[category]) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const getUserAgent = (config: SocketConfig): proto.ClientPayload.IUserAgent => {
|
|||||||
appVersion: {
|
appVersion: {
|
||||||
primary: config.version[0],
|
primary: config.version[0],
|
||||||
secondary: config.version[1],
|
secondary: config.version[1],
|
||||||
tertiary: config.version[2],
|
tertiary: config.version[2]
|
||||||
},
|
},
|
||||||
platform: proto.ClientPayload.UserAgent.Platform.WEB,
|
platform: proto.ClientPayload.UserAgent.Platform.WEB,
|
||||||
releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
|
releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
|
||||||
@@ -23,13 +23,13 @@ const getUserAgent = (config: SocketConfig): proto.ClientPayload.IUserAgent => {
|
|||||||
localeLanguageIso6391: 'en',
|
localeLanguageIso6391: 'en',
|
||||||
mnc: '000',
|
mnc: '000',
|
||||||
mcc: '000',
|
mcc: '000',
|
||||||
localeCountryIso31661Alpha2: config.countryCode,
|
localeCountryIso31661Alpha2: config.countryCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
|
'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
|
||||||
'Windows': proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
|
Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
|
||||||
}
|
}
|
||||||
|
|
||||||
const getWebInfo = (config: SocketConfig): proto.ClientPayload.IWebInfo => {
|
const getWebInfo = (config: SocketConfig): proto.ClientPayload.IWebInfo => {
|
||||||
@@ -41,12 +41,11 @@ const getWebInfo = (config: SocketConfig): proto.ClientPayload.IWebInfo => {
|
|||||||
return { webSubPlatform }
|
return { webSubPlatform }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const getClientPayload = (config: SocketConfig) => {
|
const getClientPayload = (config: SocketConfig) => {
|
||||||
const payload: proto.IClientPayload = {
|
const payload: proto.IClientPayload = {
|
||||||
connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
|
connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
|
||||||
connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
|
connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
|
||||||
userAgent: getUserAgent(config),
|
userAgent: getUserAgent(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.webInfo = getWebInfo(config)
|
payload.webInfo = getWebInfo(config)
|
||||||
@@ -54,7 +53,6 @@ const getClientPayload = (config: SocketConfig) => {
|
|||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const generateLoginNode = (userJid: string, config: SocketConfig): proto.IClientPayload => {
|
export const generateLoginNode = (userJid: string, config: SocketConfig): proto.IClientPayload => {
|
||||||
const { user, device } = jidDecode(userJid)!
|
const { user, device } = jidDecode(userJid)!
|
||||||
const payload: proto.IClientPayload = {
|
const payload: proto.IClientPayload = {
|
||||||
@@ -62,7 +60,7 @@ export const generateLoginNode = (userJid: string, config: SocketConfig): proto.
|
|||||||
passive: false,
|
passive: false,
|
||||||
pull: true,
|
pull: true,
|
||||||
username: +user,
|
username: +user,
|
||||||
device: device,
|
device: device
|
||||||
}
|
}
|
||||||
return proto.ClientPayload.fromObject(payload)
|
return proto.ClientPayload.fromObject(payload)
|
||||||
}
|
}
|
||||||
@@ -85,7 +83,7 @@ export const generateRegistrationNode = (
|
|||||||
const companion: proto.IDeviceProps = {
|
const companion: proto.IDeviceProps = {
|
||||||
os: config.browser[0],
|
os: config.browser[0],
|
||||||
platformType: getPlatformType(config.browser[1]),
|
platformType: getPlatformType(config.browser[1]),
|
||||||
requireFullSync: config.syncFullHistory,
|
requireFullSync: config.syncFullHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
const companionProto = proto.DeviceProps.encode(companion).finish()
|
const companionProto = proto.DeviceProps.encode(companion).finish()
|
||||||
@@ -102,8 +100,8 @@ export const generateRegistrationNode = (
|
|||||||
eIdent: signedIdentityKey.public,
|
eIdent: signedIdentityKey.public,
|
||||||
eSkeyId: encodeBigEndian(signedPreKey.keyId, 3),
|
eSkeyId: encodeBigEndian(signedPreKey.keyId, 3),
|
||||||
eSkeyVal: signedPreKey.keyPair.public,
|
eSkeyVal: signedPreKey.keyPair.public,
|
||||||
eSkeySig: signedPreKey.signature,
|
eSkeySig: signedPreKey.signature
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return proto.ClientPayload.fromObject(registerPayload)
|
return proto.ClientPayload.fromObject(registerPayload)
|
||||||
@@ -111,7 +109,11 @@ export const generateRegistrationNode = (
|
|||||||
|
|
||||||
export const configureSuccessfulPairing = (
|
export const configureSuccessfulPairing = (
|
||||||
stanza: BinaryNode,
|
stanza: BinaryNode,
|
||||||
{ advSecretKey, signedIdentityKey, signalIdentities }: Pick<AuthenticationCreds, 'advSecretKey' | 'signedIdentityKey' | 'signalIdentities'>
|
{
|
||||||
|
advSecretKey,
|
||||||
|
signedIdentityKey,
|
||||||
|
signalIdentities
|
||||||
|
}: Pick<AuthenticationCreds, 'advSecretKey' | 'signedIdentityKey' | 'signalIdentities'>
|
||||||
) => {
|
) => {
|
||||||
const msgId = stanza.attrs.id
|
const msgId = stanza.attrs.id
|
||||||
|
|
||||||
@@ -158,7 +160,7 @@ export const configureSuccessfulPairing = (
|
|||||||
attrs: {
|
attrs: {
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'result',
|
type: 'result',
|
||||||
id: msgId,
|
id: msgId
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -178,10 +180,7 @@ export const configureSuccessfulPairing = (
|
|||||||
const authUpdate: Partial<AuthenticationCreds> = {
|
const authUpdate: Partial<AuthenticationCreds> = {
|
||||||
account,
|
account,
|
||||||
me: { id: jid, name: bizName },
|
me: { id: jid, name: bizName },
|
||||||
signalIdentities: [
|
signalIdentities: [...(signalIdentities || []), identity],
|
||||||
...(signalIdentities || []),
|
|
||||||
identity
|
|
||||||
],
|
|
||||||
platform: platformNode?.attrs.name
|
platform: platformNode?.attrs.name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,10 +190,7 @@ export const configureSuccessfulPairing = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encodeSignedDeviceIdentity = (
|
export const encodeSignedDeviceIdentity = (account: proto.IADVSignedDeviceIdentity, includeSignatureKey: boolean) => {
|
||||||
account: proto.IADVSignedDeviceIdentity,
|
|
||||||
includeSignatureKey: boolean
|
|
||||||
) => {
|
|
||||||
account = { ...account }
|
account = { ...account }
|
||||||
// set to null if we are not to include the signature key
|
// set to null if we are not to include the signature key
|
||||||
// or if we are including the signature key but it is empty
|
// or if we are including the signature key but it is empty
|
||||||
@@ -202,7 +198,5 @@ export const encodeSignedDeviceIdentity = (
|
|||||||
account.accountSignatureKey = null
|
account.accountSignatureKey = null
|
||||||
}
|
}
|
||||||
|
|
||||||
return proto.ADVSignedDeviceIdentity
|
return proto.ADVSignedDeviceIdentity.encode(account).finish()
|
||||||
.encode(account)
|
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -9,7 +9,8 @@ const inflatePromise = promisify(inflate)
|
|||||||
export const decompressingIfRequired = async (buffer: Buffer) => {
|
export const decompressingIfRequired = async (buffer: Buffer) => {
|
||||||
if (2 & buffer.readUInt8()) {
|
if (2 & buffer.readUInt8()) {
|
||||||
buffer = await inflatePromise(buffer.slice(1))
|
buffer = await inflatePromise(buffer.slice(1))
|
||||||
} else { // nodes with no compression have a 0x00 prefix, we remove that
|
} else {
|
||||||
|
// nodes with no compression have a 0x00 prefix, we remove that
|
||||||
buffer = buffer.slice(1)
|
buffer = buffer.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,11 +154,7 @@ export const decodeDecompressedBinaryNode = (
|
|||||||
const device = readByte()
|
const device = readByte()
|
||||||
const user = readString(readByte())
|
const user = readString(readByte())
|
||||||
|
|
||||||
return jidEncode(
|
return jidEncode(user, domainType === 0 || domainType === 128 ? 's.whatsapp.net' : 'lid', device)
|
||||||
user,
|
|
||||||
domainType === 0 || domainType === 128 ? 's.whatsapp.net' : 'lid',
|
|
||||||
device
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const readString = (tag: number): string => {
|
const readString = (tag: number): string => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import * as constants from './constants'
|
import * as constants from './constants'
|
||||||
import { FullJid, jidDecode } from './jid-utils'
|
import { FullJid, jidDecode } from './jid-utils'
|
||||||
import type { BinaryNode, BinaryNodeCodingOptions } from './types'
|
import type { BinaryNode, BinaryNodeCodingOptions } from './types'
|
||||||
@@ -38,9 +37,7 @@ const encodeBinaryNodeInner = (
|
|||||||
pushBytes([(value >> 8) & 0xff, value & 0xff])
|
pushBytes([(value >> 8) & 0xff, value & 0xff])
|
||||||
}
|
}
|
||||||
|
|
||||||
const pushInt20 = (value: number) => (
|
const pushInt20 = (value: number) => pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff])
|
||||||
pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff])
|
|
||||||
)
|
|
||||||
const writeByteLength = (length: number) => {
|
const writeByteLength = (length: number) => {
|
||||||
if (length >= 4294967296) {
|
if (length >= 4294967296) {
|
||||||
throw new Error('string too large to encode: ' + length)
|
throw new Error('string too large to encode: ' + length)
|
||||||
@@ -221,9 +218,7 @@ const encodeBinaryNodeInner = (
|
|||||||
throw new Error('Invalid node: tag cannot be undefined')
|
throw new Error('Invalid node: tag cannot be undefined')
|
||||||
}
|
}
|
||||||
|
|
||||||
const validAttributes = Object.keys(attrs || {}).filter(k => (
|
const validAttributes = Object.keys(attrs || {}).filter(k => typeof attrs[k] !== 'undefined' && attrs[k] !== null)
|
||||||
typeof attrs[k] !== 'undefined' && attrs[k] !== null
|
|
||||||
))
|
|
||||||
|
|
||||||
writeListStart(2 * validAttributes.length + 1 + (typeof content !== 'undefined' ? 1 : 0))
|
writeListStart(2 * validAttributes.length + 1 + (typeof content !== 'undefined' ? 1 : 0))
|
||||||
writeString(tag)
|
writeString(tag)
|
||||||
@@ -241,7 +236,8 @@ const encodeBinaryNodeInner = (
|
|||||||
writeByteLength(content.length)
|
writeByteLength(content.length)
|
||||||
pushBytes(content)
|
pushBytes(content)
|
||||||
} else if (Array.isArray(content)) {
|
} else if (Array.isArray(content)) {
|
||||||
const validContent = content.filter(item => item && (item.tag || Buffer.isBuffer(item) || item instanceof Uint8Array || typeof item === 'string')
|
const validContent = content.filter(
|
||||||
|
item => item && (item.tag || Buffer.isBuffer(item) || item instanceof Uint8Array || typeof item === 'string')
|
||||||
)
|
)
|
||||||
writeListStart(validContent.length)
|
writeListStart(validContent.length)
|
||||||
for (const item of validContent) {
|
for (const item of validContent) {
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ export const reduceBinaryNodeToDictionary = (node: BinaryNode, tag: string) => {
|
|||||||
(dict, { attrs }) => {
|
(dict, { attrs }) => {
|
||||||
dict[attrs.name || attrs.config_code] = attrs.value || attrs.config_value
|
dict[attrs.name || attrs.config_code] = attrs.value || attrs.config_value
|
||||||
return dict
|
return dict
|
||||||
}, { } as { [_: string]: string }
|
},
|
||||||
|
{} as { [_: string]: string }
|
||||||
)
|
)
|
||||||
return dict
|
return dict
|
||||||
}
|
}
|
||||||
@@ -105,7 +106,7 @@ export function binaryNodeToString(node: BinaryNode | BinaryNode['content'], i =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(node)) {
|
if (Array.isArray(node)) {
|
||||||
return node.map((x) => tabs(i + 1) + binaryNodeToString(x, i + 1)).join('\n')
|
return node.map(x => tabs(i + 1) + binaryNodeToString(x, i + 1)).join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
const children = binaryNodeToString(node.content, i + 1)
|
const children = binaryNodeToString(node.content, i + 1)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export type FullJid = JidWithDevice & {
|
|||||||
domainType?: number
|
domainType?: 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}`
|
||||||
}
|
}
|
||||||
@@ -43,27 +42,26 @@ export const jidDecode = (jid: string | undefined): FullJid | undefined => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** is the jid a user */
|
/** is the jid a user */
|
||||||
export const areJidsSameUser = (jid1: string | undefined, jid2: string | undefined) => (
|
export const areJidsSameUser = (jid1: string | undefined, jid2: string | undefined) =>
|
||||||
jidDecode(jid1)?.user === jidDecode(jid2)?.user
|
jidDecode(jid1)?.user === jidDecode(jid2)?.user
|
||||||
)
|
|
||||||
/** is the jid Meta IA */
|
/** is the jid Meta IA */
|
||||||
export const isJidMetaIa = (jid: string | undefined) => (jid?.endsWith('@bot'))
|
export const isJidMetaIa = (jid: string | undefined) => jid?.endsWith('@bot')
|
||||||
/** is the jid a user */
|
/** is the jid a user */
|
||||||
export const isJidUser = (jid: string | undefined) => (jid?.endsWith('@s.whatsapp.net'))
|
export const isJidUser = (jid: string | undefined) => jid?.endsWith('@s.whatsapp.net')
|
||||||
/** is the jid a group */
|
/** is the jid a group */
|
||||||
export const isLidUser = (jid: string | undefined) => (jid?.endsWith('@lid'))
|
export const isLidUser = (jid: string | undefined) => jid?.endsWith('@lid')
|
||||||
/** is the jid a broadcast */
|
/** is the jid a broadcast */
|
||||||
export const isJidBroadcast = (jid: string | undefined) => (jid?.endsWith('@broadcast'))
|
export const isJidBroadcast = (jid: string | undefined) => jid?.endsWith('@broadcast')
|
||||||
/** is the jid a group */
|
/** is the jid a group */
|
||||||
export const isJidGroup = (jid: string | undefined) => (jid?.endsWith('@g.us'))
|
export const isJidGroup = (jid: string | undefined) => jid?.endsWith('@g.us')
|
||||||
/** is the jid the status broadcast */
|
/** is the jid the status broadcast */
|
||||||
export const isJidStatusBroadcast = (jid: string) => jid === 'status@broadcast'
|
export const isJidStatusBroadcast = (jid: string) => jid === 'status@broadcast'
|
||||||
/** is the jid a newsletter */
|
/** is the jid a newsletter */
|
||||||
export const isJidNewsletter = (jid: string | undefined) => (jid?.endsWith('@newsletter'))
|
export const isJidNewsletter = (jid: string | undefined) => jid?.endsWith('@newsletter')
|
||||||
|
|
||||||
const botRegexp = /^1313555\d{4}$|^131655500\d{2}$/
|
const botRegexp = /^1313555\d{4}$|^131655500\d{2}$/
|
||||||
|
|
||||||
export const isJidBot = (jid: string | undefined) => (jid && botRegexp.test(jid.split('@')[0]) && jid.endsWith('@c.us'))
|
export const isJidBot = (jid: string | undefined) => jid && botRegexp.test(jid.split('@')[0]) && jid.endsWith('@c.us')
|
||||||
|
|
||||||
export const jidNormalizedUser = (jid: string | undefined) => {
|
export const jidNormalizedUser = (jid: string | undefined) => {
|
||||||
const result = jidDecode(jid)
|
const result = jidDecode(jid)
|
||||||
@@ -72,5 +70,5 @@ export const jidNormalizedUser = (jid: string | undefined) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { user, server } = result
|
const { user, server } = result
|
||||||
return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : server as JidServer)
|
return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : (server as JidServer))
|
||||||
}
|
}
|
||||||
|
|||||||
4427
src/WAM/constants.ts
4427
src/WAM/constants.ts
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,14 @@
|
|||||||
import { BinaryInfo } from './BinaryInfo'
|
import { BinaryInfo } from './BinaryInfo'
|
||||||
import { FLAG_BYTE, FLAG_EVENT, FLAG_EXTENDED, FLAG_FIELD, FLAG_GLOBAL, Value, WEB_EVENTS, WEB_GLOBALS } from './constants'
|
import {
|
||||||
|
FLAG_BYTE,
|
||||||
|
FLAG_EVENT,
|
||||||
|
FLAG_EXTENDED,
|
||||||
|
FLAG_FIELD,
|
||||||
|
FLAG_GLOBAL,
|
||||||
|
Value,
|
||||||
|
WEB_EVENTS,
|
||||||
|
WEB_GLOBALS
|
||||||
|
} from './constants'
|
||||||
|
|
||||||
const getHeaderBitLength = (key: number) => (key < 256 ? 2 : 3)
|
const getHeaderBitLength = (key: number) => (key < 256 ? 2 : 3)
|
||||||
|
|
||||||
@@ -10,9 +19,7 @@ export const encodeWAM = (binaryInfo: BinaryInfo) => {
|
|||||||
encodeEvents(binaryInfo)
|
encodeEvents(binaryInfo)
|
||||||
|
|
||||||
console.log(binaryInfo.buffer)
|
console.log(binaryInfo.buffer)
|
||||||
const totalSize = binaryInfo.buffer
|
const totalSize = binaryInfo.buffer.map(a => a.length).reduce((a, b) => a + b)
|
||||||
.map((a) => a.length)
|
|
||||||
.reduce((a, b) => a + b)
|
|
||||||
const buffer = Buffer.alloc(totalSize)
|
const buffer = Buffer.alloc(totalSize)
|
||||||
let offset = 0
|
let offset = 0
|
||||||
for (const buffer_ of binaryInfo.buffer) {
|
for (const buffer_ of binaryInfo.buffer) {
|
||||||
@@ -47,12 +54,9 @@ function encodeGlobalAttributes(binaryInfo: BinaryInfo, globals: {[key: string]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
function encodeEvents(binaryInfo: BinaryInfo) {
|
function encodeEvents(binaryInfo: BinaryInfo) {
|
||||||
for(const [
|
for (const [name, { props, globals }] of binaryInfo.events.map(a => Object.entries(a)[0])) {
|
||||||
name,
|
|
||||||
{ props, globals },
|
|
||||||
] of binaryInfo.events.map((a) => Object.entries(a)[0])) {
|
|
||||||
encodeGlobalAttributes(binaryInfo, globals)
|
encodeGlobalAttributes(binaryInfo, globals)
|
||||||
const event = WEB_EVENTS.find((a) => a.name === name)!
|
const event = WEB_EVENTS.find(a => a.name === name)!
|
||||||
|
|
||||||
const props_ = Object.entries(props)
|
const props_ = Object.entries(props)
|
||||||
|
|
||||||
@@ -67,8 +71,8 @@ function encodeEvents(binaryInfo: BinaryInfo) {
|
|||||||
|
|
||||||
for (let i = 0; i < props_.length; i++) {
|
for (let i = 0; i < props_.length; i++) {
|
||||||
const [key, _value] = props_[i]
|
const [key, _value] = props_[i]
|
||||||
const id = (event.props)[key][0]
|
const id = event.props[key][0]
|
||||||
extended = i < (props_.length - 1)
|
extended = i < props_.length - 1
|
||||||
let value = _value
|
let value = _value
|
||||||
if (typeof value === 'boolean') {
|
if (typeof value === 'boolean') {
|
||||||
value = value ? 1 : 0
|
value = value ? 1 : 0
|
||||||
@@ -80,7 +84,6 @@ function encodeEvents(binaryInfo: BinaryInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function serializeData(key: number, value: Value, flag: number): Buffer {
|
function serializeData(key: number, value: Value, flag: number): Buffer {
|
||||||
const bufferLength = getHeaderBitLength(key)
|
const bufferLength = getHeaderBitLength(key)
|
||||||
let buffer: Buffer
|
let buffer: Buffer
|
||||||
@@ -150,12 +153,7 @@ function serializeData(key: number, value: Value, flag: number): Buffer {
|
|||||||
throw 'missing'
|
throw 'missing'
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeHeader(
|
function serializeHeader(buffer: Buffer, offset: number, key: number, flag: number) {
|
||||||
buffer: Buffer,
|
|
||||||
offset: number,
|
|
||||||
key: number,
|
|
||||||
flag: number
|
|
||||||
) {
|
|
||||||
if (key < 256) {
|
if (key < 256) {
|
||||||
buffer.writeUInt8(flag, offset)
|
buffer.writeUInt8(flag, offset)
|
||||||
offset += 1
|
offset += 1
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class USyncContactProtocol implements USyncQueryProtocol {
|
|||||||
getQueryElement(): BinaryNode {
|
getQueryElement(): BinaryNode {
|
||||||
return {
|
return {
|
||||||
tag: 'contact',
|
tag: 'contact',
|
||||||
attrs: {},
|
attrs: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ export class USyncContactProtocol implements USyncQueryProtocol {
|
|||||||
return {
|
return {
|
||||||
tag: 'contact',
|
tag: 'contact',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: user.phone,
|
content: user.phone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export class USyncDeviceProtocol implements USyncQueryProtocol {
|
|||||||
return {
|
return {
|
||||||
tag: 'devices',
|
tag: 'devices',
|
||||||
attrs: {
|
attrs: {
|
||||||
version: '2',
|
version: '2'
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class USyncDisappearingModeProtocol implements USyncQueryProtocol {
|
|||||||
getQueryElement(): BinaryNode {
|
getQueryElement(): BinaryNode {
|
||||||
return {
|
return {
|
||||||
tag: 'disappearing_mode',
|
tag: 'disappearing_mode',
|
||||||
attrs: {},
|
attrs: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export class USyncDisappearingModeProtocol implements USyncQueryProtocol {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
duration,
|
duration,
|
||||||
setAt,
|
setAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class USyncStatusProtocol implements USyncQueryProtocol {
|
|||||||
getQueryElement(): BinaryNode {
|
getQueryElement(): BinaryNode {
|
||||||
return {
|
return {
|
||||||
tag: 'status',
|
tag: 'status',
|
||||||
attrs: {},
|
attrs: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export class USyncStatusProtocol implements USyncQueryProtocol {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
setAt,
|
setAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class USyncBotProfileProtocol implements USyncQueryProtocol {
|
|||||||
return {
|
return {
|
||||||
tag: 'bot',
|
tag: 'bot',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: [{ tag: 'profile', attrs: { 'persona_id': user.personaId } }]
|
content: [{ tag: 'profile', attrs: { persona_id: user.personaId } }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,6 @@ export class USyncBotProfileProtocol implements USyncQueryProtocol {
|
|||||||
prompts.push(`${getBinaryNodeChildString(prompt, 'emoji')!} ${getBinaryNodeChildString(prompt, 'text')!}`)
|
prompts.push(`${getBinaryNodeChildString(prompt, 'emoji')!} ${getBinaryNodeChildString(prompt, 'text')!}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isDefault: !!getBinaryNodeChild(profile, 'default'),
|
isDefault: !!getBinaryNodeChild(profile, 'default'),
|
||||||
jid: node.attrs.jid,
|
jid: node.attrs.jid,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export class USyncLIDProtocol implements USyncQueryProtocol {
|
|||||||
getQueryElement(): BinaryNode {
|
getQueryElement(): BinaryNode {
|
||||||
return {
|
return {
|
||||||
tag: 'lid',
|
tag: 'lid',
|
||||||
attrs: {},
|
attrs: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,15 @@ import { USyncQueryProtocol } from '../Types/USync'
|
|||||||
import { BinaryNode, getBinaryNodeChild } from '../WABinary'
|
import { BinaryNode, getBinaryNodeChild } from '../WABinary'
|
||||||
import { USyncBotProfileProtocol } from './Protocols/UsyncBotProfileProtocol'
|
import { USyncBotProfileProtocol } from './Protocols/UsyncBotProfileProtocol'
|
||||||
import { USyncLIDProtocol } from './Protocols/UsyncLIDProtocol'
|
import { USyncLIDProtocol } from './Protocols/UsyncLIDProtocol'
|
||||||
import { USyncContactProtocol, USyncDeviceProtocol, USyncDisappearingModeProtocol, USyncStatusProtocol } from './Protocols'
|
import {
|
||||||
|
USyncContactProtocol,
|
||||||
|
USyncDeviceProtocol,
|
||||||
|
USyncDisappearingModeProtocol,
|
||||||
|
USyncStatusProtocol
|
||||||
|
} from './Protocols'
|
||||||
import { USyncUser } from './USyncUser'
|
import { USyncUser } from './USyncUser'
|
||||||
|
|
||||||
export type USyncQueryResultList = { [protocol: string]: unknown, id: string }
|
export type USyncQueryResultList = { [protocol: string]: unknown; id: string }
|
||||||
|
|
||||||
export type USyncQueryResult = {
|
export type USyncQueryResult = {
|
||||||
list: USyncQueryResultList[]
|
list: USyncQueryResultList[]
|
||||||
@@ -45,14 +50,16 @@ export class USyncQuery {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const protocolMap = Object.fromEntries(this.protocols.map((protocol) => {
|
const protocolMap = Object.fromEntries(
|
||||||
|
this.protocols.map(protocol => {
|
||||||
return [protocol.name, protocol.parser]
|
return [protocol.name, protocol.parser]
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const queryResult: USyncQueryResult = {
|
const queryResult: USyncQueryResult = {
|
||||||
// TODO: implement errors etc.
|
// TODO: implement errors etc.
|
||||||
list: [],
|
list: [],
|
||||||
sideList: [],
|
sideList: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const usyncNode = getBinaryNodeChild(result, 'usync')
|
const usyncNode = getBinaryNodeChild(result, 'usync')
|
||||||
@@ -63,9 +70,12 @@ export class USyncQuery {
|
|||||||
|
|
||||||
const listNode = getBinaryNodeChild(usyncNode, 'list')
|
const listNode = getBinaryNodeChild(usyncNode, 'list')
|
||||||
if (Array.isArray(listNode?.content) && typeof listNode !== 'undefined') {
|
if (Array.isArray(listNode?.content) && typeof listNode !== 'undefined') {
|
||||||
queryResult.list = listNode.content.map((node) => {
|
queryResult.list = listNode.content.map(node => {
|
||||||
const id = node?.attrs.jid
|
const id = node?.attrs.jid
|
||||||
const data = Array.isArray(node?.content) ? Object.fromEntries(node.content.map((content) => {
|
const data = Array.isArray(node?.content)
|
||||||
|
? Object.fromEntries(
|
||||||
|
node.content
|
||||||
|
.map(content => {
|
||||||
const protocol = content.tag
|
const protocol = content.tag
|
||||||
const parser = protocolMap[protocol]
|
const parser = protocolMap[protocol]
|
||||||
if (parser) {
|
if (parser) {
|
||||||
@@ -73,7 +83,10 @@ export class USyncQuery {
|
|||||||
} else {
|
} else {
|
||||||
return [protocol, null]
|
return [protocol, null]
|
||||||
}
|
}
|
||||||
}).filter(([, b]) => b !== null) as [string, unknown][]) : {}
|
})
|
||||||
|
.filter(([, b]) => b !== null) as [string, unknown][]
|
||||||
|
)
|
||||||
|
: {}
|
||||||
return { ...data, id }
|
return { ...data, id }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user