Greasy Fork is available in English.

BilibiliExp

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

// ==UserScript==
// @name        BilibiliExp
// @namespace   BilibiliExp
// @match       *://www.bilibili.com/video/*
// @match       *://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png*
// @version     1.4.0
// @author      Dreace
// @license     GPL-3.0
// @description B 站经验助手,自动投币视频、模拟移动端分享、经验获取统计、升级时间估计
// @grant       GM.xmlHttpRequest
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.deleteValue
// @grant       unsafeWindow
// @require     https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js
// @require     https://cdn.bootcss.com/blueimp-md5/1.1.0/js/md5.min.js
// ==/UserScript==

"use strict";
if (
  location.href.match(
    "www.mcbbs.net/template/mcbbs/image/special_photo_bg.png"
  ) &&
  location.href.match("access_key") &&
  window.opener
) {
  window.stop();
  document.children[0].innerHTML =
    '<title>BilibiliExp - 获取 Access Key</title><meta charset="UTF-8" name="viewport" content="width=device-width">正在跳转……';
  window.opener.postMessage("get_access_key: " + location.href, "*");
  return;
}
var coinUrl =
  "https://api.bilibili.com/x/web-interface/nav?build=0&mobi_app=web";
var addCoinUrl = "https://api.bilibili.com/x/web-interface/coin/add";
var shareUrl = "https://app.bilibili.com/x/v2/view/share/complete";
var shareUrlPre = "https://app.bilibili.com/x/v2/view/share/click";
var rewardUrl = "https://account.bilibili.com/home/reward";
var aid = null;
var expToday = 0;
var bili_jct = getCookie("bili_jct");
try {
  aid = unsafeWindow.vd ? unsafeWindow.vd.aid : unsafeWindow.aid;
} catch (error) {
  console.error("[BilibiliExp] aid 获取失败");
  return;
}
var access_key = GM.getValue("access_key");
if (access_key) {
  checkKeyStatus(access_key);
} else {
  getKey();
}
var first = true;
(function (open) {
  XMLHttpRequest.prototype.open = function () {
    this.addEventListener(
      "readystatechange",
      function () {
        if (
          this.responseURL.indexOf("api.bilibili.com/x/v2/dm/web/view") >= 0 &&
          first
        ) {
          first = false;
          main();
        }
      },
      false
    );
    open.apply(this, arguments);
  };
})(XMLHttpRequest.prototype.open);

async function main() {
  while (document.querySelector(".dm").innerHTML.indexOf("-") !== -1) {
    await wait(1000);
  }
  if (!aid) {
    return;
  }
  var res = await gmAjax({
    url: rewardUrl,
    method: "GET",
  });
  if (res.code === 0) {
    if (res.data.level_info.current_level === 6) {
      console.log("[BilibiliExp] 已达到六级");
      injectHTML();
      return;
    }
    expToday = 50 - res.data.coins_av;
    // 暂时移除
    if (false) {
      var shareData = {
        access_key: access_key.key,
        actionKey: "appkey",
        aid: aid,
        build: "10300",
        device: "phone",
        epid: "",
        from: "64",
        mobi_app: "iphone",
        platform: "ios",
        s_locale: "zh-Hans_CN",
        share_channel: "wechat",
        share_trace_id: md5(new Date()),
        type: "av",
        statistics:
          "statistics=%7B%22appId%22%3A1%2C%22version%22%3A%226.10.0%22%2C%22abtest%22%3A%22%22%2C%22platform%22%3A1%7D",
      };
      var signed = get_sign(shareData, "c2ed53a74eeefe3cf99fbd01d8c9c375");
      res = await gmAjax({
        method: "POST",
        url: shareUrlPre,
        data: signed.data + "&sign=" + signed.sign,
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
      });
      res = await gmAjax({
        method: "POST",
        url: shareUrl,
        data: signed.data + "&sign=" + signed.sign,
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
      });
      console.log("[BilibiliExp] " + res.data.toast);
    }
  } else {
    console.error("[BilibiliExp] 等级信息获取失败");
    return;
  }
  res = await biliAjax({
    url: coinUrl,
    type: "GET",
    dataType: "json",
  });
  var totalCoin = res.data.money;
  console.log("[BilibiliExp] 当前硬币 " + totalCoin + " 个");
  if (totalCoin < 50) {
    console.log("[BilibiliExp] 硬币小于 50,暂不投币");
    return;
  } else {
    if (expToday === 0) {
      console.log("[BilibiliExp] 今日已获取全部经验");
      return;
    }
  }
  console.log("[BilibiliExp] 准备投币");
  res = await addCoin();
  if (res && res.code === 0) {
    console.log("[BilibiliExp] 投了一个币");
    expToday -= 10;
  }
  if (expToday > 0) {
    res = await addCoin();
    if (res && res.code === 0) {
      console.log("[BilibiliExp] 投了一个币");
      expToday -= 10;
    }
  }
  await wait(3000);
  injectHTML();
}

