diff --git a/src/main.jsx b/src/main.jsx index f457424..b644f78 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -27,6 +27,7 @@ import ChatAssign from '@/views/Conversations/ChatAssign' import DingdingLogin from '@/views/dingding/Login' import DingdingQRCode from '@/views/dingding/QRCode' import DingdingAuthCode from '@/views/dingding/AuthCode' +import LocalWhatsAppQRCode from '@/views/accounts/LocalWhatsAppQRCode' import useAuthStore from '@/stores/AuthStore' import '@/assets/index.css' @@ -107,6 +108,7 @@ const router = createBrowserRouter([ { path: 'dingding/callback', element: }, { path: 'dingding/qr-code', element: }, { path: 'dingding/auth-code', element: }, + { path: 'whatsapp/qr-code', element: }, ], }, ]) diff --git a/src/utils/request.js b/src/utils/request.js index e13b98a..4d46a82 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -1,6 +1,8 @@ import { BUILD_VERSION } from '@/config' +// Ref: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch + const customHeaders = [] // 添加 HTTP Reuqest 自定义头部 diff --git a/src/views/accounts/LocalWhatsAppQRCode.jsx b/src/views/accounts/LocalWhatsAppQRCode.jsx new file mode 100644 index 0000000..a5639ff --- /dev/null +++ b/src/views/accounts/LocalWhatsAppQRCode.jsx @@ -0,0 +1,24 @@ +import { useEffect, useCallback, useState } from 'react' +import { Typography, App, Flex, Input, QRCode } from 'antd' +import { ReloadOutlined, CheckCircleFilled, ExclamationCircleFilled } from '@ant-design/icons' +import useAuthStore from '@/stores/AuthStore' +import { fetchQRCode } from '@/actions/WaiAction' +import useConversationStore from '@/stores/ConversationStore' +import { isEmpty } from '@/utils/commons' + +// 把 QR Code 转换成二维码,用来本地测试 WhatsApp Baileys 框架 +const LocalWhatsAppQRCode = () => { + + const [qrCode, setQRCode] = useState('expired') + + return ( + + WhatsApp Baileys + + { + setQRCode(e.target.value) + }}/> + + ) +} +export default LocalWhatsAppQRCode diff --git a/wai-server/core/baileys/index.js b/wai-server/core/baileys/index.js index 3446512..8756aec 100644 --- a/wai-server/core/baileys/index.js +++ b/wai-server/core/baileys/index.js @@ -240,6 +240,35 @@ const createWhatsApp = async phone => { } } + const handleTemplateMessage = async msgObj => { + const text = msgObj.message.templateMessage?.hydratedTemplate?.hydratedContentText; + + waEmitter.emit(emitEventName, { + id: msgObj.key.id, + externalId, + status: msgStatus, + direction: msgDirection, + from: msgFrom, + to: msgTo, + type: 'text', + text: { + body: text, + }, + conversation: { + type: conversationType, + name: groupSubject, + }, + customerProfile: { + id: decodeJid(msgObj.key.remoteJid), + name: msgObj.verifiedBizName || msgObj.pushName, + }, + whatsAppNo, + fromMe: msgObj.key.fromMe, + eventSource: serverConfig.name + '.messages.upsert.notify', + updateTime: formatTimestamp(msgObj.messageTimestamp), + }); + }; + const handleMessagesUpdate = async messageUpdate => { console.info('messages.update: ', JSON.stringify(messageUpdate, undefined, 2)); @@ -354,6 +383,7 @@ const createWhatsApp = async phone => { // 第一次一分钟,后面是 20 秒更新一次 if (qrCode === null) { qrCode = qr; + console.info('QR Code: ' + qrCode); waEmitter.emit('creds:update', { id: generateId(), qr, whatsAppNo, diff --git a/wai-server/core/baileys/local.js b/wai-server/core/baileys/local.js deleted file mode 100644 index 9924ec1..0000000 --- a/wai-server/core/baileys/local.js +++ /dev/null @@ -1,164 +0,0 @@ -const { - makeWASocket, - Browsers, - DisconnectReason, - fetchLatestBaileysVersion, - makeCacheableSignalKeyStore, - makeInMemoryStore, - useMultiFileAuthState, - delay, - downloadMediaMessage, - isJidUser -} = require('@whiskeysockets/baileys'); - -const { formatPhoneNumber, parsePhoneNumber, formatStatus, formatTimestamp } = require('./helper'); -const NodeCache = require('node-cache'); -const P = require('pino'); - -const logger = P({ timestamp: () => `,"time":"${new Date().toJSON()}"` }, P.destination('./wa-logs.txt')) -logger.level = 'trace' - -// external map to store retry counts of messages when decryption/encryption fails -// keep this out of the socket itself, so as to prevent a message decryption/encryption loop across socket restarts -const msgRetryCounterCache = new NodeCache() - -// the store maintains the data of the WA connection in memory -// can be written out to a file & read from it -const store = makeInMemoryStore({ logger }) -store?.readFromFile('./baileys_store_multi.json') -// save every 10s -setInterval(() => { - store?.writeToFile('./baileys_store_multi.json') -}, 10_000) - -// start a connection -const startSock = async() => { - const { state, saveCreds } = await useMultiFileAuthState('baileys_auth_info') - // fetch latest version of WA Web - const { version, isLatest } = await fetchLatestBaileysVersion() - console.log(`using WA v${version.join('.')}, isLatest: ${isLatest}`) - - const sock = makeWASocket({ - version, - logger, - auth: { - creds: state.creds, - /** caching makes the store faster to send/recv messages */ - keys: makeCacheableSignalKeyStore(state.keys, logger), - }, - msgRetryCounterCache, - generateHighQualityLinkPreview: true, - }) - - store?.bind(sock.ev) - - - const sendMessageWTyping = async(msg, jid) => { - await sock.presenceSubscribe(jid) - await delay(500) - - await sock.sendPresenceUpdate('composing', jid) - await delay(2000) - - await sock.sendPresenceUpdate('paused', jid) - - await sock.sendMessage(jid, msg) - } - - - return new Promise((resolve) => { - // the process function lets you process all events that just occurred - // efficiently in a batch - sock.ev.process( - // events is a map for event name => event data - async(events) => { - // something about the connection changed - // maybe it closed, or we received all offline message or connection opened - if(events['connection.update']) { - const update = events['connection.update'] - const { connection, qr, lastDisconnect } = update - if(connection === 'close') { - // reconnect if not logged out - if((lastDisconnect?.error)?.output?.statusCode !== DisconnectReason.loggedOut) { - startSock() - } else { - console.log('Connection closed. You are logged out.') - } - } - if (connection === 'open') { - console.log('Connection open.') - - // setInterval(async() => { - - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - const charactersLength = characters.length; - for (let i = 0; i < 10; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - sendMessageWTyping({ text: result + "-local.Connection open 发送:" + new Date().toString() }, '8613317835586@s.whatsapp.net') - // }, 1000*60*5) - - - - setInterval(async() => { - - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - const charactersLength = characters.length; - for (let i = 0; i < 10; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - sendMessageWTyping({ text: result + "-setInterval(5min) 发送:" + new Date().toString() }, '8613317835586@s.whatsapp.net') - }, 1000*60*5) - } - - if (qr !== undefined) { - resolve(qr); - } - - console.log('connection update', update) - } - - if(events['creds.update']) { - await saveCreds() - } - - // received a new message - if(events['messages.upsert']) { - const upsert = events['messages.upsert'] - console.log('recv messages ', JSON.stringify(upsert, undefined, 2)) - - if(upsert.type === 'notify') { - for (const msg of upsert.messages) { - - if (msg.message?.conversation || msg.message?.extendedTextMessage?.text) { - const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text - if (text == "requestPlaceholder" && !upsert.requestId) { - const messageId = await sock.requestPlaceholderResend(msg.key) - console.log('requested placeholder resync, id=', messageId) - } else if (upsert.requestId) { - console.log('Message received from phone, id=', upsert.requestId, msg) - } - } - } - } - } - - - } - ) - - - - }) - - //return sock -} - -async function go() { -const qrCode = await startSock(); -console.info('qr code: ', qrCode); -} - -go(); \ No newline at end of file diff --git a/wai-server/core/baileys/test.js b/wai-server/core/baileys/test.js deleted file mode 100644 index adb8e1f..0000000 --- a/wai-server/core/baileys/test.js +++ /dev/null @@ -1,100 +0,0 @@ -function generateRandomString(length) { - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - const charactersLength = characters.length; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; -} - -const start = () => { - - setInterval(() => { - - const randomString = generateRandomString(10); - var myHeaders = new Headers(); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Accept", "*/*"); - myHeaders.append("Host", "wai-server-01-qq4qmtq7wc9he4.chinahighlights.cn"); - myHeaders.append("Connection", "keep-alive"); - - var raw = JSON.stringify({ - "from": "8618777396951", - "to": "8613557032060", - "content": randomString + "-setInterval(2min) 发送:" + new Date().toString() - }); - - var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' - }; - - fetch("http://wai-server-01-qq4qmtq7wc9he4.chinahighlights.cn/api/v1/channels/send", requestOptions) - .then(rsp => rsp.json()) - .then(json => console.info('8613557032060: ', json)) - .catch(ex => console.error(ex)); - }, 1000*60*2); - // - - setInterval(() => { - - const randomString = generateRandomString(10); - var myHeaders = new Headers(); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Accept", "*/*"); - myHeaders.append("Host", "wai-server-01-qq4qmtq7wc9he4.chinahighlights.cn"); - myHeaders.append("Connection", "keep-alive"); - - var raw = JSON.stringify({ - "from": "8618777396951", - "to": "8613317835586", - "content": randomString + "-setInterval(3min) 发送:" + new Date().toString() - }); - - var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' - }; - - fetch("http://wai-server-01-qq4qmtq7wc9he4.chinahighlights.cn/api/v1/channels/send", requestOptions) - .then(rsp => rsp.json()) - .then(json => console.info('8613317835586: ', json)) - .catch(ex => console.error(ex)); - }, 1000*60*3); - // - - setInterval(() => { - - const randomString = generateRandomString(10); - var myHeaders = new Headers(); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Accept", "*/*"); - myHeaders.append("Host", "wai-server-01-qq4qmtq7wc9he4.chinahighlights.cn"); - myHeaders.append("Connection", "keep-alive"); - - var raw = JSON.stringify({ - "from": "8618777396951", - "to": "8617607735120", - "content": randomString + "-setInterval(5min) 发送:" + new Date().toString() - }); - - var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' - }; - - fetch("http://wai-server-01-qq4qmtq7wc9he4.chinahighlights.cn/api/v1/channels/send", requestOptions) - .then(rsp => rsp.json()) - .then(json => console.info('8613317835586: ', json)) - .catch(ex => console.error(ex)); - }, 1000*60*5); -} - -start(); \ No newline at end of file diff --git a/wai-server/local.js b/wai-server/local.js new file mode 100644 index 0000000..8e3fe22 --- /dev/null +++ b/wai-server/local.js @@ -0,0 +1,8 @@ +const { createWhatsApp } = require('./core/baileys'); + +const go = async () => { + const whatsApp = await createWhatsApp('8618777396951'); + whatsApp.start(); +} + +go(); \ No newline at end of file