Path: blob/master/node_modules/@adiwajshing/baileys/lib/Socket/chats.js
2593 views
"use strict";1Object.defineProperty(exports, "__esModule", { value: true });2exports.makeChatsSocket = void 0;3const boom_1 = require("@hapi/boom");4const WAProto_1 = require("../../WAProto");5const Utils_1 = require("../Utils");6const make_mutex_1 = require("../Utils/make-mutex");7const WABinary_1 = require("../WABinary");8const messages_send_1 = require("./messages-send");9const MAX_SYNC_ATTEMPTS = 5;10const makeChatsSocket = (config) => {11const { logger } = config;12const sock = messages_send_1.makeMessagesSocket(config);13const { ev, ws, authState, generateMessageTag, sendNode, query, fetchPrivacySettings, } = sock;14const mutationMutex = make_mutex_1.makeMutex();15const getAppStateSyncKey = async (keyId) => {16const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId]);17return key;18};19const interactiveQuery = async (userNodes, queryNode) => {20const result = await query({21tag: 'iq',22attrs: {23to: WABinary_1.S_WHATSAPP_NET,24type: 'get',25xmlns: 'usync',26},27content: [28{29tag: 'usync',30attrs: {31sid: generateMessageTag(),32mode: 'query',33last: 'true',34index: '0',35context: 'interactive',36},37content: [38{39tag: 'query',40attrs: {},41content: [queryNode]42},43{44tag: 'list',45attrs: {},46content: userNodes47}48]49}50],51});52const usyncNode = WABinary_1.getBinaryNodeChild(result, 'usync');53const listNode = WABinary_1.getBinaryNodeChild(usyncNode, 'list');54const users = WABinary_1.getBinaryNodeChildren(listNode, 'user');55return users;56};57const onWhatsApp = async (...jids) => {58const results = await interactiveQuery([59{60tag: 'user',61attrs: {},62content: jids.map(jid => ({63tag: 'contact',64attrs: {},65content: `+${jid}`66}))67}68], { tag: 'contact', attrs: {} });69return results.map(user => {70const contact = WABinary_1.getBinaryNodeChild(user, 'contact');71return { exists: contact.attrs.type === 'in', jid: user.attrs.jid };72}).filter(item => item.exists);73};74const fetchStatus = async (jid) => {75const [result] = await interactiveQuery([{ tag: 'user', attrs: { jid } }], { tag: 'status', attrs: {} });76if (result) {77const status = WABinary_1.getBinaryNodeChild(result, 'status');78return {79status: status.content.toString(),80setAt: new Date(+status.attrs.t * 1000)81};82}83};84const updateProfilePicture = async (jid, content) => {85const { img } = await Utils_1.generateProfilePicture(content);86await query({87tag: 'iq',88attrs: {89to: WABinary_1.jidNormalizedUser(jid),90type: 'set',91xmlns: 'w:profile:picture'92},93content: [94{95tag: 'picture',96attrs: { type: 'image' },97content: img98}99]100});101};102const fetchBlocklist = async () => {103var _a, _b;104const result = await query({105tag: 'iq',106attrs: {107xmlns: 'blocklist',108to: WABinary_1.S_WHATSAPP_NET,109type: 'get'110}111});112const child = (_a = result.content) === null || _a === void 0 ? void 0 : _a[0];113return (_b = child.content) === null || _b === void 0 ? void 0 : _b.map(i => i.attrs.jid);114};115const updateBlockStatus = async (jid, action) => {116await query({117tag: 'iq',118attrs: {119xmlns: 'blocklist',120to: WABinary_1.S_WHATSAPP_NET,121type: 'set'122},123content: [124{125tag: 'item',126attrs: {127action,128jid129}130}131]132});133};134const getBusinessProfile = async (jid) => {135var _a, _b;136const results = await query({137tag: 'iq',138attrs: {139to: 's.whatsapp.net',140xmlns: 'w:biz',141type: 'get'142},143content: [{144tag: 'business_profile',145attrs: { v: '244' },146content: [{147tag: 'profile',148attrs: { jid }149}]150}]151});152const profiles = WABinary_1.getBinaryNodeChild(WABinary_1.getBinaryNodeChild(results, 'business_profile'), 'profile');153if (!profiles) {154// if not bussines155if (logger.level === 'trace') {156logger.trace({ jid }, 'Not bussines');157}158return;159}160const address = WABinary_1.getBinaryNodeChild(profiles, 'address');161const description = WABinary_1.getBinaryNodeChild(profiles, 'description');162const website = WABinary_1.getBinaryNodeChild(profiles, 'website');163const email = WABinary_1.getBinaryNodeChild(profiles, 'email');164const category = WABinary_1.getBinaryNodeChild(WABinary_1.getBinaryNodeChild(profiles, 'categories'), 'category');165const business_hours = WABinary_1.getBinaryNodeChild(profiles, 'business_hours');166const business_hours_config = business_hours && WABinary_1.getBinaryNodeChildren(business_hours, 'business_hours_config');167return {168wid: (_a = profiles.attrs) === null || _a === void 0 ? void 0 : _a.jid,169address: address === null || address === void 0 ? void 0 : address.content.toString(),170description: description === null || description === void 0 ? void 0 : description.content.toString(),171website: [website === null || website === void 0 ? void 0 : website.content.toString()],172email: email === null || email === void 0 ? void 0 : email.content.toString(),173category: category === null || category === void 0 ? void 0 : category.content.toString(),174business_hours: {175timezone: (_b = business_hours === null || business_hours === void 0 ? void 0 : business_hours.attrs) === null || _b === void 0 ? void 0 : _b.timezone,176business_config: business_hours_config === null || business_hours_config === void 0 ? void 0 : business_hours_config.map(({ attrs }) => attrs)177}178};179};180const updateAccountSyncTimestamp = async (fromTimestamp) => {181logger.info({ fromTimestamp }, 'requesting account sync');182await sendNode({183tag: 'iq',184attrs: {185to: WABinary_1.S_WHATSAPP_NET,186type: 'set',187xmlns: 'urn:xmpp:whatsapp:dirty',188id: generateMessageTag(),189},190content: [191{192tag: 'clean',193attrs: {194type: 'account_sync',195timestamp: fromTimestamp.toString(),196}197}198]199});200};201const resyncAppState = async (collections) => {202const appStateChunk = { totalMutations: [], collectionsToHandle: [] };203// we use this to determine which events to fire204// otherwise when we resync from scratch -- all notifications will fire205const initialVersionMap = {};206await authState.keys.transaction(async () => {207var _a;208const collectionsToHandle = new Set(collections);209// in case something goes wrong -- ensure we don't enter a loop that cannot be exited from210const attemptsMap = {};211// keep executing till all collections are done212// sometimes a single patch request will not return all the patches (God knows why)213// so we fetch till they're all done (this is determined by the "has_more_patches" flag)214while (collectionsToHandle.size) {215const states = {};216const nodes = [];217for (const name of collectionsToHandle) {218const result = await authState.keys.get('app-state-sync-version', [name]);219let state = result[name];220if (state) {221if (typeof initialVersionMap[name] === 'undefined') {222initialVersionMap[name] = state.version;223}224}225else {226state = Utils_1.newLTHashState();227}228states[name] = state;229logger.info(`resyncing ${name} from v${state.version}`);230nodes.push({231tag: 'collection',232attrs: {233name,234version: state.version.toString(),235// return snapshot if being synced from scratch236return_snapshot: (!state.version).toString()237}238});239}240const result = await query({241tag: 'iq',242attrs: {243to: WABinary_1.S_WHATSAPP_NET,244xmlns: 'w:sync:app:state',245type: 'set'246},247content: [248{249tag: 'sync',250attrs: {},251content: nodes252}253]254});255const decoded = await Utils_1.extractSyncdPatches(result); // extract from binary node256for (const key in decoded) {257const name = key;258const { patches, hasMorePatches, snapshot } = decoded[name];259try {260if (snapshot) {261const { state: newState, mutations } = await Utils_1.decodeSyncdSnapshot(name, snapshot, getAppStateSyncKey, initialVersionMap[name]);262states[name] = newState;263logger.info(`restored state of ${name} from snapshot to v${newState.version} with ${mutations.length} mutations`);264await authState.keys.set({ 'app-state-sync-version': { [name]: newState } });265appStateChunk.totalMutations.push(...mutations);266}267// only process if there are syncd patches268if (patches.length) {269const { newMutations, state: newState } = await Utils_1.decodePatches(name, patches, states[name], getAppStateSyncKey, initialVersionMap[name]);270await authState.keys.set({ 'app-state-sync-version': { [name]: newState } });271logger.info(`synced ${name} to v${newState.version}`);272if (newMutations.length) {273logger.trace({ newMutations, name }, 'recv new mutations');274}275appStateChunk.totalMutations.push(...newMutations);276}277if (hasMorePatches) {278logger.info(`${name} has more patches...`);279}280else { // collection is done with sync281collectionsToHandle.delete(name);282}283}284catch (error) {285logger.info({ name, error: error.stack }, 'failed to sync state from version, removing and trying from scratch');286await authState.keys.set({ 'app-state-sync-version': { [name]: null } });287// increment number of retries288attemptsMap[name] = (attemptsMap[name] || 0) + 1;289// if retry attempts overshoot290// or key not found291if (attemptsMap[name] >= MAX_SYNC_ATTEMPTS || ((_a = error.output) === null || _a === void 0 ? void 0 : _a.statusCode) === 404) {292// stop retrying293collectionsToHandle.delete(name);294}295}296}297}298});299processSyncActions(appStateChunk.totalMutations);300return appStateChunk;301};302/**303* fetch the profile picture of a user/group304* type = "preview" for a low res picture305* type = "image for the high res picture"306*/307const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {308var _a;309jid = WABinary_1.jidNormalizedUser(jid);310const result = await query({311tag: 'iq',312attrs: {313to: jid,314type: 'get',315xmlns: 'w:profile:picture'316},317content: [318{ tag: 'picture', attrs: { type, query: 'url' } }319]320}, timeoutMs);321const child = WABinary_1.getBinaryNodeChild(result, 'picture');322return (_a = child === null || child === void 0 ? void 0 : child.attrs) === null || _a === void 0 ? void 0 : _a.url;323};324const sendPresenceUpdate = async (type, toJid) => {325const me = authState.creds.me;326if (type === 'available' || type === 'unavailable') {327await sendNode({328tag: 'presence',329attrs: {330name: me.name,331type332}333});334}335else {336await sendNode({337tag: 'chatstate',338attrs: {339from: me.id,340to: toJid,341},342content: [343{ tag: type, attrs: {} }344]345});346}347};348const presenceSubscribe = (toJid) => (sendNode({349tag: 'presence',350attrs: {351to: toJid,352id: generateMessageTag(),353type: 'subscribe'354}355}));356const handlePresenceUpdate = ({ tag, attrs, content }) => {357let presence;358const jid = attrs.from;359const participant = attrs.participant || attrs.from;360if (tag === 'presence') {361presence = {362lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',363lastSeen: attrs.t ? +attrs.t : undefined364};365}366else if (Array.isArray(content)) {367const [firstChild] = content;368let type = firstChild.tag;369if (type === 'paused') {370type = 'available';371}372presence = { lastKnownPresence: type };373}374else {375logger.error({ tag, attrs, content }, 'recv invalid presence node');376}377if (presence) {378ev.emit('presence.update', { id: jid, presences: { [participant]: presence } });379}380};381const resyncMainAppState = async () => {382logger.debug('resyncing main app state');383await (mutationMutex.mutex(() => resyncAppState([384'critical_block',385'critical_unblock_low',386'regular_high',387'regular_low',388'regular'389]))390.catch(err => (logger.warn({ trace: err.stack }, 'failed to sync app state'))));391};392const processSyncActions = (actions) => {393var _a, _b, _c, _d, _e;394const updates = {};395const contactUpdates = {};396const msgDeletes = [];397for (const { syncAction: { value: action }, index: [_, id, msgId, fromMe] } of actions) {398const update = { id };399if (action === null || action === void 0 ? void 0 : action.muteAction) {400update.mute = ((_a = action.muteAction) === null || _a === void 0 ? void 0 : _a.muted) ?401Utils_1.toNumber(action.muteAction.muteEndTimestamp) :402undefined;403}404else if (action === null || action === void 0 ? void 0 : action.archiveChatAction) {405update.archive = !!((_b = action.archiveChatAction) === null || _b === void 0 ? void 0 : _b.archived);406}407else if (action === null || action === void 0 ? void 0 : action.markChatAsReadAction) {408update.unreadCount = !!((_c = action.markChatAsReadAction) === null || _c === void 0 ? void 0 : _c.read) ? 0 : -1;409}410else if (action === null || action === void 0 ? void 0 : action.clearChatAction) {411msgDeletes.push({412remoteJid: id,413id: msgId,414fromMe: fromMe === '1'415});416}417else if (action === null || action === void 0 ? void 0 : action.contactAction) {418contactUpdates[id] = {419...(contactUpdates[id] || {}),420id,421name: action.contactAction.fullName422};423}424else if (action === null || action === void 0 ? void 0 : action.pushNameSetting) {425const me = {426...authState.creds.me,427name: (_d = action === null || action === void 0 ? void 0 : action.pushNameSetting) === null || _d === void 0 ? void 0 : _d.name428};429ev.emit('creds.update', { me });430}431else if (action === null || action === void 0 ? void 0 : action.pinAction) {432update.pin = ((_e = action.pinAction) === null || _e === void 0 ? void 0 : _e.pinned) ? Utils_1.toNumber(action.timestamp) : undefined;433}434else {435logger.warn({ action, id }, 'unprocessable update');436}437if (Object.keys(update).length > 1) {438updates[update.id] = {439...(updates[update.id] || {}),440...update441};442}443}444if (Object.values(updates).length) {445ev.emit('chats.update', Object.values(updates));446}447if (Object.values(contactUpdates).length) {448ev.emit('contacts.upsert', Object.values(contactUpdates));449}450if (msgDeletes.length) {451ev.emit('messages.delete', { keys: msgDeletes });452}453};454const appPatch = async (patchCreate) => {455const name = patchCreate.type;456const myAppStateKeyId = authState.creds.myAppStateKeyId;457if (!myAppStateKeyId) {458throw new boom_1.Boom('App state key not present!', { statusCode: 400 });459}460await mutationMutex.mutex(async () => {461logger.debug({ patch: patchCreate }, 'applying app patch');462await resyncAppState([name]);463const { [name]: initial } = await authState.keys.get('app-state-sync-version', [name]);464const { patch, state } = await Utils_1.encodeSyncdPatch(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey);465const node = {466tag: 'iq',467attrs: {468to: WABinary_1.S_WHATSAPP_NET,469type: 'set',470xmlns: 'w:sync:app:state'471},472content: [473{474tag: 'sync',475attrs: {},476content: [477{478tag: 'collection',479attrs: {480name,481version: (state.version - 1).toString(),482return_snapshot: 'false'483},484content: [485{486tag: 'patch',487attrs: {},488content: WAProto_1.proto.SyncdPatch.encode(patch).finish()489}490]491}492]493}494]495};496await query(node);497await authState.keys.set({ 'app-state-sync-version': { [name]: state } });498if (config.emitOwnEvents) {499const result = await Utils_1.decodePatches(name, [{ ...patch, version: { version: state.version }, }], initial, getAppStateSyncKey);500processSyncActions(result.newMutations);501}502});503};504/** sending abt props may fix QR scan fail if server expects */505const fetchAbt = async () => {506const abtNode = await query({507tag: 'iq',508attrs: {509to: WABinary_1.S_WHATSAPP_NET,510xmlns: 'abt',511type: 'get',512id: generateMessageTag(),513},514content: [515{ tag: 'props', attrs: { protocol: '1' } }516]517});518const propsNode = WABinary_1.getBinaryNodeChild(abtNode, 'props');519let props = {};520if (propsNode) {521props = WABinary_1.reduceBinaryNodeToDictionary(propsNode, 'prop');522}523logger.debug('fetched abt');524return props;525};526/** sending non-abt props may fix QR scan fail if server expects */527const fetchProps = async () => {528const resultNode = await query({529tag: 'iq',530attrs: {531to: WABinary_1.S_WHATSAPP_NET,532xmlns: 'w',533type: 'get',534id: generateMessageTag(),535},536content: [537{ tag: 'props', attrs: {} }538]539});540const propsNode = WABinary_1.getBinaryNodeChild(resultNode, 'props');541let props = {};542if (propsNode) {543props = WABinary_1.reduceBinaryNodeToDictionary(propsNode, 'prop');544}545logger.debug('fetched props');546return props;547};548/**549* modify a chat -- mark unread, read etc.550* lastMessages must be sorted in reverse chronologically551* requires the last messages till the last message received; required for archive & unread552*/553const chatModify = (mod, jid) => {554const patch = Utils_1.chatModificationToAppPatch(mod, jid);555return appPatch(patch);556};557ws.on('CB:presence', handlePresenceUpdate);558ws.on('CB:chatstate', handlePresenceUpdate);559ws.on('CB:ib,,dirty', async (node) => {560const { attrs } = WABinary_1.getBinaryNodeChild(node, 'dirty');561const type = attrs.type;562switch (type) {563case 'account_sync':564let { lastAccountSyncTimestamp } = authState.creds;565if (lastAccountSyncTimestamp) {566await updateAccountSyncTimestamp(lastAccountSyncTimestamp);567}568lastAccountSyncTimestamp = +attrs.timestamp;569ev.emit('creds.update', { lastAccountSyncTimestamp });570break;571default:572logger.info({ node }, 'received unknown sync');573break;574}575});576ws.on('CB:notification,type:server_sync', (node) => {577const update = WABinary_1.getBinaryNodeChild(node, 'collection');578if (update) {579const name = update.attrs.name;580mutationMutex.mutex(async () => {581await resyncAppState([name])582.catch(err => logger.error({ trace: err.stack, node }, 'failed to sync state'));583});584}585});586ev.on('connection.update', ({ connection }) => {587if (connection === 'open') {588sendPresenceUpdate('available');589fetchBlocklist();590fetchPrivacySettings();591fetchAbt();592fetchProps();593}594});595return {596...sock,597appPatch,598sendPresenceUpdate,599presenceSubscribe,600profilePictureUrl,601onWhatsApp,602fetchBlocklist,603fetchStatus,604updateProfilePicture,605updateBlockStatus,606getBusinessProfile,607resyncAppState,608chatModify,609resyncMainAppState,610};611};612exports.makeChatsSocket = makeChatsSocket;613614615