hotcoin.test.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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 script = `
  64. let storage = JSON.parse(`${localStorage}`) || {}
  65. // for (const k in storage) {
  66. // localStorage.setItem(k,storage[k]);
  67. // }
  68. // `
  69. // script=`
  70. // localStorage.setItem('aaa','bbb')
  71. // `
  72. // await Runtime.evaluate({
  73. // expression: `localStorage.setItem('aaa','>>>>>')`,
  74. // // awaitPromise: true,
  75. // });
  76. for (const k in storage) {
  77. await Runtime.evaluate({
  78. expression: `localStorage.setItem(\`${k}\`, \`${storage[k]}\`)`,
  79. // awaitPromise: true,
  80. })
  81. }
  82. }
  83. }
  84. test('自动登录', async (done) => {
  85. await init()
  86. const {
  87. Storage,
  88. Page,
  89. Runtime,
  90. } = driver.cdp
  91. await Runtime.enable()
  92. try {
  93. Page.navigate({ url: baseUrl })
  94. await login(Storage, Runtime)
  95. let t = Date.now()
  96. let s = setInterval(async () => {
  97. let cookies = await Storage.getCookies()
  98. fs.writeFileSync(cookiesPath, JSON.stringify(cookies), 'utf8')
  99. let localStorage = await Runtime.evaluate({
  100. expression: 'JSON.stringify(localStorage)',
  101. })
  102. fs.writeFileSync(storagePath, localStorage?.result?.value, 'utf8')
  103. if (Date.now() - t > 5 * 60 * 1000) {
  104. clearInterval(s)
  105. }
  106. }, 1000)
  107. } catch (e) {
  108. console.error(e)
  109. }
  110. await sleep(timeout)
  111. done()
  112. }, timeout)
  113. test('监听wss', async (done) => {
  114. await init()
  115. const {
  116. Storage,
  117. Page,
  118. Network,
  119. Runtime,
  120. } = driver.cdp
  121. Network.enable()
  122. Page.navigate({ url: baseUrl })
  123. await login(Storage, Runtime)
  124. //这个网站连接了3个wss,不同的wss的数据结构不一样。
  125. // "wss://wsw.spentr.com/trade/multiple",
  126. //"wss://wcws.spentr.com/",
  127. const requestInfo = {}
  128. const decodeWsw = (data) => {
  129. let res = inflate(data/* , { to: 'string' } */)
  130. console.info('解压缩结果:', res)
  131. switch (res) {
  132. case 'pong':
  133. return res
  134. default: {
  135. let root = require('./hotconi/proto/market.v2.trade')
  136. let response = root.market.v2.trade.Response.decode(res)
  137. let object = response.toJSON()
  138. console.info('=decodeWsw==>', object)
  139. }
  140. }
  141. }
  142. const decodeWsws = (data) => {
  143. let res = inflate(data/* , { to: 'string' } */)
  144. //字符串中有2个隐藏字符 DCC EM
  145. let regex = new RegExp('(\\n|' + String.fromCharCode(18) + '|' + String.fromCharCode(31) + ')', 'g')
  146. let keys = inflate(data, { to: 'string' }).split(regex).filter(s => (s || '').includes('type.googleapis.com')).flatMap(s => (s || '').split('/'))
  147. // CommonPbMsg
  148. console.info('解压缩结果:', res)
  149. switch (res) {
  150. case 'pong':
  151. return res
  152. default: {
  153. let root = require('./hotconi/proto/' + (keys.join('.') || '').replaceAll('"', ''))
  154. let response = root.type.googleapis.com.Response.decode(res)
  155. let object = response.toJSON()
  156. console.info('=decodeWsws==>', object)
  157. }
  158. }
  159. }
  160. // 一个二进制数据流还原
  161. // opcode number WebSocket message opcode.
  162. // mask boolean WebSocket message mask.
  163. // payloadData string WebSocket message payload data. If the opcode is 1, this is a text message and payloadData is a UTF-8 string.
  164. // If the opcode isn't 1, then payloadData is a base64 encoded string representing binary data.
  165. const formatResponse = (params = {
  166. requestId: '',
  167. timestamp: '',
  168. response: {
  169. opcode: 1,
  170. mask: false,
  171. payloadData: '',
  172. },
  173. }) => {
  174. try {
  175. if (!params.response?.opcode) {
  176. return params.response
  177. }
  178. if (params.response?.opcode === 1) {
  179. return params.response.payloadData
  180. }
  181. const binaryData = Buffer.from(params.response.payloadData, 'base64')
  182. let res = {}
  183. if ((requestInfo[params.requestId].url || '').includes('wsw.spentr.com')) {
  184. res = decodeWsw(binaryData)
  185. } else if ((requestInfo[params.requestId].url || '').includes('wcws.spentr.com')) {
  186. res = decodeWsws(binaryData)
  187. }
  188. console.info('结果解析:', res)
  189. } catch (e) {
  190. console.error(e)
  191. }
  192. }
  193. Network.webSocketCreated(params => {
  194. console.log('WebSocket created:', params.url)
  195. requestInfo[params.requestId] = {
  196. url: params.url,
  197. }
  198. })
  199. // 订阅 Network.webSocketFrameSent 和 Network.webSocketFrameReceived 事件,监听 WebSocket 帧的发送和接收:
  200. // 订阅 Network.webSocketClosed 事件,监听 WebSocket 连接的关闭:
  201. Network.webSocketClosed(params => {
  202. console.log('WebSocket closed:', params.closeCode, params.closeReason)
  203. // formatResponse(params)
  204. })
  205. // 订阅Network.webSocketWillSendHandshakeRequest事件,拦截WebSocket连接的握手请求:
  206. Network.webSocketWillSendHandshakeRequest(params => {
  207. console.log('WebSocket handshake request:', params.request)
  208. // formatResponse(params)
  209. })
  210. // 订阅Network.webSocketHandshakeResponseReceived事件,拦截WebSocket连接的握手响应:
  211. Network.webSocketHandshakeResponseReceived(params => {
  212. console.log('WebSocket handshake response:', params.response)
  213. let data = formatResponse(params)
  214. console.info('webSocketHandshakeResponseReceived解析结果:', data)
  215. })
  216. // 订阅Network.webSocketFrameSent和Network.webSocketFrameReceived事件,拦截WebSocket数据帧的发送和接收:
  217. Network.webSocketFrameSent(params => {
  218. console.log('WebSocket frame sent:', params.response)
  219. // formatResponse(params)
  220. })
  221. Network.webSocketFrameReceived(params => {
  222. // console.log('WebSocket frame received:', params.response)
  223. console.info('webSocketFrameReceived 回复', params.response)
  224. let data = formatResponse(params)
  225. console.info('webSocketFrameReceived解析结果:', data)
  226. })
  227. Network.webSocketFrameError(params => {
  228. console.log('WebSocket frame error:', params.response)
  229. // let data = formatResponse(params)
  230. // console.info('webSocketFrameError解析结果:', data)
  231. })
  232. await sleep(timeout)
  233. done()
  234. }, timeout)
  235. test('拆包', done => {
  236. let str = `0{"sid":"b6535f35-3047-4f12-9cd3-c526b39cd019","upgrades":["websocket"],"pingInterval":30000,"pingTimeout":60000}`
  237. while (/^\d/.test(str)) {
  238. str = str.substring(1)
  239. }
  240. try {
  241. JSON.parse(str)
  242. } catch (e) {
  243. console.error(e)
  244. }
  245. console.info(str)
  246. done()
  247. })
  248. })