BiliveHeart

B站直播心跳

目前为 2022-07-02 提交的版本,查看 最新版本

此脚本不应直接安装,它是供其他脚本使用的外部库。如果你需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/447321/1066416/BiliveHeart.js

  1. // ==UserScript==
  2. // @name BiliveHeart
  3. // @namespace https://github.com/lzghzr/TampermonkeyJS
  4. // @version 0.0.6
  5. // @author lzghzr
  6. // @description B站直播心跳
  7. // @include /^https?:\/\/live\.bilibili\.com\/(?:blanc\/)?\d/
  8. // @require https://greasyfork.org/scripts/441505-crypto-js4-1-1/code/crypto-js411.js?version=1028182
  9. // @license MIT
  10. // @grant none
  11. // ==/UserScript==
  12. class RoomHeart {
  13. constructor(t) {
  14. this.roomID = t
  15. }
  16. areaID;
  17. parentID;
  18. seq = 0;
  19. roomID;
  20. get id() {
  21. return [this.parentID, this.areaID, this.seq, this.roomID]
  22. }
  23. buvid = this.getItem("LIVE_BUVID");
  24. uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (t => {
  25. const e = 16 * Math.random() | 0;
  26. return ("x" === t ? e : 3 & e | 8).toString(16)
  27. }));
  28. device = [this.buvid, this.uuid];
  29. get ts() {
  30. return Date.now()
  31. }
  32. _patchData = {};
  33. get patchData() {
  34. const t = [];
  35. for (const[e, i]of Object.entries(this._patchData))
  36. t.push(i);
  37. return t
  38. }
  39. get isPatch() {
  40. return 0 === this.patchData.length ? 0 : 1
  41. }
  42. W = "undefined" == typeof unsafeWindow ? window : unsafeWindow;
  43. ua = this.W && this.W.navigator ? this.W.navigator.userAgent : "";
  44. csrf = this.getItem("bili_jct") || "";
  45. nextInterval = Math.floor(5) + Math.floor(55 * Math.random());
  46. heartBeatInterval;
  47. secretKey;
  48. secretRule;
  49. timestamp;
  50. lastHeartbeatTimestamp = Date.now();
  51. get watchTimeFromLastReport() {
  52. const t = Math.ceil(((new Date).getTime() - this.lastHeartbeatTimestamp) / 1e3);
  53. return t < 0 ? 0 : t > this.heartBeatInterval ? this.heartBeatInterval : t
  54. }
  55. start() {
  56. return this.getInfoByRoom()
  57. }
  58. doneFunc = function () {};
  59. async getInfoByRoom() {
  60. if (0 === this.roomID)
  61. return !1;
  62. const t = await fetch(`//api.live.bilibili.com/room/v1/Room/get_info?room_id=${this.roomID}&from=room`, {
  63. mode: "cors",
  64. credentials: "include"
  65. }).then((t => t.json()));
  66. return 0 === t.code && (({
  67. area_id: this.areaID,
  68. parent_area_id: this.parentID,
  69. room_id: this.roomID
  70. } = t.data), 0 !== this.areaID && 0 !== this.parentID && (this.e(), !0))
  71. }
  72. async webHeartBeat() {
  73. if (this.seq > 30)
  74. return;
  75. const t = `${this.nextInterval}|${this.roomID}|1|0`,
  76. e = CryptoJS.enc.Utf8.parse(t),
  77. i = CryptoJS.enc.Base64.stringify(e),
  78. s = await fetch(`//live-trace.bilibili.com/xlive/rdata-interface/v1/heartbeat/webHeartBeat?hb=${encodeURIComponent(i)}&pf=web`, {
  79. mode: "cors",
  80. credentials: "include"
  81. }).then((t => t.json()));
  82. 0 === s.code && (this.nextInterval = s.data.next_interval, setTimeout((() => this.webHeartBeat()), 1e3 * this.nextInterval))
  83. }
  84. async savePatchData() {
  85. if (this.seq > 30)
  86. return;
  87. const t = {
  88. id: JSON.stringify(this.id),
  89. device: JSON.stringify(this.device),
  90. ets: this.timestamp,
  91. benchmark: this.secretKey,
  92. time: this.watchTimeFromLastReport > this.heartBeatInterval ? this.heartBeatInterval : this.watchTimeFromLastReport,
  93. ts: this.ts,
  94. ua: this.ua
  95. },
  96. e = this.sypder(JSON.stringify(t), this.secretRule),
  97. i = Object.assign({
  98. s: e
  99. }, t);
  100. this._patchData[this.roomID] = i,
  101. setTimeout((() => this.savePatchData()), 15e3)
  102. }
  103. async e() {
  104. const t = {
  105. id: JSON.stringify(this.id),
  106. device: JSON.stringify(this.device),
  107. ts: this.ts,
  108. is_patch: 0,
  109. heart_beat: "[]",
  110. ua: this.ua
  111. },
  112. e = await fetch("//live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/E", {
  113. headers: {
  114. "content-type": "application/x-www-form-urlencoded"
  115. },
  116. method: "POST",
  117. body: `${this.json2str(t)}&csrf_token=${this.csrf}&csrf=${this.csrf}&visit_id=`,
  118. mode: "cors",
  119. credentials: "include"
  120. }).then((t => t.json()));
  121. 0 === e.code && (this.seq += 1, ({
  122. heartbeat_interval: this.heartBeatInterval,
  123. secret_key: this.secretKey,
  124. secret_rule: this.secretRule,
  125. timestamp: this.timestamp
  126. } = e.data), setTimeout((() => this.x()), 1e3 * this.heartBeatInterval))
  127. }
  128. async x() {
  129. if (this.seq > 30)
  130. return this.doneFunc();
  131. const t = {
  132. id: JSON.stringify(this.id),
  133. device: JSON.stringify(this.device),
  134. ets: this.timestamp,
  135. benchmark: this.secretKey,
  136. time: this.heartBeatInterval,
  137. ts: this.ts,
  138. ua: this.ua
  139. },
  140. e = this.sypder(JSON.stringify(t), this.secretRule),
  141. i = Object.assign({
  142. s: e
  143. }, t);
  144. this._patchData[this.roomID] = i,
  145. this.lastHeartbeatTimestamp = Date.now();
  146. const s = await fetch("//live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/X", {
  147. headers: {
  148. "content-type": "application/x-www-form-urlencoded"
  149. },
  150. method: "POST",
  151. body: `${this.json2str(i)}&csrf_token=${this.csrf}&csrf=${this.csrf}&visit_id=`,
  152. mode: "cors",
  153. credentials: "include"
  154. }).then((t => t.json()));
  155. 0 === s.code && (this.seq += 1, ({
  156. heartbeat_interval: this.heartBeatInterval,
  157. secret_key: this.secretKey,
  158. secret_rule: this.secretRule,
  159. timestamp: this.timestamp
  160. } = s.data), setTimeout((() => this.x()), 1e3 * this.heartBeatInterval))
  161. }
  162. sypder(t, e) {
  163. const i = JSON.parse(t),
  164. [s, a, r, n] = JSON.parse(i.id),
  165. [o, c] = JSON.parse(i.device),
  166. h = i.benchmark,
  167. m = {
  168. platform: "web",
  169. parent_id: s,
  170. area_id: a,
  171. seq_id: r,
  172. room_id: n,
  173. buvid: o,
  174. uuid: c,
  175. ets: i.ets,
  176. time: i.time,
  177. ts: i.ts
  178. };
  179. let d = JSON.stringify(m);
  180. for (const t of e)
  181. switch (t) {
  182. case 0:
  183. d = CryptoJS.HmacMD5(d, h).toString(CryptoJS.enc.Hex);
  184. break;
  185. case 1:
  186. d = CryptoJS.HmacSHA1(d, h).toString(CryptoJS.enc.Hex);
  187. break;
  188. case 2:
  189. d = CryptoJS.HmacSHA256(d, h).toString(CryptoJS.enc.Hex);
  190. break;
  191. case 3:
  192. d = CryptoJS.HmacSHA224(d, h).toString(CryptoJS.enc.Hex);
  193. break;
  194. case 4:
  195. d = CryptoJS.HmacSHA512(d, h).toString(CryptoJS.enc.Hex);
  196. break;
  197. case 5:
  198. d = CryptoJS.HmacSHA384(d, h).toString(CryptoJS.enc.Hex);
  199. break;
  200. default:
  201. break
  202. }
  203. return d
  204. }
  205. getItem(t) {
  206. return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(t).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || ""
  207. }
  208. json2str(t) {
  209. let e = "";
  210. for (const i in t)
  211. e += `${i}=${encodeURIComponent(t[i])}&`;
  212. return e.slice(0, -1)
  213. }
  214. }