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/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": "
",
- "AttachList": null
-}
\ No newline at end of file
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(() => '二维码已过期');
}