diff --git a/wai-server/api/messages/message.controller.js b/wai-server/api/messages/message.controller.js index 6db4240..4d1a639 100644 --- a/wai-server/api/messages/message.controller.js +++ b/wai-server/api/messages/message.controller.js @@ -3,11 +3,12 @@ const generateId = require('../../utils/generateId.util'); const { sessionStore } = require('../../core'); const { objectMapper, pick } = require('../../utils/commons.util'); -const { ctxToSendBuilder, ctxToDB } = require('./apiPayloadHelper'); -const { createOutboundMessage } = require('../../services/outbound_messages.service'); +// const { ctxToSendBuilder, ctxToDB } = require('./apiPayloadHelper'); +const { createOutboundMessage, getOutboundMessage } = require('../../services/outbound_messages.service'); const waEmitter = require('../../core/emitter'); const logger = require('../../utils/logger.util'); +const { ctxToSendBuilder, ctxToDB, DbData } = require('../../helper/wai.msg.helper'); function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } diff --git a/wai-server/core/handler/whatsappHandler.js b/wai-server/core/handler/whatsappHandler.js index dde9ac5..469b055 100644 --- a/wai-server/core/handler/whatsappHandler.js +++ b/wai-server/core/handler/whatsappHandler.js @@ -10,6 +10,7 @@ const { sessionStore } = require('..'); const { getOutboundMessage, upsertOutboundMessage } = require('../../services/outbound_messages.service'); const logger = require('../../utils/logger.util'); +const { DbData, } = require('../../helper/wai.msg.helper'); const connectionEventNames = ['connection:connect', 'connection:open', 'connection:close']; const messageEventNames = ['message:received', 'message:updated']; @@ -19,7 +20,7 @@ const eventTypeMapped = { 'message:updated': 'wai.message.updated', 'creds:update': 'wai.creds.update', }; -const timeField = { saved: 'createTime', pending: 'createTime', sent: 'sendTime', delivered: 'deliverTime', read: 'readTime' }; +const timeField = { saved: 'createTime', pending: 'createTime', sent: 'sendTime', delivered: 'deliverTime', read: 'readTime', failed: 'updateTime' }; const statusMapped = { saved: 'accepted', pending: 'accepted', sent: 'sent', delivered: 'delivered', read: 'read', failed: 'failed' }; const directionField = { 'message:received': 'inbound', 'message:updated': 'outbound' }; const directionPrefix = { inbound: 'in_', outbound: 'out_' }; @@ -53,9 +54,8 @@ const webhookBodyBuilder = (messageData, messageType) => { }; const webhookBodyFill = (webhookBody, messageData) => { - const { type } = messageData; - const messageObj = { [type]: messageData }; - Object.assign(webhookBody.waiMessage, messageObj); + const DBDataObj = DbData(messageData); + Object.assign(webhookBody.waiMessage, DBDataObj); return webhookBody; }; @@ -150,8 +150,9 @@ const setupMessageHandler = () => { const savedMsg = await getOutboundMessage(targetUpsert); const bixFields = pick(savedMsg, ['actionId', 'externalId']); logger.info('message evt\n', eventName, messageData, savedMsg); + const typeField = { msgtype: messageData?.type || savedMsg?.msgtype || 'text' }; // fix: type 空 - const webhookBody = webhookBodyBuilder({ ...messageData, ...bixFields }, eventName); + const webhookBody = webhookBodyBuilder({ ...messageData, ...bixFields, ...typeField }, eventName); const { waiMessage } = webhookBody; const timeFields = pick(waiMessage, [...Object.values(timeField), 'createTime', 'updateTime']); @@ -160,9 +161,11 @@ const setupMessageHandler = () => { const pusher = { customerProfile_id: waiMessage.customerProfile?.id || '', customerProfile_name: waiMessage.customerProfile?.name || '' }; const record = objectMapper(waiMessage, { from: 'from', to: 'to', status: 'msg_status', type: 'msgtype' }, false); const contentFields = waiMessage.type === 'text' ? { text_body: waiMessage.text.body } : {}; + // const contentFieldsToDB = + // todo: 现在只能收text 消息, 后续再加其他类型 const msgRow = await upsertOutboundMessage( - { ...timeFields, ...upsertFields, ...pusher, ...contentFields, ...record, message_origin: savedMsg?.message_origin || JSON.stringify(messageData) }, + { ...timeFields, ...upsertFields, ...pusher, ...contentFields, ...record, ...typeField, message_origin: savedMsg?.message_origin || JSON.stringify(messageData) }, targetUpsert, ); // console.log('upsert=========================', upsert); diff --git a/wai-server/helper/wai.msg.helper.js b/wai-server/helper/wai.msg.helper.js new file mode 100644 index 0000000..018486c --- /dev/null +++ b/wai-server/helper/wai.msg.helper.js @@ -0,0 +1,273 @@ +/** + * + */ +'use strict'; +const { objectMapper, pick } = require('../utils/commons.util'); + +const mediaMsg = { + contentToSend: msg => { + const { msgtype, msgcontent, ...body } = msg; + return { + // ...body, + to: body.to, + externalId: body.actionId, + // type WAMediaUpload = Buffer | WAMediaPayloadStream | WAMediaPayloadURL; + [msgtype]: { + url: msgcontent[msgtype].link, + }, + ...(msgcontent[msgtype].caption ? { caption: msgcontent[msgtype].caption } : {}), // image, video, document + // mimetype: 'audio/mp4', + ...(msgtype === 'document' ? { filename: msgcontent[msgtype].filename } : {}), // document + ...(msgcontent.context ? { quoted: { key: msgcontent.context.message_id } } : {}), + // seconds?: number; // audio + // isAnimated?: boolean; // sticker + // jpegThumbnail?: string; // image, video + imageUrl: msgcontent[msgtype].link, // 25.01.01 版本, 后续更改payload格式 todo: + }; + }, + dataToDB: msg => { + const { msgtype, msgcontent, ...body } = msg; + const record = pick(msg, ['actionId', 'msgtype', 'externalId', 'from', 'to']); + return { + direction: 'outbound', + msg_status: 'ready', + createTime: Date.now(), + id: msg.actionId, + ...record, + IVADS_link: msgcontent[msgtype].link, + IVADS_caption: msgcontent[msgtype].caption || '', + IVADS_filename: msgcontent[msgtype].filename || '', + ...(msgcontent.context + ? { + context_id: msgcontent.context.message_id, + context_from: msgcontent.message_origin.from, + } + : {}), + message_origin: JSON.stringify(msg), + }; + }, + DbData: row => ({ + [row.msgtype]: { + link: row.IVADS_link, + caption: row.IVADS_caption, + filename: row.IVADS_filename, + }, + ...(row.context_id ? { context: { message_id: row.context_id, from: row.context_from } } : {}), + }), +}; + +const waiMsgTypeMapped = { + text: { + type: 'text', + contentToSend: msg => ({ + // ...msg, + to: msg.to, + externalId: msg.actionId, + text: msg.msgcontent.body, + content: msg.msgcontent.body, // 25.01.01 版本, 后续更改payload格式 todo: + // linkPreview: true, // {} + ...(msg.msgcontent.context ? { quoted: { key: msg.msgcontent.context.message_id } } : {}), + }), + dataToDB: msg => { + const { msgcontent } = msg; + const record = pick(msg, ['actionId', 'msgtype', 'externalId', 'from', 'to']); + return { + direction: 'outbound', + msg_status: 'ready', + createTime: Date.now(), + id: msg.actionId, + ...record, + text_body: msgcontent.body, + text_preview_url: msgcontent.preview_url, + ...(msgcontent.context + ? { + context_id: msgcontent.context.message_id, + context_from: msgcontent.message_origin.from, + } + : {}), + message_origin: JSON.stringify(msg), + }; + }, + DbData: row => ({ + text: { body: row.text_body, preview_url: row.text_preview_url }, + ...(row.context_id ? { context: { message_id: row.context_id, from: row.context_from } } : {}), + }), + }, + image: { + type: 'image', + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(msg), + DbData: msg => mediaMsg.DbData(msg), + }, + sticker: { + type: 'sticker', + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(msg), + DbData: msg => mediaMsg.DbData(msg), + }, + audio: { + type: 'audio', + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(msg), + DbData: msg => mediaMsg.DbData(msg), + }, + video: { + type: 'video', // todo: gif + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(msg), + DbData: msg => mediaMsg.DbData(msg), + }, + document: { + type: 'document', + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(msg), + DbData: msg => mediaMsg.DbData(msg), + }, + react: { + type: 'react', + contentToSend: msg => ({ + // ...msg, + to: msg.to, + externalId: msg.actionId, + react: { + text: msg.msgcontent.body, // emoji | use an empty string to remove the reaction + key: msg.msgcontent.context.message_id, + }, + }), + dataToDB: msg => { + const { msgtype, msgcontent, ...body } = msg; + const record = pick(msg, ['actionId', 'msgtype', 'externalId', 'from', 'to']); + return { + direction: 'outbound', + msg_status: 'ready', + createTime: Date.now(), + id: msg.actionId, + ...record, + reaction_message_id: '', + reaction_emoji: '', + message_origin: JSON.stringify(msg), + }; + }, + DbData: row => ({ + reaction: { emoji: row.reaction_emoji, message_id: row.reaction_message_id }, + }), + }, + location: { + type: 'location', + contentToSend: msg => ({ + // ...msg, + to: msg.to, + externalId: msg.actionId, + // location: { degreesLatitude: msg.latitude, degreesLongitude: msg.longitude }, // todo: + }), + dataToDB: msg => { + const { msgtype, msgcontent, ...body } = msg; + const record = pick(msg, ['actionId', 'msgtype', 'externalId', 'from', 'to']); + return { + direction: 'outbound', + msg_status: 'ready', + createTime: Date.now(), + id: msg.actionId, + ...record, + message_origin: JSON.stringify(msg), + }; + }, + DbData: row => ({ + location: { + latitude: row.location_latitude, + longitude: row.location_longitude, + name: row.location_name, + address: row.location_address, + url: row.location_url, + }, + }), + }, + contacts: { + type: 'contacts', + contentToSend: msg => ({ + // ...msg, + to: msg.to, + externalId: msg.actionId, + // contacts: { displayName: '', contacts: msg.contacts }, // todo: + }), + dataToDB: msg => { + const { msgtype, msgcontent, ...body } = msg; + const record = pick(msg, ['actionId', 'msgtype', 'externalId', 'from', 'to']); + return { + direction: 'outbound', + msg_status: 'ready', + createTime: Date.now(), + id: msg.actionId, + ...record, + contacts: JSON.stringify(msg.contacts), + message_origin: JSON.stringify(msg), + }; + }, + DbData: row => ({ + contacts: JSON.parse(row.contacts), + }), + }, + template: { + type: 'template', + contentToSend: msg => ({ + // ...msg, + to: msg.to, + externalId: msg.actionId, + // msgcontent: { + // ...msg.template, + // components: [ + // ...msg.template.components.filter(com => !['footer', 'buttons'].includes(com.type.toLowerCase())), + // ], + // }, + }), + dataToDB: msg => { + const { msgtype, msgcontent, ...body } = msg; + const record = pick(msg, ['actionId', 'msgtype', 'externalId', 'from', 'to']); + return { + direction: 'outbound', + msg_status: 'ready', + createTime: Date.now(), + id: msg.actionId, + ...record, + message_origin: JSON.stringify(msg), + }; + }, + }, +}; + +/** + * API Payload to Send + */ +exports.ctxToSendBuilder = ctxContent => { + const { contentToSend } = waiMsgTypeMapped[ctxContent.msgtype]; + const msgReady = contentToSend(ctxContent); + return msgReady; +}; + +/** + * API Payload to DB + */ +exports.ctxToDB = ctxContent => { + const { dataToDB } = waiMsgTypeMapped[ctxContent.msgtype]; + const msgReady = dataToDB(ctxContent); + return msgReady; +}; + +/** + * Parse DB Data to UI/API/Webhook + */ +exports.DbData = row => { + const { DbData } = waiMsgTypeMapped[row.msgtype]; + const msgReady = DbData(row); + return msgReady; +}; diff --git a/wai-server/services/outbound_messages.service.js b/wai-server/services/outbound_messages.service.js index aa680b1..448d5fe 100644 --- a/wai-server/services/outbound_messages.service.js +++ b/wai-server/services/outbound_messages.service.js @@ -18,7 +18,8 @@ const getOutboundMessage = async msg => { }; const createOutboundMessage = async (data) => { - return await OutboundModelModel.create(data); + const r = await OutboundModelModel.create(data); + return r; } /**