UNPKG

@juzi/wechaty-puppet-whatsapp

Version:
328 lines 13.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const ee_ts_1 = require("ee-ts"); const config_js_1 = require("./config.js"); const cache_manager_js_1 = require("./data/cache-manager.js"); const error_type_js_1 = require("./exception/error-type.js"); const whatsapp_error_js_1 = __importDefault(require("./exception/whatsapp-error.js")); const miscellaneous_js_1 = require("./helper/miscellaneous.js"); const schedule_manager_js_1 = __importDefault(require("./helper/schedule/schedule-manager.js")); const request_manager_js_1 = require("./request/request-manager.js"); const whatsapp_interface_js_1 = require("./schema/whatsapp-interface.js"); const whatsapp_manager_js_1 = __importDefault(require("./whatsapp/whatsapp-manager.js")); const PUPPET = __importStar(require("@juzi/wechaty-puppet")); const PRE = 'Manager'; class Manager extends ee_ts_1.EventEmitter { options; whatsAppManager; cacheManager; _requestManager; scheduleManager; memory; fetchingMessages = false; heartbeatTimer; selfId = ''; constructor(options) { super(); this.options = options; this.whatsAppManager = new whatsapp_manager_js_1.default(this); this.scheduleManager = schedule_manager_js_1.default.Instance; this.whatsAppManager.on({ friendship: data => this.emit('friendship', data), login: data => { this.emit('login', data); this.selfId = data; }, logout: (botId, data) => this.emit('logout', botId, data), message: data => this.emit('message', data), ready: () => this.emit('ready'), 'room-invite': data => this.emit('room-invite', data), 'room-join': data => this.emit('room-join', data), 'room-leave': data => this.emit('room-leave', data), 'room-topic': data => this.emit('room-topic', data), scan: data => this.emit('scan', data), dirty: data => this.onDirty(data), }); return new Proxy(this, { get: (target, prop) => { return request_manager_js_1.requestManagerKeys.indexOf(prop) > -1 ? target.requestManager[prop].bind(target.requestManager) : target[prop]; }, }); } getMemory() { if (this.memory) { return this.memory; } else { throw (0, whatsapp_error_js_1.default)(error_type_js_1.WA_ERROR_TYPE.ERR_INIT, 'No Memory'); } } /** * Lifecycle */ async start(memory) { if (memory) { this.memory = memory; } const session = await this.getMemory().get(config_js_1.MEMORY_SLOT); config_js_1.log.verbose(PRE, 'start()'); const whatsAppClient = await this.whatsAppManager.genWhatsAppClient(this.options['puppeteerOptions'], session); try { await this.whatsAppManager.initWhatsAppEvents(); await this.whatsAppManager.initWhatsAppClient(); } catch (error) { config_js_1.log.error(PRE, `start() error message: ${error.stack}`); await (0, miscellaneous_js_1.sleep)(2 * 1000); await this.start(session); } this._requestManager = new request_manager_js_1.RequestManager(whatsAppClient); this.startHeartbeat(); return whatsAppClient; } async stop() { config_js_1.log.verbose(PRE, 'stop()'); this.stopSchedule(); await this.whatsAppManager.stop(); await this.releaseCache(); this._requestManager = undefined; this.stopHeartbeat(); } get requestManager() { if (!this._requestManager) { throw (0, whatsapp_error_js_1.default)(error_type_js_1.WA_ERROR_TYPE.ERR_INIT, 'No request manager'); } return this._requestManager; } getWhatsAppClient() { return this.whatsAppManager.getWhatsAppClient(); } /** * LOGIC METHODS */ /** * Fetch history messages of contact or room, and then call onMessage method to emit them or not. * @param {WhatsAppContact} contactOrRoom contact or room instance */ async processHistoryMessages(contactOrRoom) { if (this.fetchingMessages) { return; } this.fetchingMessages = true; const fetchedMessageList = await this.fetchMessages(contactOrRoom); const filteredMessageList = await this.filterFetchedMessages(contactOrRoom.id._serialized, fetchedMessageList); await this.processFetchedMessages(filteredMessageList); this.fetchingMessages = false; } async fetchMessages(contactOrRoom) { if (contactOrRoom.isMe) { // can not get chat for bot self return []; } const chat = await contactOrRoom.getChat(); const messageList = await chat.fetchMessages({}); return messageList; } async filterFetchedMessages(contactOrRoomId, messageList) { const cacheManager = await this.getCacheManager(); const maxTimestampForLoadHistoryMessages = (0, miscellaneous_js_1.getMaxTimestampForLoadHistoryMessages)(); const latestTimestampInCache = await cacheManager.getLatestMessageTimestampForChat(contactOrRoomId); const minTimestamp = Math.min(latestTimestampInCache, maxTimestampForLoadHistoryMessages); try { const _messageList = messageList.filter(m => m.timestamp >= minTimestamp); const latestMessageTimestamp = _messageList[_messageList.length - 1]?.timestamp; if (latestMessageTimestamp) { await cacheManager.setLatestMessageTimestampForChat(contactOrRoomId, latestMessageTimestamp); } return _messageList; } catch (error) { config_js_1.log.error(PRE, `filterFetchedMessages error: ${error.message}`); return []; } } async processFetchedMessages(messageList) { const batchSize = 50; await (0, miscellaneous_js_1.batchProcess)(batchSize, messageList, async (message) => { if (message.ack === whatsapp_interface_js_1.MessageAck.ACK_DEVICE || message.ack === whatsapp_interface_js_1.MessageAck.ACK_READ) { await this.processMessage(message); } }); } async getRoomChatById(roomId) { if ((0, miscellaneous_js_1.isRoomId)(roomId)) { const roomChat = (await this.requestManager.getChatById(roomId)); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!roomChat.participants) { roomChat.participants = [{ id: (await this.requestManager.getContactById(this.selfId)).id, isAdmin: true, isSuperAdmin: true, }]; } return roomChat; } else { throw (0, whatsapp_error_js_1.default)(error_type_js_1.WA_ERROR_TYPE.ERR_GROUP_OR_CONTACT_ID, `The roomId: ${roomId} is not right.`); } } /** * Get member id list from web api * @param { PuppetWhatsApp } this whatsapp client * @param { string } roomId roomId * @returns { string[] } member id list */ async syncRoomMemberList(roomId) { const roomChat = await this.getRoomChatById(roomId); // FIXME: How to deal with pendingParticipants? Maybe we should find which case could has this attribute. const memberIdList = roomChat.participants.map(m => m.id._serialized); const cacheManager = await this.getCacheManager(); await cacheManager.setRoomMemberIdList(roomId, memberIdList); return memberIdList; } async syncContactOrRoomList() { const whatsapp = this.getWhatsAppClient(); const now = Date.now(); config_js_1.log.info(PRE, `syncContactOrRoomList() whatsapp.getContacts() start at ${new Date().toISOString()}`); const contactList = await whatsapp.getContacts(); config_js_1.log.info(PRE, `syncContactOrRoomList() whatsapp.getContacts() end at ${new Date().toISOString()}, cost ${Date.now() - now}ms`); const contactOrRoomList = contactList.filter(c => c.id.server !== 'broadcast' && c.id._serialized !== '0@c.us'); config_js_1.log.info(PRE, `syncContactOrRoomList() contactOrRoomList.length: ${contactOrRoomList.length}`); return contactOrRoomList; } async processMessage(message) { config_js_1.log.silly(PRE, `processMessage(${message})`); await this.whatsAppManager.getMessageEventHandler().onMessage(message); } /** * Cache Section */ async initCache(userId) { config_js_1.log.verbose(PRE, `initCache(${userId})`); if (this.cacheManager) { config_js_1.log.warn(PRE, 'initCache() already initialized, skip the init...'); return; } await cache_manager_js_1.CacheManager.init(userId); this.cacheManager = cache_manager_js_1.CacheManager.Instance; } async releaseCache() { config_js_1.log.verbose(PRE, 'releaseCache()'); if (this.cacheManager) { config_js_1.log.warn(PRE, 'releaseCache() already initialized, skip the init...'); return; } await cache_manager_js_1.CacheManager.release(); } async getCacheManager() { if (!this.cacheManager) { throw (0, whatsapp_error_js_1.default)(error_type_js_1.WA_ERROR_TYPE.ERR_INIT, 'no cache manager'); } return this.cacheManager; } /** * Schedule */ startSchedule() { this.scheduleManager.addScheduledTask('0 */2 * * * *', async () => { config_js_1.log.silly(PRE, 'startSyncMissedMessages'); const contactOrRoomList = await this.syncContactOrRoomList(); const batchSize = 100; await (0, miscellaneous_js_1.batchProcess)(batchSize, contactOrRoomList, async (contactOrRoom) => { await this.processHistoryMessages(contactOrRoom); }); config_js_1.log.silly(PRE, 'startSyncMissedMessages finished'); }); } stopSchedule() { this.scheduleManager.clearAllTasks(); } /** * Heatbeat */ startHeartbeat() { if (!this.heartbeatTimer) { this.asystoleCount = 0; this.heartbeatTimer = setInterval(this.heartbeat.bind(this), 15 * 1000); } } stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; } } asystoleCount = 0; async heartbeat() { /** * puppteer.isConnected behaviour: (in MacOs) * it will still return true if the Chromium window is closed with command + w * it will not return true if the Chromium process is terminated with command + q */ let alive = false; try { alive = !!this.getWhatsAppClient().pupBrowser?.isConnected(); } catch (e) { alive = false; } if (alive) { this.asystoleCount = 0; this.emit('heartbeat', 'puppeteer still connected'); } else { this.asystoleCount += 1; config_js_1.log.warn(PRE, `asystole count: ${this.asystoleCount}`); if (this.asystoleCount > config_js_1.MAX_HEARTBEAT_MISSED) { config_js_1.log.error(PRE, 'max asystole reached, restarting...'); await this.stop(); await this.start(); this.asystoleCount = 0; } } } async onDirty(data) { config_js_1.log.info(PRE, `onDirty(${JSON.stringify(data)})`); switch (data.payloadType) { case PUPPET.types.Dirty.Contact: { const contactId = data.payloadId; const rawContact = await this.requestManager.getContactById(contactId); const avatar = await rawContact.getProfilePicUrl() || ''; const contact = Object.assign(rawContact, { avatar }); await this.cacheManager?.setContactOrRoomRawPayload(contactId, contact); break; } default: break; } this.emit('dirty', data); } } exports.default = Manager; //# sourceMappingURL=manager.js.map