bliveproxy111

B站直播websocket hook框架

当前为 2022-04-23 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/443893/1043294/bliveproxy111.js

  1. // ==UserScript==
  2. // @name bliveproxy111
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4
  5. // @description B站直播websocket hook框架
  6. // @author xfgryujk
  7. // @include /https?:\/\/live\.bilibili\.com\/?\??.*/
  8. // @include /https?:\/\/live\.bilibili\.com\/\d+\??.*/
  9. // @include /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/
  10. // @run-at document-start
  11. // @require https://cdn.jsdelivr.net/gh/google/brotli@5692e422da6af1e991f9182345d58df87866bc5e/js/decode.js
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. // 使用方法:
  16. // bliveproxy.addCommandHandler('DANMU_MSG', command => {
  17. // console.log(command)
  18. // let info = command.info
  19. // info[1] = '测试'
  20. // })
  21.  
  22. (function() {
  23. const HEADER_SIZE = 16
  24.  
  25. const WS_BODY_PROTOCOL_VERSION_NORMAL = 0
  26. const WS_BODY_PROTOCOL_VERSION_HEARTBEAT = 1
  27. const WS_BODY_PROTOCOL_VERSION_BROTLI = 3
  28.  
  29. const OP_HEARTBEAT_REPLY = 3 // WS_OP_HEARTBEAT_REPLY
  30. const OP_SEND_MSG_REPLY = 5 // WS_OP_MESSAGE
  31. const OP_AUTH_REPLY = 8 // WS_OP_CONNECT_SUCCESS
  32.  
  33. let textEncoder = new TextEncoder()
  34. let textDecoder = new TextDecoder()
  35.  
  36. function main() {
  37. if (window.bliveproxy) {
  38. // 防止多次加载
  39. return
  40. }
  41. initApi()
  42. hook()
  43. }
  44.  
  45. function initApi() {
  46. window.bliveproxy = api
  47. }
  48.  
  49. let api = {
  50. addCommandHandler(cmd, handler) {
  51. let handlers = this._commandHandlers[cmd]
  52. if (!handlers) {
  53. handlers = this._commandHandlers[cmd] = []
  54. }
  55. handlers.push(handler)
  56. },
  57. removeCommandHandler(cmd, handler) {
  58. let handlers = this._commandHandlers[cmd]
  59. if (!handlers) {
  60. return
  61. }
  62. this._commandHandlers[cmd] = handlers.filter(item => item !== handler)
  63. },
  64.  
  65. // 私有API
  66. _commandHandlers: {},
  67. _getCommandHandlers(cmd) {
  68. return this._commandHandlers[cmd] || null
  69. }
  70. }
  71.  
  72. function hook() {
  73. window.WebSocket = new Proxy(window.WebSocket, {
  74. construct(target, args) {
  75. let obj = new target(...args)
  76. return new Proxy(obj, proxyHandler)
  77. }
  78. })
  79. }
  80.  
  81. let proxyHandler = {
  82. get(target, property) {
  83. let value = target[property]
  84. if ((typeof value) === 'function') {
  85. value = value.bind(target)
  86. }
  87. return value
  88. },
  89. set(target, property, value) {
  90. if (property === 'onmessage') {
  91. let realOnMessage = value
  92. value = function(event) {
  93. myOnMessage(event, realOnMessage)
  94. }
  95. }
  96. target[property] = value
  97. return value
  98. }
  99. }
  100.  
  101. function myOnMessage(event, realOnMessage) {
  102. if (!(event.data instanceof ArrayBuffer)) {
  103. realOnMessage(event)
  104. return
  105. }
  106.  
  107. let data = new Uint8Array(event.data)
  108. function callRealOnMessageByPacket(packet) {
  109. realOnMessage({...event, data: packet})
  110. }
  111. handleMessage(data, callRealOnMessageByPacket)
  112. }
  113.  
  114. function makePacketFromCommand(command) {
  115. let body = textEncoder.encode(JSON.stringify(command))
  116. return makePacketFromUint8Array(body, OP_SEND_MSG_REPLY)
  117. }
  118.  
  119. function makePacketFromUint8Array(body, operation) {
  120. let packLen = HEADER_SIZE + body.byteLength
  121. let packet = new ArrayBuffer(packLen)
  122.  
  123. // 不需要压缩
  124. let ver = operation === OP_HEARTBEAT_REPLY ? WS_BODY_PROTOCOL_VERSION_HEARTBEAT : WS_BODY_PROTOCOL_VERSION_NORMAL
  125. let packetView = new DataView(packet)
  126. packetView.setUint32(0, packLen) // pack_len
  127. packetView.setUint16(4, HEADER_SIZE) // raw_header_size
  128. packetView.setUint16(6, ver) // ver
  129. packetView.setUint32(8, operation) // operation
  130. packetView.setUint32(12, 1) // seq_id
  131.  
  132. let packetBody = new Uint8Array(packet, HEADER_SIZE, body.byteLength)
  133. for (let i = 0; i < body.byteLength; i++) {
  134. packetBody[i] = body[i]
  135. }
  136. return packet
  137. }
  138.  
  139. function handleMessage(data, callRealOnMessageByPacket) {
  140. let dataView = new DataView(data.buffer)
  141. let operation = dataView.getUint32(8)
  142.  
  143. switch (operation) {
  144. case OP_AUTH_REPLY:
  145. case OP_SEND_MSG_REPLY: {
  146. // 可能有多个包一起发,需要分包
  147. let offset = 0
  148. while (offset < data.byteLength) {
  149. let dataView = new DataView(data.buffer, offset)
  150. let packLen = dataView.getUint32(0)
  151. let rawHeaderSize = dataView.getUint16(4)
  152. let ver = dataView.getUint16(6)
  153. let operation = dataView.getUint32(8)
  154. // let seqId = dataView.getUint32(12)
  155.  
  156. let body = new Uint8Array(data.buffer, offset + rawHeaderSize, packLen - rawHeaderSize)
  157. if (operation === OP_SEND_MSG_REPLY) {
  158. // 业务消息
  159. switch (ver) {
  160. case WS_BODY_PROTOCOL_VERSION_NORMAL: {
  161. // body是单个JSON消息
  162. body = textDecoder.decode(body)
  163. body = JSON.parse(body)
  164. handleCommand(body, callRealOnMessageByPacket)
  165. break
  166. }
  167. case WS_BODY_PROTOCOL_VERSION_BROTLI: {
  168. // body是压缩过的多个消息
  169. body = BrotliDecode(body)
  170. handleMessage(body, callRealOnMessageByPacket)
  171. break
  172. }
  173. default: {
  174. // 未知的body格式
  175. let packet = makePacketFromUint8Array(body, operation)
  176. callRealOnMessageByPacket(packet)
  177. break
  178. }
  179. }
  180. } else {
  181. // 非业务消息
  182. let packet = makePacketFromUint8Array(body, operation)
  183. callRealOnMessageByPacket(packet)
  184. }
  185.  
  186. offset += packLen
  187. }
  188. break
  189. }
  190.  
  191. // 服务器心跳包,前4字节是人气值,后面是客户端发的心跳包内容
  192. // packLen不包括客户端发的心跳包内容,不知道是不是服务器BUG
  193. // 这里没用到心跳包就不处理了
  194. // case OP_HEARTBEAT_REPLY:
  195. default: {
  196. // 只有一个包
  197. let packLen = dataView.getUint32(0)
  198. let rawHeaderSize = dataView.getUint16(4)
  199.  
  200. let body = new Uint8Array(data.buffer, rawHeaderSize, packLen - rawHeaderSize)
  201. let packet = makePacketFromUint8Array(body, operation)
  202. callRealOnMessageByPacket(packet)
  203. break
  204. }
  205. }
  206. }
  207.  
  208. function handleCommand(command, callRealOnMessageByPacket) {
  209. if (command instanceof Array) {
  210. for (let oneCommand of command) {
  211. this.handleCommand(oneCommand)
  212. }
  213. return
  214. }
  215.  
  216. let cmd = command.cmd || ''
  217. let pos = cmd.indexOf(':')
  218. if (pos != -1) {
  219. cmd = cmd.substr(0, pos)
  220. }
  221. let handlers = api._getCommandHandlers(cmd)
  222. if (handlers) {
  223. for (let handler of handlers) {
  224. handler(command)
  225. }
  226. }
  227. // console.log(command)
  228.  
  229. let packet = makePacketFromCommand(command)
  230. callRealOnMessageByPacket(packet)
  231. }
  232.  
  233. main()
  234. })();