BilibiliExp

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

目前为 2019-12-01 提交的版本。查看 最新版本

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