BiliveHeart

B站直播心跳

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

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