|
|
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);
|