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/channel/bubbleMsgUtils.js

1003 lines
39 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, fixTo2Decimals, pick, objectMapper } from "@/utils/commons";
import dayjs from "dayjs";
import { v4 as uuid } from "uuid";
function removeFirstPlus(str) {
if (str.startsWith('+')) {
return str.slice(1);
}
return str;
}
export const WABAccounts = [
{
"id": "217973041403372",
"phoneNumber": "+8618174165365",
"wabaId": "190290134156880",
"verifiedName": "Global Highlights Multilanguage",
"qualityRating": "GREEN",
"messagingLimit": "TIER_1K",
"isOfficialBusinessAccount": false,
"codeVerificationStatus": "EXPIRED",
"status": "CONNECTED",
"displayPhoneNumber": "+86 181 7416 5365",
"nameStatus": "APPROVED",
"newNameStatus": "NONE"
},
{
"id": "160079783860667",
"phoneNumber": "+8617607730395",
"wabaId": "190290134156880",
"verifiedName": "Global Highlights",
"qualityRating": "GREEN",
"messagingLimit": "TIER_1K",
"isOfficialBusinessAccount": false,
"codeVerificationStatus": "EXPIRED",
"status": "CONNECTED",
"displayPhoneNumber": "+86 176 0773 0395",
"nameStatus": "APPROVED",
"newNameStatus": "NONE",
"decision": "DEFERRED",
"requestedVerifiedName": "Global Highlights",
"rejectionReason": "NONE"
}
];
export const WABAccountsMapped = WABAccounts.reduce((a, c) => ({ ...a, [removeFirstPlus(c.phoneNumber)]: c, [c.phoneNumber]: c }), {})
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,
externalId: msg.externalId,
to: msg.to,
from: msg.from,
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,
externalId: msg.externalId,
to: msg.to,
from: msg.from,
msgtype: 'text',
msgcontent: { body: msg.text, preview_url: true, ...(msg.context ? { context: msg.context, message_origin: msg.message_origin?.msgOrigin || msg.message_origin } : {}) },
}),
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,
externalId: msg.externalId,
to: msg.to,
from: msg.from,
msgtype: 'template',
msgcontent: {
...msg.template,
components: [
...msg.template.components.filter((com) => !['footer', 'buttons'].includes(com.type.toLowerCase())),
...(msg.template.components.filter((com) => 'buttons' === com.type.toLowerCase()).length > 0
? msg.template.components
.filter((com) => 'buttons' === com.type.toLowerCase())[0]
// .buttons.filter((btns) => ! ['phone_number', 'url'].includes( btns.type.toLowerCase()))
.buttons.filter((btns) => !isEmpty(btns.example)) // 静态按钮不发
.map((btn, btnI) => ({
type: 'button',
sub_type: btn.type.toLowerCase(),
index: btnI,
// parameters: [{ text: 'lq1FTtA8', type: 'text' }]
}))
: []),
],
},
}),
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,
template: {
...msg.template,
components: [
...msg.template.components.filter((com) => !['footer', 'buttons'].includes(com.type.toLowerCase())),
...(msg.template.components.filter((com) => 'buttons' === com.type.toLowerCase()).length > 0
? msg.template.components
.filter((com) => 'buttons' === com.type.toLowerCase())[0]
// .buttons.filter((btns) => ! ['phone_number', 'url'].includes( btns.type.toLowerCase()))
.buttons.filter((btns) => !isEmpty(btns.example)) // 静态按钮不发
.map((btn, btnI) => ({
type: 'button',
sub_type: btn.type.toLowerCase(),
index: btnI,
// parameters: [{ text: 'lq1FTtA8', type: 'text' }]
}))
: []),
],
},
actionId: msg.id,
conversationid: msg.id.split('.')[0],
type: 'text',
title: msg.template.name, // || msg.template_origin.components.header?.[0]?.text || '',
text: autoLinkText(templateDataMapped?.body?.text || ''), // msg.template_origin.components.body?.[0]?.text || '',
whatsapp_msg_type: 'template',
};
},
},
email: {
type: 'email',
// contentToSend: (msg) => ({
// action: 'message',
// actionId: msg.id,
// renderId: msg.id,
// to: msg.to,
// msgtype: 'email',
// msgcontent: { body: msg.text, preview_url: true, },
// }),
contentToRender: (msg) => ({
...msg,
whatsapp_msg_type: '',
actionId: msg.id,
conversationid: msg.conversationid,
originText: msg.text,
text: (msg.text),
msgtext: {
...msg,
},
msgOrigin: {
...msg,
},
}),
},
};
const whatsappMsgMapped = {
'whatsapp.inbound_message.received': {
getMsg: (result) => {
// console.log('whatsapp.inbound_message.received', result);
const data1 = pick(result, ['conversationid', 'opi_sn', 'coli_sn', 'coli_id'])
return isEmpty(result?.whatsappInboundMessage) ? null : { ...result.whatsappInboundMessage, ...data1, messageorigin: result.messageorigin, msg_source: 'WABA', msg_direction: 'inbound' }
},
contentToRender: (contentObj) => {
// console.log('whatsapp.inbound_message.received to render', contentObj);
return parseRenderMessageItem(contentObj)
},
contentToUpdate: () => null,
},
'whatsapp.message.updated': {
getMsg: (result) => {
// console.log('getMsg', result);
return isEmpty(result?.whatsappMessage)
? null
: { ...result.whatsappMessage, conversationid: result.conversationid, messageorigin: result.messageorigin, msg_source: 'WABA', msg_direction: 'outbound' }
},
contentToRender: (contentObj) => {
if (contentObj?.status === 'failed' && ['130472', 'BAD_REQUEST'].includes(contentObj.errorCode)) {
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 || ''}` : '',
}),
},
'wai.message.received': {
getMsg: (result) => {
const data1 = pick(result, ['conversationid', 'opi_sn', 'coli_sn', 'coli_id'])
return isEmpty(result?.waiMessage)
? null
: { ...result.waiMessage, ...data1, messageorigin: result.messageorigin, msg_source: 'wai', ...objectMapper(result.waiMessage, { direction: { key: 'msg_direction' } }) }
},
contentToRender: (contentObj) => {
return parseRenderMessageItem(contentObj)
},
contentToUpdate: () => null,
},
'wai.message.updated': {
getMsg: (result) => {
return isEmpty(result?.waiMessage)
? null
: {
...result.waiMessage,
conversationid: result.conversationid,
messageorigin: result.messageorigin,
msg_source: 'wai',
...objectMapper(result.waiMessage, { direction: { key: 'msg_direction' } }),
}
},
contentToRender: (contentObj) => {
if (contentObj?.status === 'failed') {
contentObj = {
...contentObj,
type: 'error',
text: { body: `` }, // 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: msgcontent.msg_direction === 'outbound' ? msgStatusRenderMapped[msgcontent?.status || 'failed'] : '',
sender: msgcontent.msg_direction === 'outbound' ? 'me' : msgcontent?.customerProfile?.name || '',
dateString: msgcontent.status === 'failed' ? `发送失败 ❌` : '',
}),
},
'wai.creds.update': {
getMsg: (result) => {
return isEmpty(result?.waiMessage)
? {}
: { ...result.waiMessage, conversationid: result.conversationid, msg_source: 'wai', }
},
contentToRender: (contentObj) => null,
contentToUpdate: (msgcontent) => null,
contentToNotify: (contentObj) => {
return {
...contentObj,
status: contentObj?.status || '',
key: contentObj.to || '',
content: `WhatsApp号码: ${contentObj.to}`,
title: (contentObj.status === 'offline') ? `WhatsApp 断开连接` : '',
type: (contentObj.status === 'offline') ? 'warning' : 'info',
};
},
},
}
const emailMsgMapped = {
'email.inbound.received': {
getMsg: (result) => {
// console.log('email.inbound.received', result);
const data1 = pick(result, ['conversationid', 'opi_sn', 'coli_sn', 'coli_id']);
return isEmpty(result?.emailMessage) ? null : { ...result.emailMessage, ...data1, msg_source: 'email', msg_direction: 'inbound' };
},
contentToRender: (contentObj) => {
// console.log('email.inbound.received to render', contentObj);
return parseRenderMessageItem(contentObj);
},
contentToUpdate: (msgcontent) => null,
},
'email.updated': {
getMsg: (result) => {
// console.log('email.updated', result);
const { emailMessage } = result;
const data1 = pick(result, ['conversationid', 'opi_sn', 'coli_sn', 'coli_id']);
return isEmpty(result?.emailMessage) ? null : { ...emailMessage, ...data1, msg_source: 'email', msg_direction: 'outbound' };
},
contentToRender: (contentObj) => null,
contentToUpdate: (msgcontent) => ({
...msgcontent,
...parseRenderMessageItem({...msgcontent, }),
id: msgcontent.id,
status: msgStatusRenderMapped[(msgcontent?.status || 'failed')],
sender: 'me',
dateString: msgcontent.status==='failed' ? `发送失败 ❌` : '',
}),
},
'email.action.received': {
getMsg: (result) => {
return isEmpty(result?.emailMessage) ? null : { ...result.emailMessage, id: result.id };
},
contentToRender: (contentObj) => null,
contentToUpdate: (msgcontent) => null,
contentToNotify: (contentObj) => {
return {
...contentObj,
status: contentObj?.status || 'failed',
key: contentObj.email || contentObj.from || '',
content: `${contentObj.email || contentObj.from || '未知邮箱'} ${contentObj?.error?.message || ''}`,
title: (contentObj.status === 'failed') ? `接收邮件失败` : '',
type: (contentObj.status === 'failed') ? 'warning' : 'info',
};
},
}
}
export const msgStatusRenderMapped = {
'accepted': 'waiting', // 'sent', // 接口的发送请求
'sent': 'sent',
'delivered': 'received',
'read': 'read',
'failed': 'failed',
'send': 'sent',
};
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,
date: msgcontent.createTime,
sender: 'me',
}),
},
'error': {
// 发送消息的同步返回: 发送失败时
getMsg: (result) => result,
contentToRender: () => null,
contentToUpdate: (msgcontent) => {
let apiErrorCode,
apiErrorMsg = '';
const waCode = msgcontent.error.message.match(/\(#(\d+)\)/);
let waError = whatsappError?.[waCode?.[1]] || msgcontent.error.message;
apiErrorMsg = whatsappError[msgcontent.error.code];
if (msgcontent.error.message.includes('Invalid E.146 phone number')) {
waError = whatsappError.INVALID_PHONE_NUMBER;
apiErrorMsg = '';
}
return {
...msgcontent,
id: msgcontent.actionId,
status: msgcontent?.status || 'failed',
dateString: `发送失败 ${waError} \n[${msgcontent.error.code}] ${apiErrorMsg}`,
conversationid: msgcontent.actionId.split('.')[0],
};
},
},
...cloneDeep(emailMsgMapped),
};
/**
* 消息类型处理, 合并各渠道类型
* * WABA: error, system, text, image, video, audio, sticker, contact, location, document, template, interactive, order, list, button, reaction,
* * Email: email
*/
export const whatsappMsgTypeMapped = {
error: {
type: (_m) => ({ type: 'system' }),
data: (msg) => ({ id: msg.wamid, text: msg.errorCode ? msg.errorMessage : msg.text.body }),
renderForReply: (msg) => ({ id: msg.wamid, message: msg.errorCode ? msg.errorMessage : msg.text.body }),
},
system: {
type: 'system',
data: (msg) => ({ id: msg.wamid, text: msg.system?.body }),
renderForReply: (msg) => ({ id: msg.wamid, message: msg?.text?.body || msg?.text }),
},
text: {
type: 'text',
data: (msg) => ({ id: msg.wamid, text: autoLinkText(msg?.text?.body), originText: msg?.text?.body, title: msg?.customerProfile?.name || '', }), // msg?.from ||
renderForReply: (msg) => ({ id: msg.wamid, message: msg?.text?.body || msg?.text }),
},
image: {
type: 'photo',
data: (msg) => ({
id: msg.wamid,
text: msg.image?.caption,
onPhotoError: ({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src="https://hiana-crm.oss-accelerate.aliyuncs.com/WAMedia/afe412d4-3acf-4e79-a623-048aeb4d696a.png";
},
data: {
id: msg.wamid,
uri: msg.image?.link,
width: 'auto',
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: 'auto',
height: 200,
alt: msg.image?.caption || '',
message: 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,
text: msg.video.caption,
data: {
id: msg.wamid,
videoURL: msg.video.link,
status: {
click: true,
loading: 0,
download: true,
},
height: 200,
width: '100%',
},
}),
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,
audioProps: { preload: 'auto' },
data: {
audioURL: msg.audio.link,
audioType: msg.audio?.mime_type || 'audio/ogg',
controlsList: 'nofullscreen',
},
}),
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})`, dateString: `${dayjs(msg.sendTime).format('MM-DD HH:mm')} [ WhatsApp未提供消息内容 ] 可能是客人删除消息/会话, \n可询问客人截图/详细内容 或 忽略 📌` }),
renderForReply: (msg) => ({ id: msg?.wamid || msg.id, message: `[Message type unsupported](${msg.wamid})` }),
},
unresolvable: {
type: 'text',
data: (msg) => ({ id: msg.wamid, text: `[无法解析](${msg.wamid})`, }),
renderForReply: (msg) => ({ id: msg?.wamid || msg.id, message: `[无法解析](${msg.wamid})` }),
},
reaction: {
type: 'text',
data: (msg) => ({ id: msg.wamid, text: msg.reaction?.emoji || '' }),
renderForReply: (msg) => ({ id: msg.wamid, message: msg.reaction?.emoji || '' }),
},
document: {
type: 'file',
data: (msg) => ({
id: msg.wamid,
title: msg.document?.filename || '',
text: msg.document?.caption || msg.document?.filename || '',
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: '[联系人] ' + msg.contacts?.[0].name.formatted_name + '...',
}),
},
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`,
src: 'https://cdn.pixabay.com/photo/2016/03/22/04/23/map-1272165_1280.png',
href: `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}`
},
},
email: {
type: 'email',
data: (msg) => ({ id: msg.id || msg.uid, subject: msg.subject, }),
renderForReply: (msg) => {
const _msg = { ...msg, ...msg.email };
return {
id: _msg.id,
message: `[邮件] ${_msg.subject}`,
};
},
},
};
/**
* render received msg
*/
export const parseRenderMessageItem = (msg) => {
// console.log('parseRenderMessageItem', msg);
const thisMsgType = Object.keys(whatsappMsgTypeMapped).includes(msg.type) ? msg.type : 'unsupported';
return {
...msg,
opi_sn: msg.opi_sn || '',
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',
localDate: (msg?.sendTime || msg?.createTime || '').replace('T', ' '),
dateString: dayjs(msg?.sendTime || msg.createTime).format('MM-DD HH:mm'),
from: msg.from,
sender: msg.from,
senderName: msg.msg_direction === 'outbound' ? 'me' : msg?.customerProfile?.name || msg?.fromName || msg?.from || '',
customer_name: msg?.customerProfile?.name || '',
whatsapp_name: msg?.customerProfile?.name || '',
whatsapp_phone_number: isEmpty(msg?.customerProfile) ? msg.to : msg.from,
// whatsapp_msg_type: msg.msg_source==='WABA' ? msg.type : '',
// whatsapp_msg_type: (msg.msg_source || 'WABA') === 'WABA' ? msg.type : '', // 1.0接口没有msg_source
statusCN: msgStatusRenderMappedCN[msg?.status || 'failed'],
statusTitle: msgStatusRenderMappedCN[msg?.status || 'failed'],
replyButton: !['waiting', 'failed'].includes(msg?.status || '') ,
...((isEmpty(msg.context) && isEmpty(msg.reaction)) || msg.context?.forwarded === true // || isEmpty(msg.messageorigin)
? {}
: {
reply: {
/**
* reply: { message: msg.messageorigin, title: 'React from', titleColor: '#1ba784' }
*/
title: isEmpty(msg.messageorigin) ? '...' : 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 && msg.messageorigin?.sender !== 'me' ? '#a791ff' : "#128c7e",
id: msg.context?.id || msg.reaction?.message_id,
},
origin: msg.context,
}),
msg_source: msg?.msg_source || msg.type,
...((msg.msg_source) === 'WABA' ? {
whatsapp_msg_type: msg.type,
waba: msg.msg_direction === 'outbound' ? msg.from : msg.to,
wabaName: WABAccountsMapped[msg.msg_direction === 'outbound' ? msg.from : msg.to]?.verifiedName,
} : {
whatsapp_msg_type: '',
waba: '',
wabaName: '',
}),
};
};
/**
* 从数据库读取的记录
*/
export const parseRenderMessageList = (messages) => {
return messages.map((msg, i) => {
let msgContentString = '';
const msgtext = msg?.msgtext ?? msg?.msgtext_AsJOSN;
const messageorigin = msg?.messageorigin ?? msg?.messageorigin_AsJOSN;
const template = msg?.template_AsJOSN ?? msg?.template_AsJOSN_AsJOSN;
if (typeof msgtext === 'string') {
// debug: json 缺少一部分
msgContentString = msgtext.charAt(msgtext.length - 1) !== '}' ? msgtext + '}}' : msgtext;
// if (msg.msgtext.charAt(msg.msgtext.length - 1) === '"') {
// msgContentString = msg.msgtext + '}}';
// } else {
// msgContentString = msg.msgtext + '"}';
// }
}
const msgContent = typeof msgtext === 'string' ? JSON.parse(msgContentString) : (msgtext || {});
const msgType = isEmpty(msgContent) ? msg.msgtype : (Object.keys(whatsappMsgTypeMapped).includes(msgContent.type) ? msgContent.type : 'unresolvable')
msgContent.template = msg.msgtype === 'template' ? { ...msgContent.template, ...template } : {};
// const parseMethod = msgContent.bizType === 'whatsapp' ? cloneDeep(whatsappMsgTypeMapped) : {};
let waCode, waError = '';
if ((msgContent?.status || 'failed') === 'failed' && msgContent.errorMessage && msg.msg_direction === 'outbound') {
(waCode = msgContent.errorMessage.match(/\(#(\d+)\)/));
(waError = (whatsappError?.[waCode?.[1]] || whatsappError?.[msgContent.errorCode] || msgContent.errorMessage));
if (!isEmpty(msgContent.whatsappApiError)) {
waError = whatsappError?.[msgContent.whatsappApiError.code] || msgContent.whatsappApiError.message;
// waError += `\n[${msgContent.errorCode}] ${whatsappError?.[msgContent.errorCode] || msgContent.errorMessage}`;
}
if (msgContent.errorMessage.includes('Invalid E.146 phone number')) {
waError = whatsappError.INVALID_PHONE_NUMBER;
}
}
const msgTypeData = whatsappMsgTypeMapped?.[msgType]?.data(msgContent) || {};
return {
...msg,
msgOrigin: { ...msgContent, ...msgContent.email },
// id: msg.id || msgContent.wamid || msgContent.id,
...msgTypeData,
id: msgTypeData?.id || msg.sn,
type: msgContent.type,
...(typeof whatsappMsgTypeMapped[msgType].type === 'function' ? whatsappMsgTypeMapped[msgType].type(msg) : { type: whatsappMsgTypeMapped[msgType].type || 'text' }),
date: msg.msgtime, // msgContent?.sendTime || msg.msgtime || '',
dateText: dayjs(msg.msgtime).format('MM-DD HH:mm'),
dateString: dayjs(msg.msgtime).format('MM-DD HH:mm'),
localDate: (msg.msgtime || '').replace('T', ' '),
from: msgContent.from,
sender: msgContent.from,
senderName: msgContent?.customerProfile?.name || msgContent.from || 'me',
replyButton: !['waiting', 'failed'].includes(msgContent?.status || '') ,
// 用forwarded表示Resend, 与Reply互斥
forwarded: msg.msg_direction === 'outbound' && msg.msg_source === 'email' && ['email'].includes(msgContent.type) && (msgContent?.status || 'failed') === 'failed',
...(msg.msg_direction === 'outbound'
? {
sender: 'me',
senderName: 'me',
status: msgStatusRenderMapped[msgContent?.status || 'failed'],
dateString: msgStatusRenderMapped[msgContent?.status || 'failed'] === 'failed' ? `${(msg.msgtime || '').replace('T', ' ')} 发送失败 ${waError}` : '',
statusCN: msgStatusRenderMappedCN[msgContent?.status || 'failed'],
statusTitle: msgStatusRenderMappedCN[msgContent?.status || 'failed'],
id: (msgContent?.status || 'failed') === 'failed' ? (msgContent.actionId || msgContent.id) : (msgTypeData.id || msg.id || msg.sn),
actionId: msgContent.actionId,
replyButton: !['waiting', 'failed'].includes(msgContent?.status || 'failed') ,
}
: {}),
...(isEmpty(messageorigin) && (isEmpty(msgContent.context) || msgContent.context?.forwarded === true)
? // ...((isEmpty(msg.context) && isEmpty(msg.reaction)) || msg.context?.forwarded === true || isEmpty(messageorigin)
// ...((isEmpty(messageorigin) || isEmpty(msgContent.context))
{}
: {
reply: {
message: messageorigin?.text?.body || messageorigin?.text,
title: messageorigin?.customerProfile?.name || messageorigin?.senderName || 'me',
...(typeof whatsappMsgTypeMapped[messageorigin?.type || 'unsupported']?.renderForReply === 'function'
? whatsappMsgTypeMapped[messageorigin?.type || 'unsupported'].renderForReply(messageorigin)
: {}),
// titleColor: messageorigin?.customerProfile?.name ? '#a791ff' : '#128c7e',
titleColor: msg.messageorigin_direction === 'inbound' ? '#a791ff' : '#128c7e',
id: msgContent.context?.id || msgContent.context?.message_id || msgContent.reaction?.message_id || messageorigin?.wamid,
},
origin: messageorigin,
}),
// conversationid: conversationid,
// title: msg.customerProfile.name,
// whatsapp_msg_type: (msg.msg_source || 'WABA') === 'WABA' ? msgContent.type : '', // 1.0接口没有msg_source
whatsapp_msg_type: '',
waba: '',
wabaName: '',
...((msg.msg_source) === 'WABA' ? {
whatsapp_msg_type: msgContent.type,
waba: msg.msg_direction === 'outbound' ? msgContent.from : msgContent.to,
wabaName: WABAccountsMapped[msg.msg_direction === 'outbound' ? msgContent.from : msgContent.to]?.verifiedName,
} : {}),
...((msg.msg_source) === 'wai' ? {
whatsapp_msg_type: msgContent.type,
wabaName: '个人号',
} : {}),
}
});
};
export const whatsappError = {
'BAD_REQUEST': ' ',
'PARAM_INVALID': '参数错误, 请联系技术组',
'INTERNAL_SERVER_ERROR': '无法连接WhatsApp.\n请稍后重试',
'2': '[2] 无法连接WhatsApp.\n请稍后重试', // (#2) Service temporarily unavailable
'INVALID_PHONE_NUMBER': '无效号码, 请修正号码后重新从订单进入会话',
'100': '参数错误, 请联系技术组',
'FORBIDDEN': '[FORBIDDEN] ',
'4': '[4] 无法连接WhatsApp.\n请稍后重试', // (#4) Application request limit reached
'131026': '[131026] 消息无法投递(未同意WhatsApp 的隐私政策).\n请使用邮件联系',
'131047': '[131047] 会话未激活. \n请使用模板消息💬发送',
'131053': '[131053] 文件上传失败.',
'131048': '[131048] 账户被风控.', // 消息发送太多, 达到垃圾数量限制
'131049': '[131049] 号码触发风控. \n请暂停发送营销消息, 使用跟进模板\n或引导客户主动发起会话.', // 消息发送太多, 营销限制
'131031': '[131031] 账户已锁定.',
'130472': '[130472] 此号码不接收商业号消息\n请使用邮件联系 或 引导客户主动发起会话.',
};
export const whatsappSupportFileTypes = {
sticker: { types: ['image/webp'], size: 1024 * 100 },
photo: { types: ['image/jpeg', 'image/png'], size: 1024 * 1024 * 5 },
video: { types: ['video/mp4', 'video/3gp'], size: 1024 * 1024 * 16 },
document: {
types: [
'text/plain',
'application/pdf',
'application/vnd.ms-powerpoint',
'application/msword',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
],
size: 1024 * 1024 * 100,
},
audio: { types: ['audio/aac', 'audio/mp4', 'audio/mpeg', 'audio/amr', 'audio/ogg'], size: 1024 * 1024 * 16 },
};
/**
* 系统弹窗通知
*/
export const handleNotification = (title, _options) => {
if (!("Notification" in window)) {
// alert("This browser does not support desktop notification");
return false;
}
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(); // 关闭通知,适用于设置了手动关闭的通知
};
}
};
/**
* WhatsApp 国际号码
*
* - Make sure to remove any leading 0s or special calling codes.
* - All phone numbers in Argentina (country code "54") should have a "9" between the country code and area code. The prefix "15" must be removed so the final number will have 13 digits total: +54 9 XXX XXX XXXX
* - Phone numbers in Mexico (country code "52") need to have "1" after "+52", even if they're Nextel numbers.
* - 巴西号码: 13位的话删除国家地区码后面的`9`
* - UK: 号码部分10位, 前缀`7`补全
*/
export const phoneNumberToWAID = (input) => {
// Remove any non-digit characters
const cleanedInput = (input.replace(/[^\d]/g, ''))
.replace(/^0+/, '') // Remove leading zeros
;
// Check if the number starts with a valid country code
const countryCode = cleanedInput.slice(0, 2);
const isArgentina = countryCode === '54';
const isMexico = countryCode === '52';
const isBrazil = countryCode === '55';
const isUK = countryCode === '44';
// Remove leading 0s and special calling codes
let formattedNumber = cleanedInput.replace(/^0+/, '');
if (isArgentina) {
// Remove the prefix "15" if present
// formattedNumber = formattedNumber.replace(/^15/, '');
formattedNumber = formattedNumber.replace(/^54 ?15/, '54');
// Remove leading '0' after country code
formattedNumber = formattedNumber.replace(/^54 ?0/, '54');
// Insert "9" between the country code and area code
formattedNumber = `54 9 ${formattedNumber.slice(2)}`;
} else if (isMexico) {
// Remove leading '0' after country code
// formattedNumber = formattedNumber.replace(/^52 ?0/, '52');
// Insert "1" after the country code
// formattedNumber = `52 1 ${formattedNumber.slice(2)}`;
} else if (isBrazil) {
if (cleanedInput.length > 12) {
formattedNumber = cleanedInput.slice(0, 4) + cleanedInput.slice(-8);
}
} else if (isUK) {
if (cleanedInput.length < 12) {
formattedNumber = `44 7 ${cleanedInput.slice(2)}`;
}
}
// Remove any non-digit characters
formattedNumber = formattedNumber.replace(/[^\d]/g, '');
return formattedNumber;
}
export const uploadProgressSimulate = () => fixTo2Decimals(Math.random() * (0.8 - 0.2) + 0.2);