UNPKG

@juzi/wechaty-puppet-whatsapp

Version:
301 lines 14.5 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 PUPPET = __importStar(require("@juzi/wechaty-puppet")); const config_js_1 = require("../../config.js"); const whatsapp_interface_js_1 = require("../../schema/whatsapp-interface.js"); const whatsapp_base_js_1 = __importDefault(require("../whatsapp-base.js")); const miscellaneous_js_1 = require("../../helper/miscellaneous.js"); const request_pool_js_1 = require("../../request/request-pool.js"); const uuid_1 = require("uuid"); const PRE = 'MessageEventHandler'; class MessageEventHandler extends whatsapp_base_js_1.default { async onMessage(message) { config_js_1.log.info(PRE, `onMessage(${JSON.stringify(message)})`); if (!(await this.checkCacheManager())) { config_js_1.log.warn('message ignored because login process is not finished'); return; } // @ts-ignore if (message.type === 'multi_vcard' || (message.type === 'e2e_notification' && message.body === '' && !message.author)) { // skip room join notification and multi_vcard message return; } const cacheManager = await this.manager.getCacheManager(); const messageId = message.id.id; const messageInCache = await cacheManager.getMessageRawPayload(messageId); if (messageInCache) { return; } await cacheManager.setMessageRawPayload(messageId, message); if (message._data?.caption && message._data?.type === 'image') { // see issue: https://github.com/wechaty/puppet-whatsapp/issues/390 // file message also have captions, but no text message should be generated const genTextMessageFromImageMessage = message; genTextMessageFromImageMessage.type = whatsapp_interface_js_1.MessageTypes.TEXT; const textMsgId = `${genTextMessageFromImageMessage.id.id}_TEXT`; genTextMessageFromImageMessage.id.id = textMsgId; genTextMessageFromImageMessage._data = undefined; await this.onMessage(genTextMessageFromImageMessage); } const contactId = message.from; if (contactId && (0, miscellaneous_js_1.isContactId)(contactId)) { const contact = await cacheManager.getContactOrRoomRawPayload(contactId); const notFriend = !contact?.isMyContact; if (notFriend) { const friendship = { id: (0, uuid_1.v4)(), contactId, hello: message.body, timestamp: message.timestamp, type: PUPPET.types.Friendship.Receive, ticket: '', }; await cacheManager.setFriendshipRawPayload(friendship.id, friendship); this.emit('friendship', { friendshipId: friendship.id }); } } const needEmitMessage = await this.convertInviteLinkMessageToEvent(message); if (needEmitMessage) { this.emit('message', { messageId }); } } /** * This event only for the message which sent by bot (web / phone) * @param {WhatsAppMessage} message message detail info * @returns */ async onMessageAck(message) { config_js_1.log.silly(PRE, `onMessageAck(${JSON.stringify(message)})`); if (!(await this.checkCacheManager())) { config_js_1.log.warn('message ignored because login process is not finished'); return; } /** * if message ack equal MessageAck.ACK_DEVICE, we could regard it as has already send success. * * FIXME: if the ack is not consecutive, and without MessageAck.ACK_DEVICE, then we could not receive this message. * * After add sync missed message schedule, if the ack of message has not reach MessageAck.ACK_DEVICE, * the schedule will emit these messages with wrong ack (ack = MessageAck.ACK_PENDING or MessageAck.ACK_SERVER), * and will make some mistakes (can not get the media of message). */ if (message.id.fromMe) { if (config_js_1.MessageMediaTypeList.includes(message.type)) { if (message.hasMedia && message.ack === whatsapp_interface_js_1.MessageAck.ACK_SERVER) { await this.processMessageFromBot(message); } if (message.ack === whatsapp_interface_js_1.MessageAck.ACK_DEVICE || message.ack === whatsapp_interface_js_1.MessageAck.ACK_READ) { await this.processMessageFromBot(message); } } else { await this.processMessageFromBot(message); } } } /** * This event only for the message which sent by bot (web / phone) and to the bot self * @param {WhatsAppMessage} message message detail info * @returns */ async onMessageCreate(message) { config_js_1.log.silly(PRE, `onMessageCreate(${JSON.stringify(message)})`); if (!(await this.checkCacheManager())) { config_js_1.log.warn('message ignored because login process is not finished'); return; } if (message.id.fromMe) { const messageId = message.id.id; const cacheManager = await this.manager.getCacheManager(); await cacheManager.setMessageRawPayload(messageId, message); // void sleep // const requestPool = RequestPool.Instance // const now = Date.now() // while (!requestPool.hasRequest(messageId) && Date.now() - now < 400) { // await sleep(100) // } // requestPool.resolveRequest(messageId) await (0, miscellaneous_js_1.sleep)(1000); // wait for sent message method return to avoid duplicate message // self sent message is not time sensitive, so we can wait for a while this.emit('message', { messageId }); } } async processMessageFromBot(message) { const messageId = message.id.id; const cacheManager = await this.manager.getCacheManager(); const messageInCache = await cacheManager.getMessageRawPayload(messageId); await cacheManager.setMessageRawPayload(messageId, message); // set message with different message ack /** * - Non-Media Message * emit only when no cache * * - Media Message * emit message when no cache or ack of message in cache equal 1 */ if (!messageInCache || (config_js_1.MessageMediaTypeList.includes(message.type) && messageInCache.ack === whatsapp_interface_js_1.MessageAck.ACK_SERVER)) { if (!message.author) { // based on experience, not officially conformed // self message from other device contains author // while sent from this puppet it's undefined config_js_1.log.info(PRE, `seems to be self sent message, so skip. id: ${messageId}, base content: ${message.body}`); return; } const requestPool = request_pool_js_1.RequestPool.Instance; const hasRequest = requestPool.resolveRequest(messageId); if (!hasRequest) { this.emit('message', { messageId }); } } if (messageInCache && message.id.fromMe && message.ack > messageInCache.ack && [whatsapp_interface_js_1.MessageAck.ACK_READ, whatsapp_interface_js_1.MessageAck.ACK_PLAYED].includes(message.ack)) { await cacheManager.setMessageRawPayload(messageId, message); this.emit('dirty', { payloadId: messageId, payloadType: PUPPET.types.Dirty.Message, }); } } async convertInviteLinkMessageToEvent(message) { const cacheManager = await this.manager.getCacheManager(); if (message.type === whatsapp_interface_js_1.MessageTypes.GROUP_INVITE) { const inviteCode = message.inviteV4?.inviteCode; if (inviteCode) { const roomInvitationPayload = { roomInvitationId: inviteCode, }; await cacheManager.setRoomInvitationRawPayload(inviteCode, { inviteCode }); this.emit('room-invite', roomInvitationPayload); } else { config_js_1.log.warn(PRE, `convertInviteLinkMessageToEvent can not get invite code: ${JSON.stringify(message)}`); } return false; } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (message.type === whatsapp_interface_js_1.MessageTypes.TEXT && message.links && message.links.length === 1 && (0, miscellaneous_js_1.isInviteLink)(message.links[0].link)) { const inviteCode = (0, miscellaneous_js_1.getInviteCode)(message.links[0].link); if (inviteCode) { const roomInvitationPayload = { roomInvitationId: inviteCode, }; await cacheManager.setRoomInvitationRawPayload(inviteCode, { inviteCode }); this.emit('room-invite', roomInvitationPayload); return false; } } return true; } async onIncomingCall(...args) { config_js_1.log.silly(PRE, `onIncomingCall(${JSON.stringify(args)})`); } async onMediaUploaded(message) { config_js_1.log.silly(PRE, `onMediaUploaded(${JSON.stringify(message)})`); await this.createOrUpdateImageMessage(message); if (!message.hasMedia) { config_js_1.log.warn(PRE, `onMediaUploaded failed, message id: ${message.id.id}, type: ${message.type}, detail info: ${JSON.stringify(message)}`); } } async createOrUpdateImageMessage(message) { if (message.type === whatsapp_interface_js_1.MessageTypes.IMAGE) { const messageId = message.id.id; const cacheManager = await this.manager.getCacheManager(); const messageInCache = await cacheManager.getMessageRawPayload(messageId); if (messageInCache) { message.body = messageInCache.body || message.body; await cacheManager.setMessageRawPayload(messageId, message); return; } await cacheManager.setMessageRawPayload(messageId, message); } } /** * Someone delete message in all devices. Due to they have the same message id so we generate a fake id as flash-store key. * see: https://github.com/pedroslopez/whatsapp-web.js/issues/1178 * @param message revoke message * @param revokedMsg original message, sometimes it will be null */ async onMessageRevokeEveryone(message, revokedMsg) { config_js_1.log.silly(PRE, `onMessageRevokeEveryone(newMsg: ${JSON.stringify(message)}, originalMsg: ${JSON.stringify(revokedMsg)})`); if (!(await this.checkCacheManager())) { config_js_1.log.warn('message ignored because login process is not finished'); return; } const cacheManager = await this.manager.getCacheManager(); const messageId = message.id.id; if (revokedMsg) { const originalMessageId = revokedMsg.id.id; const recalledMessageId = this.generateFakeRecallMessageId(originalMessageId); message.body = recalledMessageId; await cacheManager.setMessageRawPayload(recalledMessageId, revokedMsg); } await cacheManager.setMessageRawPayload(messageId, message); this.emit('message', { messageId }); } /** * Only delete message in bot phone will trigger this event. But the message type is chat, not revoked any more. */ async onMessageRevokeMe(message) { config_js_1.log.silly(PRE, `onMessageRevokeMe(${JSON.stringify(message)})`); if (!(await this.checkCacheManager())) { config_js_1.log.warn('message ignored because login process is not finished'); } /* if (message.ack === MessageAck.ACK_PENDING) { // when the bot logout, it will receive onMessageRevokeMe event, but it's ack is MessageAck.ACK_PENDING, so let's ignore this event. return } const cacheManager = await this.manager.getCacheManager() const messageId = message.id.id message.type = WhatsAppMessageType.REVOKED message.body = messageId const recalledMessageId = this.generateFakeRecallMessageId(messageId) await cacheManager.setMessageRawPayload(recalledMessageId, message) this.emit('message', { messageId: recalledMessageId }) */ } generateFakeRecallMessageId(messageId) { return `${messageId}_revoked`; } async checkCacheManager() { let cacheManager; try { cacheManager = await this.manager.getCacheManager(); } catch (e) { } if (!cacheManager) { config_js_1.log.warn(PRE, 'message comes before login process finished'); return false; } return true; } } exports.default = MessageEventHandler; //# sourceMappingURL=message-event-handler.js.map