BilibiliExp

B 站经验助手,自动投币视频、模拟移动端分享、经验获取统计、升级时间估计

  1. // ==UserScript==
  2. // @name BilibiliExp
  3. // @namespace BilibiliExp
  4. // @match *://www.bilibili.com/video/*
  5. // @match *://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png*
  6. // @version 1.4.0
  7. // @author Dreace
  8. // @license GPL-3.0
  9. // @description B 站经验助手,自动投币视频、模拟移动端分享、经验获取统计、升级时间估计
  10. // @grant GM.xmlHttpRequest
  11. // @grant GM.setValue
  12. // @grant GM.getValue
  13. // @grant GM.deleteValue
  14. // @grant unsafeWindow
  15. // @require https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js
  16. // @require https://cdn.bootcss.com/blueimp-md5/1.1.0/js/md5.min.js
  17. // ==/UserScript==
  18.  
  19. "use strict";
  20. if (
  21. location.href.match(
  22. "www.mcbbs.net/template/mcbbs/image/special_photo_bg.png"
  23. ) &&
  24. location.href.match("access_key") &&
  25. window.opener
  26. ) {
  27. window.stop();
  28. document.children[0].innerHTML =
  29. '<title>BilibiliExp - 获取 Access Key</title><meta charset="UTF-8" name="viewport" content="width=device-width">正在跳转……';
  30. window.opener.postMessage("get_access_key: " + location.href, "*");
  31. return;
  32. }
  33. var coinUrl =
  34. "https://api.bilibili.com/x/web-interface/nav?build=0&mobi_app=web";
  35. var addCoinUrl = "https://api.bilibili.com/x/web-interface/coin/add";
  36. var shareUrl = "https://app.bilibili.com/x/v2/view/share/complete";
  37. var shareUrlPre = "https://app.bilibili.com/x/v2/view/share/click";
  38. var rewardUrl = "https://account.bilibili.com/home/reward";
  39. var aid = null;
  40. var expToday = 0;
  41. var bili_jct = getCookie("bili_jct");
  42. try {
  43. aid = unsafeWindow.vd ? unsafeWindow.vd.aid : unsafeWindow.aid;
  44. } catch (error) {
  45. console.error("[BilibiliExp] aid 获取失败");
  46. return;
  47. }
  48. var access_key = GM.getValue("access_key");
  49. if (access_key) {
  50. checkKeyStatus(access_key);
  51. } else {
  52. getKey();
  53. }
  54. var first = true;
  55. (function (open) {
  56. XMLHttpRequest.prototype.open = function () {
  57. this.addEventListener(
  58. "readystatechange",
  59. function () {
  60. if (
  61. this.responseURL.indexOf("api.bilibili.com/x/v2/dm/web/view") >= 0 &&
  62. first
  63. ) {
  64. first = false;
  65. main();
  66. }
  67. },
  68. false
  69. );
  70. open.apply(this, arguments);
  71. };
  72. })(XMLHttpRequest.prototype.open);
  73.  
  74. async function main() {
  75. while (document.querySelector(".dm").innerHTML.indexOf("-") !== -1) {
  76. await wait(1000);
  77. }
  78. if (!aid) {
  79. return;
  80. }
  81. var res = await gmAjax({
  82. url: rewardUrl,
  83. method: "GET",
  84. });
  85. if (res.code === 0) {
  86. if (res.data.level_info.current_level === 6) {
  87. console.log("[BilibiliExp] 已达到六级");
  88. injectHTML();
  89. return;
  90. }
  91. expToday = 50 - res.data.coins_av;
  92. // 暂时移除
  93. if (false) {
  94. var shareData = {
  95. access_key: access_key.key,
  96. actionKey: "appkey",
  97. aid: aid,
  98. build: "10300",
  99. device: "phone",
  100. epid: "",
  101. from: "64",
  102. mobi_app: "iphone",
  103. platform: "ios",
  104. s_locale: "zh-Hans_CN",
  105. share_channel: "wechat",
  106. share_trace_id: md5(new Date()),
  107. type: "av",
  108. statistics:
  109. "statistics=%7B%22appId%22%3A1%2C%22version%22%3A%226.10.0%22%2C%22abtest%22%3A%22%22%2C%22platform%22%3A1%7D",
  110. };
  111. var signed = get_sign(shareData, "c2ed53a74eeefe3cf99fbd01d8c9c375");
  112. res = await gmAjax({
  113. method: "POST",
  114. url: shareUrlPre,
  115. data: signed.data + "&sign=" + signed.sign,
  116. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  117. });
  118. res = await gmAjax({
  119. method: "POST",
  120. url: shareUrl,
  121. data: signed.data + "&sign=" + signed.sign,
  122. headers: {
  123. "Content-Type": "application/x-www-form-urlencoded",
  124. },
  125. });
  126. console.log("[BilibiliExp] " + res.data.toast);
  127. }
  128. } else {
  129. console.error("[BilibiliExp] 等级信息获取失败");
  130. return;
  131. }
  132. res = await biliAjax({
  133. url: coinUrl,
  134. type: "GET",
  135. dataType: "json",
  136. });
  137. var totalCoin = res.data.money;
  138. console.log("[BilibiliExp] 当前硬币 " + totalCoin + " 个");
  139. if (totalCoin < 50) {
  140. console.log("[BilibiliExp] 硬币小于 50,暂不投币");
  141. return;
  142. } else {
  143. if (expToday === 0) {
  144. console.log("[BilibiliExp] 今日已获取全部经验");
  145. return;
  146. }
  147. }
  148. console.log("[BilibiliExp] 准备投币");
  149. res = await addCoin();
  150. if (res && res.code === 0) {
  151. console.log("[BilibiliExp] 投了一个币");
  152. expToday -= 10;
  153. }
  154. if (expToday > 0) {
  155. res = await addCoin();
  156. if (res && res.code === 0) {
  157. console.log("[BilibiliExp] 投了一个币");
  158. expToday -= 10;
  159. }
  160. }
  161. await wait(3000);
  162. injectHTML();
  163. }
  164.  
  165. async function injectHTML() {
  166. var res = await gmAjax({
  167. url: rewardUrl,
  168. method: "GET",
  169. });
  170. if (res.code === 0) {
  171. var rewardInfo = res.data;
  172. var link = document.createElement("link");
  173. link.rel = "stylesheet";
  174. link.href = "//at.alicdn.com/t/font_1537779_4srood2g1uk.css";
  175. document.body.appendChild(link);
  176. var style = "color: inherit;display: inline-block;line-height: 1;";
  177. var spansData = [];
  178. var total = 0;
  179. total += rewardInfo.login ? 5 : 0;
  180. spansData.push({
  181. ok: rewardInfo.login,
  182. name: "每日登录",
  183. text: (rewardInfo.login ? 5 : 0) + "/5",
  184. className: "icon-login",
  185. });
  186. total += rewardInfo.share_av ? 5 : 0;
  187. spansData.push({
  188. ok: rewardInfo.share_av,
  189. name: "分享视频",
  190. text: (rewardInfo.share_av ? 5 : 0) + "/5",
  191. className: "icon-share",
  192. });
  193. total += rewardInfo.watch_av ? 5 : 0;
  194. spansData.push({
  195. ok: rewardInfo.watch_av,
  196. name: "观看视频",
  197. text: (rewardInfo.watch_av ? 5 : 0) + "/5",
  198. className: "icon-play",
  199. });
  200. total += rewardInfo.coins_av;
  201. spansData.push({
  202. ok: rewardInfo.coins_av === 50,
  203. name: "视频投币",
  204. text: rewardInfo.coins_av + "/50",
  205. className: "icon-coin",
  206. });
  207. spansData.push({
  208. ok: total === 65,
  209. name: "总计",
  210. text: total + "/65",
  211. className: "icon-total",
  212. });
  213. if (res.data.level_info.current_level === 6) {
  214. spansData.push({
  215. ok: true,
  216. name: "一个成熟的六级大佬",
  217. text: "六级辣",
  218. className: "icon-day",
  219. });
  220. } else {
  221. spansData.push({
  222. ok: false,
  223. name:
  224. "最快到 " + (rewardInfo.level_info.current_level + 1) + " 级剩余天数",
  225. text:
  226. Math.ceil(
  227. (rewardInfo.level_info.next_exp -
  228. rewardInfo.level_info.current_exp) /
  229. 65
  230. ) + " 天",
  231. className: "icon-day",
  232. });
  233. }
  234.  
  235. var bar = document.getElementById("arc_toolbar_report");
  236. bar.style.height = "60px";
  237. var ops = document.createElement("div");
  238. ops.className = "ops";
  239. spansData.forEach(function (item) {
  240. var span = document.createElement("span");
  241. if (item.ok) {
  242. span.style = "color:rgb(251, 114, 153);";
  243. } else {
  244. span.style = "color:rgb(80, 80, 80);";
  245. }
  246. span.title = item.name;
  247. span.innerHTML =
  248. '<i class="' +
  249. item.className +
  250. ' iconfont" style="' +
  251. style +
  252. '"></i>' +
  253. item.text;
  254. ops.appendChild(span.cloneNode(true));
  255. });
  256. ops.style.marginTop = "10px";
  257. bar.appendChild(ops);
  258. }
  259. }
  260. function addCoin() {
  261. return biliAjax({
  262. url: addCoinUrl,
  263. type: "POST",
  264. dataType: "json",
  265. data: {
  266. aid: aid,
  267. multiply: "1",
  268. select_like: 0,
  269. cross_domain: true,
  270. csrf: bili_jct,
  271. },
  272. });
  273. }
  274. function gmAjax(opt) {
  275. return new Promise(function (resolve, reject) {
  276. GM.xmlHttpRequest({
  277. method: opt.method,
  278. url: opt.url,
  279. data: opt.data ? opt.data : "",
  280. headers: opt.headers ? opt.headers : "",
  281. onload: function (response) {
  282. var res = JSON.parse(response.responseText);
  283. resolve(res);
  284. },
  285. onError: function (error) {
  286. reject(error);
  287. },
  288. });
  289. });
  290. }
  291. function wait(n) {
  292. return new Promise(function (resolve, reject) {
  293. setTimeout(function () {
  294. resolve();
  295. }, n);
  296. });
  297. }
  298. function empty() {
  299. return new Promise(function (resolve, reject) {
  300. resolve();
  301. });
  302. }
  303. function request(opt) {
  304. return $.ajax(opt);
  305. }
  306. function createPromise() {
  307. return $.Deferred();
  308. }
  309. function biliAjax(opt) {
  310. var req;
  311. opt.xhrFields = { withCredentials: true };
  312. opt.crossDomain = true;
  313. var defer = createPromise();
  314. req = request(opt);
  315. req.done(function (d) {
  316. defer.resolve(d);
  317. });
  318. req.fail(function (d) {
  319. console.log(d);
  320. defer.reject(d);
  321. });
  322. return defer;
  323. }
  324. function get_sign(params, key) {
  325. params.appkey = "27eb53fc9058f8c3";
  326. params.ts = Date.now();
  327. var s_keys = [];
  328. for (var i in params) {
  329. s_keys.push(i);
  330. }
  331. s_keys.sort();
  332. var data = "";
  333. for (var i = 0; i < s_keys.length; i++) {
  334. data += (data ? "&" : "") + s_keys[i] + "=" + params[s_keys[i]];
  335. }
  336. var sign = md5(data + key);
  337. return {
  338. sign: sign,
  339. data: data,
  340. signedData: data + "&sign=" + sign,
  341. };
  342. }
  343. function getCookie(cname) {
  344. var name = cname + "=";
  345. var ca = document.cookie.split(";");
  346. for (var i = 0; i < ca.length; i++) {
  347. var c = ca[i].trim();
  348. if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
  349. }
  350. return "";
  351. }
  352. function checkKeyStatus(access_key) {
  353. if (Date.now() - access_key.time > 25 * 24 * 3600000) {
  354. console.log("[BilibiliExp] access_key 即将过期");
  355. getKey();
  356. }
  357. }
  358. window.addEventListener("message", function (e) {
  359. if (typeof e.data == "string" && e.data.split(":")[0] == "get_access_key") {
  360. access_key_window.close();
  361. var url = e.data.split(": ")[1];
  362. var key = url.match(/access_key=([a-f0-9]{32})/);
  363. if (key) {
  364. var access_key = {
  365. key: key[1],
  366. time: Date.now(),
  367. };
  368. GM.setValue("access_key", access_key);
  369. console.log("[BilibiliExp] 成功获取 access_key: " + access_key.key);
  370. }
  371. }
  372. });
  373. function getKey() {
  374. var access_key_window = window.open("about:blank");
  375. access_key_window.document.title = "BilibiliExp - 获取 Access Key";
  376. access_key_window.document.body.innerHTML =
  377. '<meta charset="UTF-8" name="viewport" content="width=device-width">[BilibiliExp] 正在获取 Access Key';
  378. window.access_key_window = access_key_window;
  379. biliAjax({
  380. url: "https://passport.bilibili.com/login/app/third",
  381. type: "GET",
  382. dataType: "json",
  383. data: {
  384. appkey: "27eb53fc9058f8c3",
  385. api: "https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png",
  386. sign: "04224646d1fea004e79606d3b038c84a",
  387. },
  388. }).then(function (res) {
  389. if (res.data.has_login) {
  390. access_key_window.document.body.innerHTML =
  391. '<meta charset="UTF-8" name="viewport" content="width=device-width">[BilibiliExp] 正在跳转';
  392. access_key_window.location.href = res.data.confirm_uri;
  393. } else {
  394. access_key_window.close();
  395. console.error("[BilibiliExp] 必须登录 B 站才能获取 access_key");
  396. }
  397. });
  398. }