hotcoin.test.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. const {
  2. describe,
  3. test,
  4. expect,
  5. } = require('@jest/globals')
  6. const { WebDriver } = require('../common/web_driver')
  7. const {
  8. inflate,
  9. deflate,
  10. } = require('pako')
  11. const fs = require('fs')
  12. const path = require('node:path')
  13. const root = require('./hotconi/proto/market.v2.trade')
  14. // Static code + Reflection + .proto parser
  15. const timeout = 9999999
  16. async function sleep (ms) {
  17. return new Promise(resolve => setTimeout(resolve, ms))
  18. }
  19. describe('hotcoin站点测试', () => {
  20. let targetInfo = {
  21. 'targetId': '',
  22. 'type': '',
  23. 'title': '',
  24. 'url': '',
  25. 'attached': true,
  26. 'canAccessOpener': false,
  27. 'browserContextId': '',
  28. }
  29. const driver = new WebDriver({
  30. // port: 23035,
  31. })
  32. const init = async () => {
  33. // await driver.setProxy('socks://localhost:8880')
  34. await driver.setProxy('http://172.16.1.132:3128')
  35. await driver.builder()
  36. let target = await driver.cdp.Target
  37. targetInfo = await target.getTargetInfo()
  38. console.info('info>>>>', targetInfo)
  39. }
  40. const baseUrl = 'https://www.hotcoin.com/'
  41. const prefix = 'hotcoin'
  42. let cookiesPath = path.join(__dirname, 'tmp', prefix + '_cookies')
  43. let storagePath = path.join(__dirname, 'tmp', prefix + '_storage')
  44. test('测试打开', async (done) => {
  45. await init()
  46. const page = driver.cdp.Page
  47. page.navigate({ url: baseUrl })
  48. done()
  49. }, timeout)
  50. const login = async (Storage, Runtime) => {
  51. let cookie = '', localStorage = ''
  52. if (fs.existsSync(cookiesPath)) {
  53. cookie = fs.readFileSync(cookiesPath, 'utf8')
  54. }
  55. // 借助localStorage中的jwt信息登录
  56. if (fs.existsSync(storagePath)) {
  57. localStorage = fs.readFileSync(storagePath, 'utf8')
  58. }
  59. if (cookie) {
  60. await Storage.setCookies(JSON.parse(cookie))
  61. }
  62. if (localStorage) {
  63. let storage = {}
  64. try {
  65. storage = JSON.parse(`${localStorage}`) ||{}
  66. } catch (e) {
  67. console.error(e)
  68. }
  69. // let script = `
  70. // for (const k in storage) {
  71. // localStorage.setItem(k,storage[k]);
  72. // }
  73. // `
  74. // script=`
  75. // localStorage.setItem('aaa','bbb')
  76. // `
  77. // await Runtime.evaluate({
  78. // expression: `localStorage.setItem('aaa','>>>>>')`,
  79. // // awaitPromise: true,
  80. // });
  81. for (const k in storage) {
  82. await Runtime.evaluate({
  83. expression: `localStorage.setItem(\`${k}\`, \`${storage[k]}\`)`,
  84. // awaitPromise: true,
  85. })
  86. }
  87. }
  88. }
  89. test('自动登录', async (done) => {
  90. await init()
  91. const {
  92. Storage,
  93. Page,
  94. Runtime,
  95. } = driver.cdp
  96. await Runtime.enable()
  97. try {
  98. Page.navigate({ url: baseUrl })
  99. await login(Storage, Runtime)
  100. let t = Date.now()
  101. let s = setInterval(async () => {
  102. let cookies = await Storage.getCookies()
  103. fs.writeFileSync(cookiesPath, JSON.stringify(cookies), 'utf8')
  104. let localStorage = await Runtime.evaluate({
  105. expression: 'JSON.stringify(localStorage)',
  106. })
  107. fs.writeFileSync(storagePath, localStorage?.result?.value, 'utf8')
  108. if (Date.now() - t > 5 * 60 * 1000) {
  109. clearInterval(s)
  110. }
  111. }, 1000)
  112. } catch (e) {
  113. console.error(e)
  114. }
  115. await sleep(timeout)
  116. done()
  117. }, timeout)
  118. test('监听wss', async (done) => {
  119. await init()
  120. const {
  121. Storage,
  122. Page,
  123. Network,
  124. Runtime,
  125. } = driver.cdp
  126. Network.enable()
  127. Page.navigate({ url: baseUrl })
  128. await login(Storage, Runtime)
  129. //这个网站连接了3个wss,不同的wss的数据结构不一样。
  130. // "wss://wsw.spentr.com/trade/multiple",
  131. //"wss://wcws.spentr.com/",
  132. const requestInfo = {}
  133. const decodeWsw = (data) => {
  134. let res = inflate(data/* , { to: 'string' } */)
  135. console.info('解压缩结果:', res)
  136. switch (res) {
  137. case 'pong':
  138. return res
  139. default: {
  140. let root = require('./hotconi/proto/market.v2.trade')
  141. let response = root.market.v2.trade.Response.decode(res)
  142. let object = response.toJSON()
  143. console.info('=decodeWsw==>', object)
  144. }
  145. }
  146. }
  147. const decodeWsws = (data) => {
  148. let res = inflate(data/* , { to: 'string' } */)
  149. //字符串中有2个隐藏字符 DCC EM
  150. let regex = new RegExp('(\\n|' + String.fromCharCode(18) + '|' + String.fromCharCode(30)+ '|' + String.fromCharCode(31) + ')', 'g')
  151. let keys = inflate(data, { to: 'string' }).split(regex).filter(s => (s || '').includes('type.googleapis.com')).flatMap(s => (s || '').split('/'))
  152. // CommonPbMsg
  153. console.info('解压缩结果:', res)
  154. switch (res) {
  155. case 'pong':
  156. return res
  157. default: {
  158. let root = require('./hotconi/proto/' + (keys.join('.') || '').replaceAll('"', ''))
  159. let response = root.type.googleapis.com[keys[1]].Response.decode(res)
  160. let object = response.toJSON()
  161. console.info('=decodeWsws==>', object)
  162. }
  163. }
  164. }
  165. // 一个二进制数据流还原
  166. // opcode number WebSocket message opcode.
  167. // mask boolean WebSocket message mask.
  168. // payloadData string WebSocket message payload data. If the opcode is 1, this is a text message and payloadData is a UTF-8 string.
  169. // If the opcode isn't 1, then payloadData is a base64 encoded string representing binary data.
  170. const formatResponse = (params = {
  171. requestId: '',
  172. timestamp: '',
  173. response: {
  174. opcode: 1,
  175. mask: false,
  176. payloadData: '',
  177. },
  178. }) => {
  179. try {
  180. if (!params.response?.opcode) {
  181. return params.response
  182. }
  183. if (params.response?.opcode === 1) {
  184. return params.response.payloadData
  185. }
  186. const binaryData = Buffer.from(params.response.payloadData, 'base64')
  187. let res = {}
  188. if ((requestInfo[params.requestId].url || '').includes('wsw.spentr.com')) {
  189. res = decodeWsw(binaryData)
  190. } else if ((requestInfo[params.requestId].url || '').includes('wcws.spentr.com')) {
  191. res = decodeWsws(binaryData)
  192. }
  193. console.info('结果解析:', res)
  194. } catch (e) {
  195. console.error(e)
  196. }
  197. }
  198. Network.requestWillBeSent((params) => {
  199. console.log('Request Headers:', params.request.headers);
  200. });
  201. Network.webSocketCreated(params => {
  202. console.log('WebSocket created:', params.url)
  203. requestInfo[params.requestId] = {
  204. url: params.url,
  205. }
  206. })
  207. // 订阅 Network.webSocketFrameSent 和 Network.webSocketFrameReceived 事件,监听 WebSocket 帧的发送和接收:
  208. // 订阅 Network.webSocketClosed 事件,监听 WebSocket 连接的关闭:
  209. Network.webSocketClosed(params => {
  210. console.log('WebSocket closed:', params.closeCode, params.closeReason)
  211. // formatResponse(params)
  212. })
  213. // 订阅Network.webSocketWillSendHandshakeRequest事件,拦截WebSocket连接的握手请求:
  214. Network.webSocketWillSendHandshakeRequest(params => {
  215. console.log('WebSocket handshake request:', params.request)
  216. // formatResponse(params)
  217. })
  218. // 订阅Network.webSocketHandshakeResponseReceived事件,拦截WebSocket连接的握手响应:
  219. Network.webSocketHandshakeResponseReceived(params => {
  220. console.log('WebSocket handshake response:', params.response)
  221. let data = formatResponse(params)
  222. console.info('webSocketHandshakeResponseReceived解析结果:', data)
  223. })
  224. // 订阅Network.webSocketFrameSent和Network.webSocketFrameReceived事件,拦截WebSocket数据帧的发送和接收:
  225. Network.webSocketFrameSent(params => {
  226. console.log('WebSocket frame sent:', params.response)
  227. // formatResponse(params)
  228. })
  229. Network.webSocketFrameReceived(params => {
  230. // console.log('WebSocket frame received:', params.response)
  231. console.info('webSocketFrameReceived 回复', params.response)
  232. let data = formatResponse(params)
  233. console.info('webSocketFrameReceived解析结果:', data)
  234. })
  235. Network.webSocketFrameError(params => {
  236. console.log('WebSocket frame error:', params.response)
  237. // let data = formatResponse(params)
  238. // console.info('webSocketFrameError解析结果:', data)
  239. })
  240. await sleep(timeout)
  241. done()
  242. }, timeout)
  243. test('拆包', done => {
  244. let str = `0{"sid":"b6535f35-3047-4f12-9cd3-c526b39cd019","upgrades":["websocket"],"pingInterval":30000,"pingTimeout":60000}`
  245. while (/^\d/.test(str)) {
  246. str = str.substring(1)
  247. }
  248. try {
  249. JSON.parse(str)
  250. } catch (e) {
  251. console.error(e)
  252. }
  253. console.info(str)
  254. done()
  255. })
  256. })