@juzi/wechaty-puppet-whatsapp
Version:
Wechaty Puppet for WhatsApp
301 lines • 14.5 kB
JavaScript
;
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