const { describe, test, expect, } = require('@jest/globals') const { WebDriver } = require('../common/web_driver') const { inflate, deflate, } = require('pako') const fs = require('fs') const path = require('node:path') const root = require('./hotconi/proto/market.v2.trade') // Static code + Reflection + .proto parser const timeout = 9999999 async function sleep (ms) { return new Promise(resolve => setTimeout(resolve, ms)) } describe('hotcoin站点测试', () => { let targetInfo = { 'targetId': '', 'type': '', 'title': '', 'url': '', 'attached': true, 'canAccessOpener': false, 'browserContextId': '', } const driver = new WebDriver({ // port: 23035, }) const init = async () => { // await driver.setProxy('socks://localhost:8880') await driver.setProxy('http://172.16.1.132:3128') await driver.builder() let target = await driver.cdp.Target targetInfo = await target.getTargetInfo() console.info('info>>>>', targetInfo) } const baseUrl = 'https://www.hotcoin.com/' const prefix = 'hotcoin' let cookiesPath = path.join(__dirname, 'tmp', prefix + '_cookies') let storagePath = path.join(__dirname, 'tmp', prefix + '_storage') test('测试打开', async (done) => { await init() const page = driver.cdp.Page page.navigate({ url: baseUrl }) done() }, timeout) const login = async (Storage, Runtime) => { let cookie = '', localStorage = '' if (fs.existsSync(cookiesPath)) { cookie = fs.readFileSync(cookiesPath, 'utf8') } // 借助localStorage中的jwt信息登录 if (fs.existsSync(storagePath)) { localStorage = fs.readFileSync(storagePath, 'utf8') } if (cookie) { await Storage.setCookies(JSON.parse(cookie)) } if (localStorage) { let storage = {} try { storage = JSON.parse(`${localStorage}`) ||{} } catch (e) { console.error(e) } // let script = ` // for (const k in storage) { // localStorage.setItem(k,storage[k]); // } // ` // script=` // localStorage.setItem('aaa','bbb') // ` // await Runtime.evaluate({ // expression: `localStorage.setItem('aaa','>>>>>')`, // // awaitPromise: true, // }); for (const k in storage) { await Runtime.evaluate({ expression: `localStorage.setItem(\`${k}\`, \`${storage[k]}\`)`, // awaitPromise: true, }) } } } test('自动登录', async (done) => { await init() const { Storage, Page, Runtime, } = driver.cdp await Runtime.enable() try { Page.navigate({ url: baseUrl }) await login(Storage, Runtime) let t = Date.now() let s = setInterval(async () => { let cookies = await Storage.getCookies() fs.writeFileSync(cookiesPath, JSON.stringify(cookies), 'utf8') let localStorage = await Runtime.evaluate({ expression: 'JSON.stringify(localStorage)', }) fs.writeFileSync(storagePath, localStorage?.result?.value, 'utf8') if (Date.now() - t > 5 * 60 * 1000) { clearInterval(s) } }, 1000) } catch (e) { console.error(e) } await sleep(timeout) done() }, timeout) test('监听wss', async (done) => { await init() const { Storage, Page, Network, Runtime, } = driver.cdp Network.enable() Page.navigate({ url: baseUrl }) await login(Storage, Runtime) //这个网站连接了3个wss,不同的wss的数据结构不一样。 // "wss://wsw.spentr.com/trade/multiple", //"wss://wcws.spentr.com/", const requestInfo = {} const decodeWsw = (data) => { let res = inflate(data/* , { to: 'string' } */) console.info('解压缩结果:', res) switch (res) { case 'pong': return res default: { let root = require('./hotconi/proto/market.v2.trade') let response = root.market.v2.trade.Response.decode(res) let object = response.toJSON() console.info('=decodeWsw==>', object) } } } const decodeWsws = (data) => { let res = inflate(data/* , { to: 'string' } */) //字符串中有2个隐藏字符 DCC EM let regex = new RegExp('(\\n|' + String.fromCharCode(18) + '|' + String.fromCharCode(30)+ '|' + String.fromCharCode(31) + ')', 'g') let keys = inflate(data, { to: 'string' }).split(regex).filter(s => (s || '').includes('type.googleapis.com')).flatMap(s => (s || '').split('/')) // CommonPbMsg console.info('解压缩结果:', res) switch (res) { case 'pong': return res default: { let root = require('./hotconi/proto/' + (keys.join('.') || '').replaceAll('"', '')) let response = root.type.googleapis.com[keys[1]].Response.decode(res) let object = response.toJSON() console.info('=decodeWsws==>', object) } } } // 一个二进制数据流还原 // opcode number WebSocket message opcode. // mask boolean WebSocket message mask. // payloadData string WebSocket message payload data. If the opcode is 1, this is a text message and payloadData is a UTF-8 string. // If the opcode isn't 1, then payloadData is a base64 encoded string representing binary data. const formatResponse = (params = { requestId: '', timestamp: '', response: { opcode: 1, mask: false, payloadData: '', }, }) => { try { if (!params.response?.opcode) { return params.response } if (params.response?.opcode === 1) { return params.response.payloadData } const binaryData = Buffer.from(params.response.payloadData, 'base64') let res = {} if ((requestInfo[params.requestId].url || '').includes('wsw.spentr.com')) { res = decodeWsw(binaryData) } else if ((requestInfo[params.requestId].url || '').includes('wcws.spentr.com')) { res = decodeWsws(binaryData) } console.info('结果解析:', res) } catch (e) { console.error(e) } } Network.requestWillBeSent((params) => { console.log('Request Headers:', params.request.headers); }); Network.webSocketCreated(params => { console.log('WebSocket created:', params.url) requestInfo[params.requestId] = { url: params.url, } }) // 订阅 Network.webSocketFrameSent 和 Network.webSocketFrameReceived 事件,监听 WebSocket 帧的发送和接收: // 订阅 Network.webSocketClosed 事件,监听 WebSocket 连接的关闭: Network.webSocketClosed(params => { console.log('WebSocket closed:', params.closeCode, params.closeReason) // formatResponse(params) }) // 订阅Network.webSocketWillSendHandshakeRequest事件,拦截WebSocket连接的握手请求: Network.webSocketWillSendHandshakeRequest(params => { console.log('WebSocket handshake request:', params.request) // formatResponse(params) }) // 订阅Network.webSocketHandshakeResponseReceived事件,拦截WebSocket连接的握手响应: Network.webSocketHandshakeResponseReceived(params => { console.log('WebSocket handshake response:', params.response) let data = formatResponse(params) console.info('webSocketHandshakeResponseReceived解析结果:', data) }) // 订阅Network.webSocketFrameSent和Network.webSocketFrameReceived事件,拦截WebSocket数据帧的发送和接收: Network.webSocketFrameSent(params => { console.log('WebSocket frame sent:', params.response) // formatResponse(params) }) Network.webSocketFrameReceived(params => { // console.log('WebSocket frame received:', params.response) console.info('webSocketFrameReceived 回复', params.response) let data = formatResponse(params) console.info('webSocketFrameReceived解析结果:', data) }) Network.webSocketFrameError(params => { console.log('WebSocket frame error:', params.response) // let data = formatResponse(params) // console.info('webSocketFrameError解析结果:', data) }) await sleep(timeout) done() }, timeout) test('拆包', done => { let str = `0{"sid":"b6535f35-3047-4f12-9cd3-c526b39cd019","upgrades":["websocket"],"pingInterval":30000,"pingTimeout":60000}` while (/^\d/.test(str)) { str = str.substring(1) } try { JSON.parse(str) } catch (e) { console.error(e) } console.info(str) done() }) })