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

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(); // 关闭通知,适用于设置了手动关闭的通知
};
}
};