You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Global-sales/src/lib/msgUtils.js

558 lines
20 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { cloneDeep, isEmpty, olog } from "@/utils/utils";
import { v4 as uuid } from "uuid";
export const replaceTemplateString = (str, replacements) => {
let result = str;
let keys = str.match(/{{(.*?)}}/g).map(key => key.replace(/{{|}}/g, ''));
for (let i = 0; i < keys.length; i++) {
let replaceValue = replacements[i];
let template = new RegExp(`{{${keys[i]}}}`, 'g');
result = result.replace(template, replaceValue);
}
return result;
}
/**
* @deprecated 在渲染时处理
*/
export const autoLinkText = (text) => {
return text;
// let regex = /(https?:\/\/[^\s]+)/g;
// let newText = text.replace(regex, '<a href="$1" target="_blank">$1</a>');
// return newText;
}
/**
*
// +8618777396951 lyj
{
"to": "+8613317835586", // qqs
"msgtype": "text",
"msgcontent": "{\"body\":\"txtmsgtest\"}"
}
*/
const mediaMsg = {
contentToSend: (msg) => ({
action: 'message',
actionId: msg.id,
renderId: msg.id,
to: msg.to,
msgcontent: {
[msg.type]: {
link: msg.data.dataUri,
...(msg.text ? { caption: msg.text } : {}),
...(msg.type === 'document' ? { filename: msg.name } : {})
},
...(msg.context ? { context: msg.context, message_origin: msg.message_origin.msgOrigin } : {}),
},
}),
contentToRender: (msg) => ({
...msg,
actionId: msg.id,
conversationid: msg.id.split('.')[0],
data: { ...msg.data, status: { download: msg.data?.loading ? false : true, click: true, loading: msg.data.loading } },
...(msg.context
? {
reply: {
id: msg.message_origin.id,
message: msg.message_origin.text,
title: msg.message_origin.senderName || 'Reference',
titleColor: msg.message_origin?.senderName !== 'me' ? '#a791ff' : '#128c7e',
},
}
: {}),
}),
};
export const sentMsgTypeMapped = {
text: {
type: 'text',
contentToSend: (msg) => ({
action: 'message',
actionId: msg.id,
renderId: msg.id,
to: msg.to,
msgtype: 'text',
msgcontent: { body: msg.text, preview_url: true, ...(msg.context ? { context: msg.context, message_origin: msg.message_origin.msgOrigin } : {}) },
}),
contentToRender: (msg) => ({
...msg,
whatsapp_msg_type: 'text',
actionId: msg.id,
conversationid: msg.id.split('.')[0],
originText: msg.text,
text: autoLinkText(msg.text),
...(msg.context
? {
reply: {
id: msg.message_origin.id,
message: msg.message_origin.text,
title: msg.message_origin.senderName || 'Reference',
titleColor: msg.message_origin?.senderName !== 'me' ? '#a791ff' : '#128c7e',
photoURL: msg.message_origin?.data?.uri || '',
},
}
: {}),
}),
},
photo: {
type: 'image',
contentToSend: (msg) => ({
...mediaMsg.contentToSend({...msg, type: 'image'}),
msgtype: 'image',
}),
contentToRender: (msg) => ({
...msg,
...mediaMsg.contentToRender(msg),
whatsapp_msg_type: 'image',
}),
},
sticker: {
type: 'sticker',
contentToSend: (msg) => ({
...mediaMsg.contentToSend({...msg, type: 'sticker'}),
msgtype: 'sticker',
}),
contentToRender: (msg) => ({
...msg,
...mediaMsg.contentToRender(msg),
whatsapp_msg_type: 'sticker',
type: 'photo',
}),
},
video: {
type: 'video',
contentToSend: (msg) => ({
...mediaMsg.contentToSend(msg),
msgtype: 'video',
}),
contentToRender: (msg) => ({
...msg,
...mediaMsg.contentToRender(msg),
whatsapp_msg_type: 'video',
}),
},
document: {
type: 'document',
contentToSend: (msg) => ({
...mediaMsg.contentToSend(msg),
msgtype: 'document',
}),
contentToRender: (msg) => ({
...msg,
...mediaMsg.contentToRender(msg),
text: (msg?.name || '') + `\n${msg?.text || ''}`,
title: msg?.name || '',
originText: msg?.name || '',
whatsapp_msg_type: 'document',
type: 'file',
}),
},
whatsappTemplate: {
contentToSend: (msg) => ({ action: 'message', actionId: msg.id, renderId: msg.id, to: msg.to, msgtype: 'template', msgcontent: msg.template }),
contentToRender: (msg) => {
const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({ ...r, [v.type]: v }), {}) : null;
// const templateParam = (templateDataMapped?.body?.parameters || []).map(e => e.text);
// const fillTemplate = templateParam.length ? replaceTemplateString(msg.template_origin.components.body?.[0]?.text || '', templateParam) : (msg.template_origin.components.body?.[0]?.text || '');
// const footer = msg.template_origin.components?.footer?.[0]?.text || '';
return {
...msg,
actionId: msg.id,
conversationid: msg.id.split('.')[0],
type: 'text',
title: msg.template_origin.components.header?.[0]?.text || '',
text: autoLinkText(templateDataMapped?.body?.text || ''), // msg.template_origin.components.body?.[0]?.text || '',
};
},
},
};
const whatsappMsgMapped = {
'whatsapp.inbound_message.received': {
getMsg: (result) => {
console.log('whatsapp.inbound_message.received', result);
return isEmpty(result?.whatsappInboundMessage) ? null : { ...result.whatsappInboundMessage, conversationid: result.conversationid, messageorigin: result.messageorigin };
},
contentToRender: (contentObj) => {
console.log('whatsapp.inbound_message.received to render', contentObj);
// const contentObj = result?.whatsappInboundMessage || result; // debug:
return parseRenderMessageItem(contentObj);
},
contentToUpdate: () => null,
},
'whatsapp.message.updated': {
getMsg: (result) => {
console.log('getMsg', result);
return isEmpty(result?.whatsappMessage) ? null : { ...result.whatsappMessage, conversationid: result.conversationid };
},
contentToRender: (contentObj) => {
if ((contentObj?.status === 'failed' )) {
contentObj = {
...contentObj,
type: 'error',
text: {body: `${whatsappError?.[contentObj.errorCode] || contentObj.errorMessage}` }, // contentObj.errorMessage // Message failed to send.
id: contentObj.id,
wamid: contentObj.id,
};
return parseRenderMessageItem(contentObj);
}
// * 仅更新消息状态, 没有输出
return null;
},
contentToUpdate: (msgcontent) => ({
...msgcontent,
...parseRenderMessageItem(msgcontent),
id: msgcontent.wamid,
status: msgStatusRenderMapped[(msgcontent?.status || 'failed')],
sender: 'me',
dateString: msgcontent.status==='failed' ? `发送失败 ${whatsappError?.[msgcontent.errorCode] || msgcontent.errorMessage}` : '',
}),
},
};
export const msgStatusRenderMapped = {
'accepted': 'waiting', // 'sent', // 接口的发送请求
'sent': 'sent',
'delivered': 'received',
'read': 'read',
'failed': 'failed',
};
export const msgStatusRenderMappedCN = {
'accepted': '[发送ing]',
'sent': '[已发送]',
'delivered': '[已送达]',
'read': '[已读]',
'failed': '❗',
};
export const receivedMsgTypeMapped = {
...cloneDeep(whatsappMsgMapped),
'message': {
// 发送消息的同步记录 status: 'accepted'
getMsg: (result) => ({ ...result, conversationid: result.actionId.split('.')[0] }),
contentToRender: () => null,
contentToUpdate: (msgcontent) => ({
...msgcontent,
actionId: msgcontent.actionId,
id: msgcontent.wamid,
status: msgStatusRenderMapped[(msgcontent?.status || 'failed')],
conversationid: msgcontent.actionId.split('.')[0], // msgcontent.conversation.sn,
}),
},
'error': {
// 发送消息的同步返回: 发送失败时
getMsg: (result) => result,
contentToRender: () => null,
contentToUpdate: (msgcontent) => ({ ...msgcontent, id: msgcontent.actionId, status: msgcontent?.status || 'failed', dateString: `发送失败 ${msgcontent.error.message}`, conversationid: msgcontent.actionId.split('.')[0], }),
},
};
export const whatsappMsgTypeMapped = {
error: {
type: (_m) => ({ type: 'system' }),
data: (msg) => ({ id: msg.wamid, text: msg.errorCode ? msg.errorMessage : msg.text.body }),
},
system: {
type: 'system',
data: (msg) => ({ id: msg.wamid, text: msg.system.body }),
},
text: {
type: 'text',
data: (msg) => ({ id: msg.wamid, text: autoLinkText(msg.text.body), originText: msg.text.body, title: msg?.customerProfile?.name || '' }),
renderForReply: (msg) => ({ id: msg.wamid, message: msg.text.body }),
},
image: {
type: 'photo',
data: (msg) => ({
id: msg.wamid,
text: msg.image.caption,
data: {
id: msg.wamid,
uri: msg.image.link,
width: '100%',
height: 200,
alt: msg.image.caption,
status: {
click: true,
loading: 0,
download: true,
},
},
originText: msg.image?.caption || '',
}),
renderForReply: (msg) => ({
id: msg.wamid,
photoURL: msg.image.link,
width: '100%',
height: 200,
alt: msg.image?.caption || '',
}),
},
sticker: {
type: 'photo',
data: (msg) => ({
id: msg.wamid,
data: {
id: msg.wamid,
uri: msg.sticker.link,
width: '100%',
height: 120,
alt: '',
status: {
click: true,
loading: 0,
download: true,
},
},
}),
renderForReply: (msg) => ({
id: msg.wamid,
photoURL: msg.sticker.link,
width: '100%',
height: 200,
alt: '',
message: '[表情]',
}),
},
video: {
type: 'video',
data: (msg) => ({
id: msg.wamid,
data: {
id: msg.wamid,
videoURL: msg.video.link,
status: {
click: true,
loading: 0,
download: true,
},
},
}),
renderForReply: (msg) => ({
id: msg.wamid,
videoURL: msg.video.link,
photoURL: msg.video.link,
message: msg.video?.caption || '[视频]',
width: 200,
height: 200,
alt: '',
}),
},
audio: {
type: 'audio',
data: (msg) => ({
id: msg.wamid,
data: {
audioURL: msg.audio.link,
},
}),
renderForReply: (msg) => ({
id: msg.wamid,
message: '[语音]',
}),
},
// unsupported: { type: 'system', data: (msg) => ({ text: 'Message type is currently not supported.' }) },
unsupported: {
type: 'text',
data: (msg) => ({ id: msg.wamid, text: `[暂不支持此消息类型](${msg.wamid})` }),
renderForReply: (msg) => ({ id: msg.wamid, text: `[Message type unsupported](${msg.wamid})` }),
},
reaction: {
type: 'text',
data: (msg) => ({ id: msg.wamid, text: msg.reaction?.emoji || '' }),
},
document: {
type: 'file',
data: (msg) => ({
id: msg.wamid,
title: msg.document?.filename || '',
text: (msg.document?.filename || '') + `\n${msg.document?.caption || ''}`,
data: { uri: msg.document.link, status: { click: false, download: true, loading: 0 } },
originText: msg.document?.caption || msg.document?.filename || '',
}),
renderForReply: (msg) => ({
id: msg.wamid,
message: msg.document?.caption || msg.document?.filename || '[文件]',
}),
},
contacts: {
type: 'meetingLink',
data: (msg) => ({
id: msg.wamid,
meetingID: msg.wamid,
title: msg.contacts.length === 1 ? `联系人` : `${msg.contacts.length} 位联系人`,
text: msg.contacts.map((ele) => `${(ele?.org?.company || '') +' '+ele.name.formatted_name}: ${ele.phones[0].wa_id || ele.phones[0].phone}`).join('\n'),
data: msg.contacts.map((ele) => ({ id: ele.phones[0]?.wa_id || ele.phones[0].phone, wa_id: ele.phones[0]?.wa_id || '', phone: ele.phones[0].phone, name: (ele?.org?.company || '') +' '+ele.name.formatted_name })),
waBtn: msg.contacts.some(ele => ele.phones.some(p => p.wa_id)),
}),
renderForReply: (msg) => ({
id: msg.wamid,
message: '[联系人]',
}),
},
location: {
type: 'location',
data: (msg) => ({
id: msg.wamid,
title: `位置信息 ${msg.location.name || ''} ↓打开高德地图`,
text: msg.location.address, // 地址
src: `https://uri.amap.com/marker?position=${msg.location.longitude},${msg.location.latitude}&callnative=1`,
data: {
longitude: msg.location?.longitude,
latitude: msg.location?.latitude,
},
originText: msg.location?.address || '',
}),
renderForReply: (msg) => ({
id: msg.wamid,
message: '[位置]',
}),
},
template: {
type: 'text',
data: (msg) => {
const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({ ...r, [v.type]: v }), {}) : {};
return { id: msg.wamid, text: autoLinkText(templateDataMapped?.body?.text || `......${(templateDataMapped?.body?.parameters || []).map(pv => pv?.text || '').join('......')}......`), title: msg.template.name };
},
renderForReply: (msg) => {
const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({ ...r, [v.type]: v }), {}) : null;
return { id: msg.wamid, message: templateDataMapped?.body?.text || templateDataMapped?.body?.parameters?.[0]?.text || '', title: `${msg.template.name}` };
},
},
};
/**
* render received msg
*/
export const parseRenderMessageItem = (msg) => {
console.log('parseRenderMessageItem', msg);
const thisMsgType = Object.keys(whatsappMsgTypeMapped).includes(msg.type) ? msg.type : 'unsupported';
return {
msgOrigin: msg,
date: msg?.sendTime || msg?.createTime || '',
...(whatsappMsgTypeMapped?.[thisMsgType]?.data(msg) || {}),
conversationid: msg.conversationid,
...(typeof whatsappMsgTypeMapped[thisMsgType].type === 'function' ? whatsappMsgTypeMapped[thisMsgType].type(msg) : { type: whatsappMsgTypeMapped[thisMsgType].type || 'text' }),
// type: whatsappMsgTypeMapped?.[thisMsgType]?.type || 'text',
from: msg.from,
sender: msg.from,
senderName: msg?.customerProfile?.name || msg.from,
// title: msg.customerProfile.name,
customer_name: msg?.customerProfile?.name || '',
whatsapp_name: msg?.customerProfile?.name || '',
whatsapp_phone_number: msg.from,
whatsapp_msg_type: msg.type,
statusCN: msgStatusRenderMappedCN[msg?.status || 'failed'],
statusTitle: msgStatusRenderMappedCN[msg?.status || 'failed'],
...((isEmpty(msg.context) && isEmpty(msg.reaction)) || msg.context?.forwarded === true || isEmpty(msg.messageorigin)
? {}
: {
reply: {
/**
* reply: { message: msg.messageorigin, title: 'React from', titleColor: '#1ba784' }
*/
title: msg.messageorigin?.customerProfile?.name || 'me',
...(typeof whatsappMsgTypeMapped[(msg?.messageorigin?.type || 'unsupported')]?.renderForReply === 'function'
? whatsappMsgTypeMapped[(msg?.messageorigin?.type || 'unsupported')].renderForReply(msg.messageorigin)
: {}),
titleColor: msg.messageorigin?.customerProfile?.name ? '#a791ff' : "#128c7e",
},
origin: msg.context,
}),
};
};
/**
* 从数据库读取的记录
*/
export const parseRenderMessageList = (messages, conversationid = null) => {
return messages.map((msg, i) => {
let msgContentString = '';
if (typeof msg.msgtext_AsJOSN === 'string') {
// debug: json 缺少一部分
msgContentString = msg.msgtext_AsJOSN.charAt(msg.msgtext_AsJOSN.length - 1) !== '}' ? msg.msgtext_AsJOSN + '}}' : msg.msgtext_AsJOSN;
}
const msgContent = typeof msg.msgtext_AsJOSN === 'string' ? JSON.parse(msgContentString) : msg.msgtext_AsJOSN;
msgContent.template = msg.msgtype === 'template' ? { ...msgContent.template, ...msg.template_AsJOSN } : {};
const msgType = Object.keys(whatsappMsgTypeMapped).includes(msgContent.type) ? msgContent.type : 'unsupported';
// const parseMethod = msgContent.bizType === 'whatsapp' ? cloneDeep(whatsappMsgTypeMapped) : {};
return {
...msg,
msgOrigin: msgContent,
...(whatsappMsgTypeMapped?.[msgType]?.data(msgContent) || {}),
type: msgContent.type,
...(typeof whatsappMsgTypeMapped[msgType].type === 'function' ? whatsappMsgTypeMapped[msgType].type(msg) : { type: whatsappMsgTypeMapped[msgType].type || 'text' }),
date: msgContent?.sendTime || msg.msgtime || '',
localDate: (msg.msgtime || '').replace('T', ' '),
from: msgContent.from,
sender: msgContent.from,
senderName: msgContent?.customerProfile?.name || msgContent.from,
...(msg.msg_direction === 'outbound'
? {
sender: 'me',
senderName: 'me',
status: msgStatusRenderMapped[msgContent?.status || 'failed'],
dateString: msgStatusRenderMapped[msgContent?.status || 'failed'] === 'failed' ? `${(msg.msgtime || '').replace('T', ' ')} 发送失败 ${whatsappError?.[msgContent.errorCode] || msgContent.errorMessage}` : '',
statusCN: msgStatusRenderMappedCN[msgContent?.status || 'failed'],
statusTitle: msgStatusRenderMappedCN[msgContent?.status || 'failed'],
}
: {}),
...((isEmpty(msg.messageorigin_AsJOSN) && isEmpty(msgContent.context))
// ...((isEmpty(msg.messageorigin_AsJOSN) || isEmpty(msgContent.context))
? {}
: {
reply: {
message: msg.messageorigin_AsJOSN?.text?.body || msg.messageorigin_AsJOSN?.text,
title: msg.messageorigin_AsJOSN?.customerProfile?.name || msg.messageorigin_AsJOSN?.senderName || 'me',
...(typeof whatsappMsgTypeMapped[(msg.messageorigin_AsJOSN?.type || 'unsupported')]?.renderForReply === 'function'
? whatsappMsgTypeMapped[(msg.messageorigin_AsJOSN?.type || 'unsupported')].renderForReply(msg.messageorigin_AsJOSN)
: {}),
// titleColor: msg.messageorigin_AsJOSN?.customerProfile?.name ? '#a791ff' : "#128c7e",
titleColor: msg.messageorigin_direction === 'inbound' ? '#a791ff' : "#128c7e",
},
origin: msg.messageorigin_AsJOSN,
}),
// conversationid: conversationid,
// title: msg.customerProfile.name,
whatsapp_msg_type: msgContent.type,
};
});
};
export const whatsappError = {
'131026': '[131026] 消息无法投递(未注册/使用旧版/未同意政策).',
'131047': '[131047] 会话超过24小时.',
'131053': '[131053] 文件上传失败.',
'131048': '[131048] 账户被风控.', // 消息发送太多, 达到垃圾数量限制
'131031': '[131031] 账户已锁定.',
};
/**
* 系统弹窗通知
*/
export const handleNotification = (title, _options) => {
var notification;
const options = {
requireInteraction: true, // 设置手动关闭
// tag: 'global-sales-notification', // 通知ID同类通知建议设置相同ID避免通知过多遮挡桌面
// icon: '/favicon.ico', // 通知图标
..._options,
};
// 检查用户是否同意接受通知
if (Notification.permission === 'granted') {
notification = new Notification(title, options);
} else if (Notification.permission !== 'denied') {
Notification.requestPermission().then(function (permission) {
if (permission === 'granted') {
notification = new Notification(title, options);
}
});
} else {
// 用户拒绝通知权限
}
// 通知弹窗被点击后的回调
if (typeof notification !== 'undefined') {
notification.onclick = () => {
// window.parent.parent.focus();
window.focus(); // 显示当前标签页
notification.close(); // 关闭通知,适用于设置了手动关闭的通知
};
}
};