Path: blob/master/node_modules/@adiwajshing/baileys/lib/Socket/chats.js
1129 views
"use strict";1var __importDefault = (this && this.__importDefault) || function (mod) {2return (mod && mod.__esModule) ? mod : { "default": mod };3};4Object.defineProperty(exports, "__esModule", { value: true });5exports.makeChatsSocket = void 0;6const boom_1 = require("@hapi/boom");7const WAProto_1 = require("../../WAProto");8const Types_1 = require("../Types");9const Utils_1 = require("../Utils");10const make_mutex_1 = require("../Utils/make-mutex");11const process_message_1 = __importDefault(require("../Utils/process-message"));12const WABinary_1 = require("../WABinary");13const socket_1 = require("./socket");14const MAX_SYNC_ATTEMPTS = 5;15const APP_STATE_SYNC_TIMEOUT_MS = 10000;16const makeChatsSocket = (config) => {17const { logger, markOnlineOnConnect, downloadHistory, fireInitQueries } = config;18const sock = (0, socket_1.makeSocket)(config);19const { ev, ws, authState, generateMessageTag, sendNode, query, onUnexpectedError, } = sock;20let privacySettings;21/** this mutex ensures that the notifications (receipts, messages etc.) are processed in order */22const processingMutex = (0, make_mutex_1.makeMutex)();23/** cache to ensure new history sync events do not have duplicate items */24const historyCache = new Set();25let recvChats = {};26const appStateSyncTimeout = (0, Utils_1.debouncedTimeout)(APP_STATE_SYNC_TIMEOUT_MS, async () => {27if (ws.readyState === ws.OPEN) {28logger.info({ recvChats: Object.keys(recvChats).length }, 'doing initial app state sync');29await resyncMainAppState(recvChats);30}31else {32logger.warn('connection closed before app state sync');33}34historyCache.clear();35recvChats = {};36});37/** helper function to fetch the given app state sync key */38const getAppStateSyncKey = async (keyId) => {39const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId]);40return key;41};42const fetchPrivacySettings = async (force = false) => {43if (!privacySettings || force) {44const { content } = await query({45tag: 'iq',46attrs: {47xmlns: 'privacy',48to: WABinary_1.S_WHATSAPP_NET,49type: 'get'50},51content: [52{ tag: 'privacy', attrs: {} }53]54});55privacySettings = (0, WABinary_1.reduceBinaryNodeToDictionary)(content === null || content === void 0 ? void 0 : content[0], 'category');56}57return privacySettings;58};59/** helper function to run a generic IQ query */60const interactiveQuery = async (userNodes, queryNode) => {61const result = await query({62tag: 'iq',63attrs: {64to: WABinary_1.S_WHATSAPP_NET,65type: 'get',66xmlns: 'usync',67},68content: [69{70tag: 'usync',71attrs: {72sid: generateMessageTag(),73mode: 'query',74last: 'true',75index: '0',76context: 'interactive',77},78content: [79{80tag: 'query',81attrs: {},82content: [queryNode]83},84{85tag: 'list',86attrs: {},87content: userNodes88}89]90}91],92});93const usyncNode = (0, WABinary_1.getBinaryNodeChild)(result, 'usync');94const listNode = (0, WABinary_1.getBinaryNodeChild)(usyncNode, 'list');95const users = (0, WABinary_1.getBinaryNodeChildren)(listNode, 'user');96return users;97};98const onWhatsApp = async (...jids) => {99const results = await interactiveQuery([100{101tag: 'user',102attrs: {},103content: jids.map(jid => ({104tag: 'contact',105attrs: {},106content: `+${jid}`107}))108}109], { tag: 'contact', attrs: {} });110return results.map(user => {111const contact = (0, WABinary_1.getBinaryNodeChild)(user, 'contact');112return { exists: (contact === null || contact === void 0 ? void 0 : contact.attrs.type) === 'in', jid: user.attrs.jid };113}).filter(item => item.exists);114};115const fetchStatus = async (jid) => {116const [result] = await interactiveQuery([{ tag: 'user', attrs: { jid } }], { tag: 'status', attrs: {} });117if (result) {118const status = (0, WABinary_1.getBinaryNodeChild)(result, 'status');119return {120status: status === null || status === void 0 ? void 0 : status.content.toString(),121setAt: new Date(+((status === null || status === void 0 ? void 0 : status.attrs.t) || 0) * 1000)122};123}124};125/** update the profile picture for yourself or a group */126const updateProfilePicture = async (jid, content) => {127const { img } = await (0, Utils_1.generateProfilePicture)(content);128await query({129tag: 'iq',130attrs: {131to: (0, WABinary_1.jidNormalizedUser)(jid),132type: 'set',133xmlns: 'w:profile:picture'134},135content: [136{137tag: 'picture',138attrs: { type: 'image' },139content: img140}141]142});143};144/** update the profile status for yourself */145const updateProfileStatus = async (status) => {146await query({147tag: 'iq',148attrs: {149to: WABinary_1.S_WHATSAPP_NET,150type: 'set',151xmlns: 'status'152},153content: [154{155tag: 'status',156attrs: {},157content: Buffer.from(status, 'utf-8')158}159]160});161};162const updateProfileName = async (name) => {163await chatModify({ pushNameSetting: name }, '');164};165const fetchBlocklist = async () => {166const result = await query({167tag: 'iq',168attrs: {169xmlns: 'blocklist',170to: WABinary_1.S_WHATSAPP_NET,171type: 'get'172}173});174const listNode = (0, WABinary_1.getBinaryNodeChild)(result, 'list');175return (0, WABinary_1.getBinaryNodeChildren)(listNode, 'item')176.map(n => n.attrs.jid);177};178const updateBlockStatus = async (jid, action) => {179await query({180tag: 'iq',181attrs: {182xmlns: 'blocklist',183to: WABinary_1.S_WHATSAPP_NET,184type: 'set'185},186content: [187{188tag: 'item',189attrs: {190action,191jid192}193}194]195});196};197const getBusinessProfile = async (jid) => {198var _a, _b, _c, _d, _e, _f, _g;199const results = await query({200tag: 'iq',201attrs: {202to: 's.whatsapp.net',203xmlns: 'w:biz',204type: 'get'205},206content: [{207tag: 'business_profile',208attrs: { v: '244' },209content: [{210tag: 'profile',211attrs: { jid }212}]213}]214});215const profileNode = (0, WABinary_1.getBinaryNodeChild)(results, 'business_profile');216const profiles = (0, WABinary_1.getBinaryNodeChild)(profileNode, 'profile');217if (profiles) {218const address = (0, WABinary_1.getBinaryNodeChild)(profiles, 'address');219const description = (0, WABinary_1.getBinaryNodeChild)(profiles, 'description');220const website = (0, WABinary_1.getBinaryNodeChild)(profiles, 'website');221const email = (0, WABinary_1.getBinaryNodeChild)(profiles, 'email');222const category = (0, WABinary_1.getBinaryNodeChild)((0, WABinary_1.getBinaryNodeChild)(profiles, 'categories'), 'category');223const business_hours = (0, WABinary_1.getBinaryNodeChild)(profiles, 'business_hours');224const business_hours_config = business_hours && (0, WABinary_1.getBinaryNodeChildren)(business_hours, 'business_hours_config');225const websiteStr = (_a = website === null || website === void 0 ? void 0 : website.content) === null || _a === void 0 ? void 0 : _a.toString();226return {227wid: (_b = profiles.attrs) === null || _b === void 0 ? void 0 : _b.jid,228address: (_c = address === null || address === void 0 ? void 0 : address.content) === null || _c === void 0 ? void 0 : _c.toString(),229description: ((_d = description === null || description === void 0 ? void 0 : description.content) === null || _d === void 0 ? void 0 : _d.toString()) || '',230website: websiteStr ? [websiteStr] : [],231email: (_e = email === null || email === void 0 ? void 0 : email.content) === null || _e === void 0 ? void 0 : _e.toString(),232category: (_f = category === null || category === void 0 ? void 0 : category.content) === null || _f === void 0 ? void 0 : _f.toString(),233business_hours: {234timezone: (_g = business_hours === null || business_hours === void 0 ? void 0 : business_hours.attrs) === null || _g === void 0 ? void 0 : _g.timezone,235business_config: business_hours_config === null || business_hours_config === void 0 ? void 0 : business_hours_config.map(({ attrs }) => attrs)236}237};238}239};240const updateAccountSyncTimestamp = async (fromTimestamp) => {241logger.info({ fromTimestamp }, 'requesting account sync');242await sendNode({243tag: 'iq',244attrs: {245to: WABinary_1.S_WHATSAPP_NET,246type: 'set',247xmlns: 'urn:xmpp:whatsapp:dirty',248id: generateMessageTag(),249},250content: [251{252tag: 'clean',253attrs: {254type: 'account_sync',255timestamp: fromTimestamp.toString(),256}257}258]259});260};261const newAppStateChunkHandler = (recvChats) => {262return {263onMutation(mutation) {264(0, Utils_1.processSyncAction)(mutation, ev, authState.creds.me, recvChats ? { recvChats, accountSettings: authState.creds.accountSettings } : undefined, logger);265}266};267};268const resyncAppState = ev.createBufferedFunction(async (collections, recvChats) => {269const { onMutation } = newAppStateChunkHandler(recvChats);270// we use this to determine which events to fire271// otherwise when we resync from scratch -- all notifications will fire272const initialVersionMap = {};273await authState.keys.transaction(async () => {274var _a;275const collectionsToHandle = new Set(collections);276// in case something goes wrong -- ensure we don't enter a loop that cannot be exited from277const attemptsMap = {};278// keep executing till all collections are done279// sometimes a single patch request will not return all the patches (God knows why)280// so we fetch till they're all done (this is determined by the "has_more_patches" flag)281while (collectionsToHandle.size) {282const states = {};283const nodes = [];284for (const name of collectionsToHandle) {285const result = await authState.keys.get('app-state-sync-version', [name]);286let state = result[name];287if (state) {288if (typeof initialVersionMap[name] === 'undefined') {289initialVersionMap[name] = state.version;290}291}292else {293state = (0, Utils_1.newLTHashState)();294}295states[name] = state;296logger.info(`resyncing ${name} from v${state.version}`);297nodes.push({298tag: 'collection',299attrs: {300name,301version: state.version.toString(),302// return snapshot if being synced from scratch303return_snapshot: (!state.version).toString()304}305});306}307const result = await query({308tag: 'iq',309attrs: {310to: WABinary_1.S_WHATSAPP_NET,311xmlns: 'w:sync:app:state',312type: 'set'313},314content: [315{316tag: 'sync',317attrs: {},318content: nodes319}320]321});322const decoded = await (0, Utils_1.extractSyncdPatches)(result); // extract from binary node323for (const key in decoded) {324const name = key;325const { patches, hasMorePatches, snapshot } = decoded[name];326try {327if (snapshot) {328const { state: newState } = await (0, Utils_1.decodeSyncdSnapshot)(name, snapshot, getAppStateSyncKey, initialVersionMap[name], onMutation);329states[name] = newState;330logger.info(`restored state of ${name} from snapshot to v${newState.version} with mutations`);331await authState.keys.set({ 'app-state-sync-version': { [name]: newState } });332}333// only process if there are syncd patches334if (patches.length) {335const { newMutations, state: newState } = await (0, Utils_1.decodePatches)(name, patches, states[name], getAppStateSyncKey, onMutation, initialVersionMap[name]);336await authState.keys.set({ 'app-state-sync-version': { [name]: newState } });337logger.info(`synced ${name} to v${newState.version}`);338if (newMutations.length) {339logger.trace({ newMutations, name }, 'recv new mutations');340}341}342if (hasMorePatches) {343logger.info(`${name} has more patches...`);344}345else { // collection is done with sync346collectionsToHandle.delete(name);347}348}349catch (error) {350// if retry attempts overshoot351// or key not found352const isIrrecoverableError = attemptsMap[name] >= MAX_SYNC_ATTEMPTS || ((_a = error.output) === null || _a === void 0 ? void 0 : _a.statusCode) === 404;353logger.info({ name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`);354await authState.keys.set({ 'app-state-sync-version': { [name]: null } });355// increment number of retries356attemptsMap[name] = (attemptsMap[name] || 0) + 1;357if (isIrrecoverableError) {358// stop retrying359collectionsToHandle.delete(name);360}361}362}363}364});365});366/**367* fetch the profile picture of a user/group368* type = "preview" for a low res picture369* type = "image for the high res picture"370*/371const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {372var _a;373jid = (0, WABinary_1.jidNormalizedUser)(jid);374const result = await query({375tag: 'iq',376attrs: {377to: jid,378type: 'get',379xmlns: 'w:profile:picture'380},381content: [382{ tag: 'picture', attrs: { type, query: 'url' } }383]384}, timeoutMs);385const child = (0, WABinary_1.getBinaryNodeChild)(result, 'picture');386return (_a = child === null || child === void 0 ? void 0 : child.attrs) === null || _a === void 0 ? void 0 : _a.url;387};388const sendPresenceUpdate = async (type, toJid) => {389const me = authState.creds.me;390if (type === 'available' || type === 'unavailable') {391if (!me.name) {392logger.warn('no name present, ignoring presence update request...');393return;394}395ev.emit('connection.update', { isOnline: type === 'available' });396await sendNode({397tag: 'presence',398attrs: {399name: me.name,400type401}402});403}404else {405await sendNode({406tag: 'chatstate',407attrs: {408from: me.id,409to: toJid,410},411content: [412{413tag: type === 'recording' ? 'composing' : type,414attrs: type === 'recording' ? { media: 'audio' } : {}415}416]417});418}419};420const presenceSubscribe = (toJid) => (sendNode({421tag: 'presence',422attrs: {423to: toJid,424id: generateMessageTag(),425type: 'subscribe'426}427}));428const handlePresenceUpdate = ({ tag, attrs, content }) => {429var _a;430let presence;431const jid = attrs.from;432const participant = attrs.participant || attrs.from;433if (tag === 'presence') {434presence = {435lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',436lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined437};438}439else if (Array.isArray(content)) {440const [firstChild] = content;441let type = firstChild.tag;442if (type === 'paused') {443type = 'available';444}445if (((_a = firstChild.attrs) === null || _a === void 0 ? void 0 : _a.media) === 'audio') {446type = 'recording';447}448presence = { lastKnownPresence: type };449}450else {451logger.error({ tag, attrs, content }, 'recv invalid presence node');452}453if (presence) {454ev.emit('presence.update', { id: jid, presences: { [participant]: presence } });455}456};457const resyncMainAppState = async (ctx) => {458logger.debug('resyncing main app state');459await (processingMutex.mutex(() => resyncAppState(Types_1.ALL_WA_PATCH_NAMES, ctx))460.catch(err => (onUnexpectedError(err, 'main app sync'))));461};462const appPatch = async (patchCreate) => {463const name = patchCreate.type;464const myAppStateKeyId = authState.creds.myAppStateKeyId;465if (!myAppStateKeyId) {466throw new boom_1.Boom('App state key not present!', { statusCode: 400 });467}468let initial;469let encodeResult;470await processingMutex.mutex(async () => {471await authState.keys.transaction(async () => {472logger.debug({ patch: patchCreate }, 'applying app patch');473await resyncAppState([name], undefined);474const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name]);475initial = currentSyncVersion || (0, Utils_1.newLTHashState)();476encodeResult = await (0, Utils_1.encodeSyncdPatch)(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey);477const { patch, state } = encodeResult;478const node = {479tag: 'iq',480attrs: {481to: WABinary_1.S_WHATSAPP_NET,482type: 'set',483xmlns: 'w:sync:app:state'484},485content: [486{487tag: 'sync',488attrs: {},489content: [490{491tag: 'collection',492attrs: {493name,494version: (state.version - 1).toString(),495return_snapshot: 'false'496},497content: [498{499tag: 'patch',500attrs: {},501content: WAProto_1.proto.SyncdPatch.encode(patch).finish()502}503]504}505]506}507]508};509await query(node);510await authState.keys.set({ 'app-state-sync-version': { [name]: state } });511});512});513if (config.emitOwnEvents) {514const { onMutation } = newAppStateChunkHandler(undefined);515await (0, Utils_1.decodePatches)(name, [{ ...encodeResult.patch, version: { version: encodeResult.state.version }, }], initial, getAppStateSyncKey, onMutation, undefined, logger);516}517};518/** sending abt props may fix QR scan fail if server expects */519const fetchAbt = async () => {520const abtNode = await query({521tag: 'iq',522attrs: {523to: WABinary_1.S_WHATSAPP_NET,524xmlns: 'abt',525type: 'get',526},527content: [528{ tag: 'props', attrs: { protocol: '1' } }529]530});531const propsNode = (0, WABinary_1.getBinaryNodeChild)(abtNode, 'props');532let props = {};533if (propsNode) {534props = (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop');535}536logger.debug('fetched abt');537return props;538};539/** sending non-abt props may fix QR scan fail if server expects */540const fetchProps = async () => {541const resultNode = await query({542tag: 'iq',543attrs: {544to: WABinary_1.S_WHATSAPP_NET,545xmlns: 'w',546type: 'get',547},548content: [549{ tag: 'props', attrs: {} }550]551});552const propsNode = (0, WABinary_1.getBinaryNodeChild)(resultNode, 'props');553let props = {};554if (propsNode) {555props = (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop');556}557logger.debug('fetched props');558return props;559};560/**561* modify a chat -- mark unread, read etc.562* lastMessages must be sorted in reverse chronologically563* requires the last messages till the last message received; required for archive & unread564*/565const chatModify = (mod, jid) => {566const patch = (0, Utils_1.chatModificationToAppPatch)(mod, jid);567return appPatch(patch);568};569/**570* queries need to be fired on connection open571* help ensure parity with WA Web572* */573const executeInitQueries = async () => {574await Promise.all([575fetchAbt(),576fetchProps(),577fetchBlocklist(),578fetchPrivacySettings(),579]);580};581const upsertMessage = ev.createBufferedFunction(async (msg, type) => {582var _a;583ev.emit('messages.upsert', { messages: [msg], type });584if (!!msg.pushName) {585let jid = msg.key.fromMe ? authState.creds.me.id : (msg.key.participant || msg.key.remoteJid);586jid = (0, WABinary_1.jidNormalizedUser)(jid);587if (!msg.key.fromMe) {588ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }]);589}590// update our pushname too591if (msg.key.fromMe && ((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.name) !== msg.pushName) {592ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } });593}594}595// process message and emit events596await (0, process_message_1.default)(msg, {597downloadHistory,598ev,599historyCache,600recvChats,601creds: authState.creds,602keyStore: authState.keys,603logger,604});605const isAnyHistoryMsg = (0, Utils_1.isHistoryMsg)(msg.message);606if (isAnyHistoryMsg) {607// we only want to sync app state once we've all the history608// restart the app state sync timeout609logger.debug('restarting app sync timeout');610appStateSyncTimeout.start();611}612});613ws.on('CB:presence', handlePresenceUpdate);614ws.on('CB:chatstate', handlePresenceUpdate);615ws.on('CB:ib,,dirty', async (node) => {616const { attrs } = (0, WABinary_1.getBinaryNodeChild)(node, 'dirty');617const type = attrs.type;618switch (type) {619case 'account_sync':620if (attrs.timestamp) {621let { lastAccountSyncTimestamp } = authState.creds;622if (lastAccountSyncTimestamp) {623await updateAccountSyncTimestamp(lastAccountSyncTimestamp);624}625lastAccountSyncTimestamp = +attrs.timestamp;626ev.emit('creds.update', { lastAccountSyncTimestamp });627}628break;629default:630logger.info({ node }, 'received unknown sync');631break;632}633});634ev.on('connection.update', ({ connection }) => {635if (connection === 'open') {636if (fireInitQueries) {637executeInitQueries()638.catch(error => onUnexpectedError(error, 'init queries'));639}640sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable')641.catch(error => onUnexpectedError(error, 'presence update requests'));642}643});644return {645...sock,646processingMutex,647fetchPrivacySettings,648upsertMessage,649appPatch,650sendPresenceUpdate,651presenceSubscribe,652profilePictureUrl,653onWhatsApp,654fetchBlocklist,655fetchStatus,656updateProfilePicture,657updateProfileStatus,658updateProfileName,659updateBlockStatus,660getBusinessProfile,661resyncAppState,662chatModify,663resyncMainAppState,664};665};666exports.makeChatsSocket = makeChatsSocket;667668669