async function injectHTML() {
  var res = await gmAjax({
    url: rewardUrl,
    method: "GET",
  });
  if (res.code === 0) {
    var rewardInfo = res.data;
    var link = document.createElement("link");
    link.rel = "stylesheet";
    link.href = "//at.alicdn.com/t/font_1537779_4srood2g1uk.css";
    document.body.appendChild(link);
    var style = "color: inherit;display: inline-block;line-height: 1;";
    var spansData = [];
    var total = 0;
    total += rewardInfo.login ? 5 : 0;
    spansData.push({
      ok: rewardInfo.login,
      name: "每日登录",
      text: (rewardInfo.login ? 5 : 0) + "/5",
      className: "icon-login",
    });
    total += rewardInfo.share_av ? 5 : 0;
    spansData.push({
      ok: rewardInfo.share_av,
      name: "分享视频",
      text: (rewardInfo.share_av ? 5 : 0) + "/5",
      className: "icon-share",
    });
    total += rewardInfo.watch_av ? 5 : 0;
    spansData.push({
      ok: rewardInfo.watch_av,
      name: "观看视频",
      text: (rewardInfo.watch_av ? 5 : 0) + "/5",
      className: "icon-play",
    });
    total += rewardInfo.coins_av;
    spansData.push({
      ok: rewardInfo.coins_av === 50,
      name: "视频投币",
      text: rewardInfo.coins_av + "/50",
      className: "icon-coin",
    });
    spansData.push({
      ok: total === 65,
      name: "总计",
      text: total + "/65",
      className: "icon-total",
    });
    if (res.data.level_info.current_level === 6) {
      spansData.push({
        ok: true,
        name: "一个成熟的六级大佬",
        text: "六级辣",
        className: "icon-day",
      });
    } else {
      spansData.push({
        ok: false,
        name:
          "最快到 " + (rewardInfo.level_info.current_level + 1) + " 级剩余天数",
        text:
          Math.ceil(
            (rewardInfo.level_info.next_exp -
              rewardInfo.level_info.current_exp) /
              65
          ) + " 天",
        className: "icon-day",
      });
    }

    var bar = document.getElementById("arc_toolbar_report");
    bar.style.height = "60px";
    var ops = document.createElement("div");
    ops.className = "ops";
    spansData.forEach(function (item) {
      var span = document.createElement("span");
      if (item.ok) {
        span.style = "color:rgb(251, 114, 153);";
      } else {
        span.style = "color:rgb(80, 80, 80);";
      }
      span.title = item.name;
      span.innerHTML =
        '<i class="' +
        item.className +
        ' iconfont" style="' +
        style +
        '"></i>' +
        item.text;
      ops.appendChild(span.cloneNode(true));
    });
    ops.style.marginTop = "10px";
    bar.appendChild(ops);
  }
}
function addCoin() {
  return biliAjax({
    url: addCoinUrl,
    type: "POST",
    dataType: "json",
    data: {
      aid: aid,
      multiply: "1",
      select_like: 0,
      cross_domain: true,
      csrf: bili_jct,
    },
  });
}
function gmAjax(opt) {
  return new Promise(function (resolve, reject) {
    GM.xmlHttpRequest({
      method: opt.method,
      url: opt.url,
      data: opt.data ? opt.data : "",
      headers: opt.headers ? opt.headers : "",
      onload: function (response) {
        var res = JSON.parse(response.responseText);
        resolve(res);
      },
      onError: function (error) {
        reject(error);
      },
    });
  });
}
function wait(n) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      resolve();
    }, n);
  });
}
function empty() {
  return new Promise(function (resolve, reject) {
    resolve();
  });
}
function request(opt) {
  return $.ajax(opt);
}
function createPromise() {
  return $.Deferred();
}
function biliAjax(opt) {
  var req;
  opt.xhrFields = { withCredentials: true };
  opt.crossDomain = true;
  var defer = createPromise();
  req = request(opt);
  req.done(function (d) {
    defer.resolve(d);
  });
  req.fail(function (d) {
    console.log(d);
    defer.reject(d);
  });
  return defer;
}
function get_sign(params, key) {
  params.appkey = "27eb53fc9058f8c3";
  params.ts = Date.now();
  var s_keys = [];
  for (var i in params) {
    s_keys.push(i);
  }
  s_keys.sort();
  var data = "";
  for (var i = 0; i < s_keys.length; i++) {
    data += (data ? "&" : "") + s_keys[i] + "=" + params[s_keys[i]];
  }
  var sign = md5(data + key);
  return {
    sign: sign,
    data: data,
    signedData: data + "&sign=" + sign,
  };
}
function getCookie(cname) {
  var name = cname + "=";
  var ca = document.cookie.split(";");
  for (var i = 0; i < ca.length; i++) {
    var c = ca[i].trim();
    if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
  }
  return "";
}
function checkKeyStatus(access_key) {
  if (Date.now() - access_key.time > 25 * 24 * 3600000) {
    console.log("[BilibiliExp] access_key 即将过期");
    getKey();
  }
}
window.addEventListener("message", function (e) {
  if (typeof e.data == "string" && e.data.split(":")[0] == "get_access_key") {
    access_key_window.close();
    var url = e.data.split(": ")[1];
    var key = url.match(/access_key=([a-f0-9]{32})/);
    if (key) {
      var access_key = {
        key: key[1],
        time: Date.now(),
      };
      GM.setValue("access_key", access_key);
      console.log("[BilibiliExp] 成功获取 access_key: " + access_key.key);
    }
  }
});
function getKey() {
  var access_key_window = window.open("about:blank");
  access_key_window.document.title = "BilibiliExp - 获取 Access Key";
  access_key_window.document.body.innerHTML =
    '<meta charset="UTF-8" name="viewport" content="width=device-width">[BilibiliExp] 正在获取 Access Key';
  window.access_key_window = access_key_window;
  biliAjax({
    url: "https://passport.bilibili.com/login/app/third",
    type: "GET",
    dataType: "json",
    data: {
      appkey: "27eb53fc9058f8c3",
      api: "https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png",
      sign: "04224646d1fea004e79606d3b038c84a",
    },
  }).then(function (res) {
    if (res.data.has_login) {
      access_key_window.document.body.innerHTML =
        '<meta charset="UTF-8" name="viewport" content="width=device-width">[BilibiliExp] 正在跳转';
      access_key_window.location.href = res.data.confirm_uri;
    } else {
      access_key_window.close();
      console.error("[BilibiliExp] 必须登录 B 站才能获取 access_key");
    }
  });
}