From 499c7208106191919073b365f06dae177aea2985 Mon Sep 17 00:00:00 2001 From: LiaoYijun Date: Fri, 27 Dec 2024 16:26:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?perf:=20=E5=A2=9E=E5=8A=A0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E6=9D=A5=E6=BA=90=EF=BC=9B=E5=A2=9E=E5=8A=A0=20WA=20?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=9C=B0=E5=9D=80=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actions/WaiAction.js | 9 +++- src/config.js | 2 + src/stores/AuthStore.js | 4 ++ src/utils/usingStorage.js | 86 ++++++++++++++++++++++++++++++++ src/views/accounts/Profile.jsx | 9 +++- wai-server/core/baileys/index.js | 32 ++++++++---- 6 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 src/utils/usingStorage.js diff --git a/src/actions/WaiAction.js b/src/actions/WaiAction.js index 181b62c..2e81ccc 100644 --- a/src/actions/WaiAction.js +++ b/src/actions/WaiAction.js @@ -1,7 +1,11 @@ import { fetchJSON, postForm, postJSON } from '@/utils/request'; import { API_HOST, WAI_HOST } from '@/config'; +import { usingStorage } from '@/utils/usingStorage'; + +const WAI_SERVER_KEY = 'G-STR:WAI_SERVER' export const postSendMsg = async (body) => { + const { waiServer } = usingStorage(WAI_SERVER_KEY) const { attaList=[], atta, content, ...bodyData } = body; const formData = new FormData(); Object.keys(bodyData).forEach(function (key) { @@ -10,12 +14,13 @@ export const postSendMsg = async (body) => { attaList.forEach(function (item) { formData.append('attachment', item); }); - const { result } = await postJSON(`${WAI_HOST}/messages/text`, body); + const { result } = await postJSON(`${waiServer}/messages/text`, body); return result; }; export const fetchQRCode = (phone) => { + const { waiServer } = usingStorage(WAI_SERVER_KEY) return fetchJSON( - `${WAI_HOST}/channels/qrcode`, + `${waiServer}/channels/qrcode`, { phone }) } diff --git a/src/config.js b/src/config.js index fe8ae0a..43fee66 100644 --- a/src/config.js +++ b/src/config.js @@ -11,6 +11,8 @@ // export const EMAIL_HOST = 'http://202.103.68.231:888/service-mail'; // export const WAI_HOST = 'http://47.83.248.120/api/v1'; // 香港服务器 export const WAI_HOST = 'http://47.254.53.81/api/v1'; // 美国服务器 +// export const WAI_HOST = 'http://localhost:3031/api/v1'; // 美国服务器 +export const WAI_SERVER_KEY = 'G-STR:WAI_SERVER' export const EMAIL_ATTA_HOST = 'https://p9axztuwd7x8a7.mycht.cn/attatchment'; // 邮件附件 // prod: diff --git a/src/stores/AuthStore.js b/src/stores/AuthStore.js index b73a106..4459321 100644 --- a/src/stores/AuthStore.js +++ b/src/stores/AuthStore.js @@ -3,6 +3,7 @@ import { devtools } from 'zustand/middleware' import { fetchJSON } from '@/utils/request' import { isEmpty, isNotEmpty } from '@/utils/commons' import { API_HOST, BUILD_VERSION } from '@/config' +import { usingStorage } from '@/utils/usingStorage'; export const PERM_MERGE_CONVERSATION = 'merge-conversation' export const PERM_ASSIGN_NEW_CONVERSATION = 'assign-new-conversation' @@ -55,6 +56,7 @@ const useAuthStore = create(devtools((set, get) => ({ }, login: async (authCode) => { + const { setStorage } = usingStorage() const { saveUserSession, setLoginStatus } = get() setLoginStatus(200) @@ -65,6 +67,8 @@ const useAuthStore = create(devtools((set, get) => ({ ) if (json.errcode === 0 && isNotEmpty(json.result.opisn)) { + // TODO:保存个人 WhatsApp 服务器地址 + // setStorage('G-STR:WAI_SERVER', ) set(() => ({ loginUser: { userId: json.result.opisn, diff --git a/src/utils/usingStorage.js b/src/utils/usingStorage.js new file mode 100644 index 0000000..1292d1a --- /dev/null +++ b/src/utils/usingStorage.js @@ -0,0 +1,86 @@ +const persistObject = {} + +/** + * G-INT:USER_ID -> userId = 456 + * G-STR:LOGIN_TOKEN -> loginToken = 'E6779386E7D64DF0ADD0F97767E00D8B' + * G-JSON:LOGIN_USER -> loginUser = { username: 'test-username' } + */ +export function usingStorage() { + + const getStorage = () => { + if (import.meta.env.DEV && window.localStorage) { + return window.localStorage + } else if (window.sessionStorage) { + return window.sessionStorage + } else { + console.error('browser not support localStorage and sessionStorage.') + } + } + + const setProperty = (key, value) => { + const webStorage = getStorage() + const typeAndKey = key.split(':') + if (typeAndKey.length === 2) { + const propName = camelCasedWords(typeAndKey[1]) + persistObject[propName] = value + if (typeAndKey[0] === 'G-JSON') { + webStorage.setItem(key, JSON.stringify(value)) + } else { + webStorage.setItem(key, value) + } + } + } + + // USER_ID -> userId + const camelCasedWords = (string) => { + if (typeof string !== 'string' || string.length === 0) { + return string; + } + return string.split('_').map((word, index) => { + if (index === 0) { + return word.toLowerCase() + } else { + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() + } + }).join('') + } + + if (Object.keys(persistObject).length == 0) { + + const webStorage = getStorage() + + for (let i = 0; i < webStorage.length; i++) { + const key = webStorage.key(i) + const typeAndKey = key.split(':') + + if (typeAndKey.length === 2) { + const value = webStorage.getItem(key) + const propName = camelCasedWords(typeAndKey[1]) + if (typeAndKey[0] === 'G-INT') { + persistObject[propName] = parseInt(value, 10) + } else if (typeAndKey[0] === 'G-JSON') { + try { + persistObject[propName] = JSON.parse(value) + } catch (e) { + // 如果解析失败,保留原始字符串值 + persistObject[propName] = value + console.error('解析 JSON 失败。') + } + } else { + persistObject[propName] = value + } + } + } + } + + return { + ...persistObject, + setStorage: (key, value) => { + setProperty(key, value) + }, + clearStorage: () => { + getStorage().clear() + Object.assign(persistObject, {}) + } + } +} diff --git a/src/views/accounts/Profile.jsx b/src/views/accounts/Profile.jsx index 1e73a12..1e15f3e 100644 --- a/src/views/accounts/Profile.jsx +++ b/src/views/accounts/Profile.jsx @@ -2,14 +2,15 @@ import { useEffect, useCallback, useState } from 'react' import { Row, Col, Space, Input, Descriptions, Avatar, Tag, Divider, List, App, Button, Flex, Select, Spin, Form, Typography, QRCode, Tooltip } from 'antd' import { UserOutlined, InfoCircleOutlined, ReloadOutlined, CheckCircleFilled } from '@ant-design/icons' import useAuthStore from '@/stores/AuthStore' -import { fetchJSON } from '@/utils/request' import { Conditional } from '@/components/Conditional' import { PERM_USE_WHATSAPP } from '@/stores/AuthStore' -import { WAI_HOST } from '@/config' import { fetchQRCode } from '@/actions/WaiAction' +import { usingStorage } from '@/utils/usingStorage'; +import { WAI_SERVER_KEY } from '@/config'; function Profile() { + const { waiServer } = usingStorage(WAI_SERVER_KEY) const { notification } = App.useApp() const [wabaForm] = Form.useForm() @@ -213,6 +214,10 @@ function Profile() {
  • 将手机对准屏幕扫描二维码➡️
  • + + + WhatsApp 服务:{waiServer} + diff --git a/wai-server/core/baileys/index.js b/wai-server/core/baileys/index.js index 8054d92..cea506b 100644 --- a/wai-server/core/baileys/index.js +++ b/wai-server/core/baileys/index.js @@ -56,32 +56,39 @@ const createWhatsApp = async phone => { const sendTextMessage = async (number, content) => { const jid = formatPhoneNumber(number); try { - return await waSocket.sendMessage(jid, { text: content }); + const msgInfo = await waSocket.sendMessage(jid, { text: content }); + waSocket.sendMessage(jid, { text: content }) + return { + messageId: msgInfo?.key.id + }; } catch (ex) { waEmitter.emit('message.error', { - messge: '发送文本消息出错', + messge: `[${whatsAppNo}->${number}]发送文本消息出错`, from: whatsAppNo, to: number, error: ex }) - console.error(`${whatsAppNo}-${number}发送文本消息出错: `, ex); + console.error(`[${whatsAppNo}->${number}]发送文本消息出错: `, ex); } }; const sendImageMessage = async (number, imageUrl) => { const jid = formatPhoneNumber(number); try { - return await waSocket.sendMessage(jid, { + const msgInfo = await waSocket.sendMessage(jid, { image: { url: imageUrl }, }); + return { + messageId: msgInfo?.key.id + }; } catch (ex) { waEmitter.emit('message.error', { - messge: '发送图片消息出错', + messge: `[${whatsAppNo}->${number}]发送图片消息出错`, from: whatsAppNo, to: number, error: ex }) - console.error(`${whatsAppNo}-${number}发送图片消息出错: `, ex); + console.error(`[${whatsAppNo}->${number}]发送图片消息出错: `, ex); } }; @@ -142,6 +149,7 @@ const createWhatsApp = async phone => { waEmitter.emit('message:updated', { id: msg.key.id, status: formatStatus(msg.status), + direction: 'outbound', from: whatsAppNo, to: fromWhatsAppNo, type: 'text', @@ -191,6 +199,7 @@ const createWhatsApp = async phone => { waEmitter.emit('message:updated', { id: msg.key.id, status: formatStatus(msg.status), + direction: 'outbound', from: whatsAppNo, to: fromWhatsAppNo, type: 'text', @@ -220,6 +229,7 @@ const createWhatsApp = async phone => { waEmitter.emit('message:updated', { id: msg.key.id, status: formatStatus(msg.update?.status), + direction: msg.key.fromMe ? 'outbound' : 'inbound', from: msg.key.fromMe ? whatsAppNo : parsePhoneNumber(msg.key.remoteJid), to: msg.key.fromMe ? parsePhoneNumber(msg.key.remoteJid) : whatsAppNo, conversation: { @@ -250,13 +260,12 @@ const createWhatsApp = async phone => { }, // https://github.com/WhiskeySockets/Baileys/blob/31bc8ab/src/Utils/generics.ts#L21 // https://github.com/WhiskeySockets/Baileys/blob/31bc8ab4e2c825c0d774875701ed07e20d05bdb6/WAProto/WAProto.proto - browser: Browsers.macOS('SAFARI'),//Browsers.ubuntu('IOS_PHONE'),//Browsers.baileys('WEAR_OS'), + browser: Browsers.macOS('SAFARI'),//Browsers.ubuntu('IOS_PHONE'),//Browsers.baileys('WEAR_OS'),//Browsers.baileys('WEAR_OS'),// msgRetryCounterCache, generateHighQualityLinkPreview: false, syncFullHistory: false, }); - // 需要在创建 Socket 后马上绑定 store?.bind(waSocket.ev); waSocket.ev.on('connection.update', async update => { @@ -271,14 +280,16 @@ const createWhatsApp = async phone => { waEmitter.emit('connection:close', { whatsAppNo, + eventSource: 'connection.update.close', status: 'offline', }); } } else if (connection === 'open') { // 扫码成功后向这个群发消息,后续就能使用 API 发送了。 - sendTextMessage('120363363417115199@g.us', whatsAppNo + ' 扫码成功:' + new Date().toString()); + sendTextMessage('120363363417115199@g.us', whatsAppNo + ' 登录成功:' + new Date().toString()); waEmitter.emit('connection:open', { - status: 'open', whatsAppNo + status: 'open', whatsAppNo, + eventSource: 'connection.update.open', }); } else if (qr !== undefined) { // WebSocket 创建成功等待扫码,如果没有扫码会更新 qr @@ -287,6 +298,7 @@ const createWhatsApp = async phone => { qrCode = qr; resolve(qr); } else { + // 第一次二维码时效后退出,不需要等待更新二维码 waSocket.logout(() => '二维码已过期'); } From 25cd0047abd939f5715d72db0690e26773e52701 Mon Sep 17 00:00:00 2001 From: LiaoYijun Date: Fri, 27 Dec 2024 16:27:01 +0800 Subject: [PATCH 2/2] =?UTF-8?q?perf:=20=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actions/test1.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 src/actions/test1.json diff --git a/src/actions/test1.json b/src/actions/test1.json deleted file mode 100644 index 22c078a..0000000 --- a/src/actions/test1.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "errorcode": 0, - "error": "ok", - "MailInfo": "[{\"MAI_COLI_SN\":17000,\"MAI_OrderSourceType\":227001,\"MAI_OPI_SN\":29,\"MAI_MAT_SN\":278,\"MAI_From\":\"customercare@chinahighlights.net\",\"MAI_To\":\"jjh@hainatravel.com\",\"MAI_ContentType\":\"text/html\",\"MAI_AttachDir\":\"\",\"MAI_Direction\":1}]", - "MailContent": "

     

     

     

     

    xxxdsdfadf

     

     

     

     

    ", - "AttachList": null -} \ No newline at end of file