diff --git a/wai-server/api/messages/apiPayloadHelper.js b/wai-server/api/messages/apiPayloadHelper.js new file mode 100644 index 0000000..dca3fce --- /dev/null +++ b/wai-server/api/messages/apiPayloadHelper.js @@ -0,0 +1,232 @@ +/** + * + */ +'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 + }; + }, + 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), + }; + }, +}; + +const apiSendMsgTypeMapped = { + text: { + type: 'text', + contentToSend: msg => ({ + // ...msg, + to: msg.to, + externalId: msg.actionId, + text: msg.msgcontent.body, + // 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), + }; + }, + }, + image: { + type: 'image', + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(msg), + }, + sticker: { + type: 'sticker', + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(msg), + }, + audio: { + type: 'audio', + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(msg), + }, + video: { + type: 'video', // todo: gif + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(msg), + }, + document: { + type: 'document', + contentToSend: msg => ({ + ...mediaMsg.contentToSend({ ...msg }), + }), + dataToDB: msg => mediaMsg.dataToDB(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), + }; + }, + }, + // 不支持 + 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), + }; + }, + }, + // 不支持 + 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, + message_origin: JSON.stringify(msg), + }; + }, + }, + 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 { type, contentToSend } = apiSendMsgTypeMapped[ctxContent.msgtype]; + const msgReady = contentToSend(ctxContent); + return msgReady; +}; + +/** + * API Payload to DB + */ +exports.ctxToDB = ctxContent => { + const { type, dataToDB } = apiSendMsgTypeMapped[ctxContent.msgtype]; + const msgReady = dataToDB(ctxContent); + return msgReady; +}; diff --git a/wai-server/api/messages/message.controller.js b/wai-server/api/messages/message.controller.js index 1716724..6db4240 100644 --- a/wai-server/api/messages/message.controller.js +++ b/wai-server/api/messages/message.controller.js @@ -3,9 +3,11 @@ 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 waEmitter = require('../../core/emitter'); +const logger = require('../../utils/logger.util'); function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -55,36 +57,24 @@ exports.sendText = async ctx => { exports.send = async ctx => { const { msgtype, from, to, msgcontent, content: _content, actionId } = ctx.request.body; const content = _content || msgcontent.body || ''; - if (!from || !content) { - ctx.assert(from, 400, 'From and message are required'); + logger.info('send', msgtype, from, to, content); + if (!from) { + ctx.assert(from, 400, '请先到个人资料页扫码登录WhatsApp'); return; } const wsToSend = sessionStore.getSession(from); // console.log('find wsToSend', wsToSend) if (!wsToSend) { - ctx.assert(wsToSend, 400, '未登录WhatsApp, 请到个人资料页扫码登录'); // 404 + ctx.assert(wsToSend, 401, '未登录WhatsApp, 请到个人资料页扫码登录'); // 404 return; } try { - // wsToSend.sendTextMessage(to, content, actionId).then(({ messageId }) => { - // const messageId = generateId(); - const _data = ctx.request.body; - const defaultR = { direction: 'outbound' }; - const r1 = pick(_data, ['actionId', 'msgtype', 'externalId']); - r1.id = actionId; // id not null - // r1.wamid = messageId; - r1.msg_status = 'ready'; - r1.createTime = Date.now(); - const record = objectMapper(_data, { from: 'from', to: 'to' }, false); - const byType = _data.msgtype === 'text' ? { text_body: _data.msgcontent.body, text_preview_url: _data.msgcontent.preview_url } : {}; - const toUpsert = { ...defaultR, ...r1, ...record, ...byType, message_origin: JSON.stringify(_data) }; + const toUpsert = ctxToDB(_data); await createOutboundMessage({ ...toUpsert }); - // return 'Message sent successfully'; // { wsToSend, ret: 'Message sent successfully' }; - // }); // wsToSend.sendTextMessage(to, content, actionId); - // todo: build msgtype content to wa - waEmitter.emit('request.' + from + '.send.' + msgtype, { to, content, externalId: actionId }); + const messagePayload = ctxToSendBuilder(_data); + waEmitter.emit('request.' + from + '.send.' + msgtype, messagePayload); return 'Message sent successfully'; // { wsToSend, ret: 'Message sent successfully' }; } catch (error) { console.error('Error sending message:', error); diff --git a/wai-server/core/handler/whatsappHandler.js b/wai-server/core/handler/whatsappHandler.js index 7f2b47f..dde9ac5 100644 --- a/wai-server/core/handler/whatsappHandler.js +++ b/wai-server/core/handler/whatsappHandler.js @@ -64,7 +64,6 @@ const webhookBodyFill = (webhookBody, messageData) => { */ const setupConnectionHandler = () => { // connectionEventNames.forEach(eventName => { - logger.info(`Setting up event ${'connection:connect'}`); whatsappEvents.on('connection:connect', async connectionData => { try { // find Or create @@ -77,7 +76,6 @@ const setupConnectionHandler = () => { logger.error({ connectionData, error }, 'error add connection'); } }); - logger.info(`Setting up event ${'connection:open'}`); whatsappEvents.on('connection:open', async connectionData => { logger.info(`event ${'connection:open'}`, connectionData); // todo: 更新实例 @@ -96,7 +94,6 @@ const setupConnectionHandler = () => { logger.error({ connectionData, error }, 'error add connection'); } }); - logger.info(`Setting up event ${'connection:close'}`); whatsappEvents.on('connection:close', async connectionData => { logger.info(`event ${'connection:close'}`, connectionData); try { @@ -140,7 +137,6 @@ const setupCredsHandler = () => { */ const setupMessageHandler = () => { messageEventNames.forEach(eventName => { - logger.info(`Setting up event ${eventName}`); whatsappEvents.on(eventName, async messageData => { // if (messageData.status === 'pending') { // logger.info('message pending', messageData); diff --git a/wai-server/models/outbound_messages.js b/wai-server/models/outbound_messages.js index 07e4086..812b6d9 100644 --- a/wai-server/models/outbound_messages.js +++ b/wai-server/models/outbound_messages.js @@ -37,7 +37,7 @@ module.exports = function(sequelize, DataTypes) { id: { type: DataTypes.STRING(200), allowNull: false, - unique: 'outbound_messages_id_IDX', + unique: 'outbound_messages_unique', }, wamid: { type: DataTypes.STRING(200), @@ -51,6 +51,10 @@ module.exports = function(sequelize, DataTypes) { type: DataTypes.STRING(100), allowNull: true, }, + customerProfile_id: { + type: DataTypes.STRING(100), + allowNull: true, + }, customerProfile_name: { type: DataTypes.TEXT, allowNull: true, @@ -116,6 +120,67 @@ module.exports = function(sequelize, DataTypes) { type: DataTypes.STRING(200), allowNull: true, }, + IVADS_link_original: { + type: DataTypes.TEXT, + allowNull: true, + comment: '供应商给的url', + }, + IVADS_link: { + type: DataTypes.TEXT, + allowNull: true, + }, + IVADS_caption: { + type: DataTypes.STRING(1024), + allowNull: true, + }, + IVADS_id: { + type: DataTypes.STRING(100), + allowNull: true, + }, + IVADS_sha256: { + type: DataTypes.STRING(200), + allowNull: true, + }, + IVADS_mime_type: { + type: DataTypes.STRING(255), + allowNull: true, + }, + IVADS_filename: { + type: DataTypes.TEXT, + allowNull: true, + }, + location_latitude: { + type: DataTypes.STRING(50), + allowNull: true, + }, + location_longitude: { + type: DataTypes.STRING(50), + allowNull: true, + }, + location_name: { + type: DataTypes.TEXT, + allowNull: true, + }, + location_address: { + type: DataTypes.TEXT, + allowNull: true, + }, + location_url: { + type: DataTypes.TEXT, + allowNull: true, + }, + contacts: { + type: DataTypes.TEXT, + allowNull: true, + }, + reaction_message_id: { + type: DataTypes.STRING(200), + allowNull: true, + }, + reaction_emoji: { + type: DataTypes.TEXT, + allowNull: true, + }, message_origin: { type: DataTypes.TEXT, allowNull: true, @@ -133,7 +198,7 @@ module.exports = function(sequelize, DataTypes) { fields: [{ name: 'sn' }], }, { - name: 'outbound_messages_id_IDX', + name: 'outbound_messages_unique', unique: true, using: 'BTREE', fields: [{ name: 'id' }],