BilibiliExp

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

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

// ==UserScript==
// @name        BilibiliExp
// @namespace   BilibiliExp
// @match       *://www.bilibili.com/video/*
// @match       *://link.acg.tv/forum.php*
// @version     1.1.1
// @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://static.hdslb.com/js/md5.js
// ==/UserScript==
// file:///C:/WorkSpace/JavaScript/BilibiliExp/main.js
(function () {
    'use strict';
    if (location.href.match('link.acg.tv/forum.php') && 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 totalCoin = 0;
    var expToday = 0;
    var aid = "";
    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();
    }
    if (aid) {
        gmAjax({
            url: rewardUrl,
            method: 'GET'
        }).then(function (res) {
            if (res.code == 0) {
                expToday = 50 - res.data.coins_av;
                if (!res.data.share_av && access_key) {
                    var shareData = {
                        access_key: access_key.key,
                        actionKey: "appkey",
                        aid: aid,
                        build: "8960",
                        device: "phone",
                        epid: "",
                        from: "711",
                        mobi_app: "iphone",
                        platform: "ios",
                        season_id: "",
                        share_channel: "qq",
                        share_trace_id: hex_md5(new Date()),
                        statistics: "%7B%22appId%22%3A1%2C%22version%22%3A%225.50.1%22%2C%22abtest%22%3A%22890%22%2C%22platform%22%3A1%7D"
                    };
                    var signed = get_sign(shareData, "c2ed53a74eeefe3cf99fbd01d8c9c375");
                    gmAjax({
                        method: "POST",
                        url: shareUrlPre,
                        data: signed.data + "&sign=" + signed.sign,
                        headers: { "Content-Type": "application/x-www-form-urlencoded" }
                    }).then(function (res) {
                        if (res.code == 0) {
                            return gmAjax({
                                method: "POST",
                                url: shareUrl,
                                data: signed.data + "&sign=" + signed.sign,
                                headers: { "Content-Type": "application/x-www-form-urlencoded" }
                            });
                        }
                    }).then(function (res) {
                        console.log("[BilibiliExp] " + res.data.toast);
                    });
                }
            } else {
                console.error("[BilibiliExp] 等级信息获取失败");
            }
        }).then(function () {
            return biliAjax({
                url: coinUrl,
                type: 'GET',
                dataType: 'json'
            });
        }).then(function (res) {
            totalCoin = res.data.money;
            console.log("[BilibiliExp] 当前硬币 " + totalCoin + " 个");
            if (totalCoin < 50) {
                console.log("[BilibiliExp] 硬币小于 50,暂不投币");
            } else {
                if (expToday == 0) {
                    console.log("[BilibiliExp] 今日已获取全部经验");
                }
                return new Promise(function (resolve, reject) {
                    setTimeout(function () {
                        resolve();
                    }, 10000);
                });
            }
        }).then(function () {
            if (totalCoin >= 50 && expToday > 0) {
                console.log("[BilibiliExp] 准备投币");
                return addCoin();
            }
        }).then(function (res) {
            if (res && res.code == 0) {
                console.log("[BilibiliExp] 投了一个币");
                expToday -= 10;
                if (expToday > 0) {
                    return addCoin();
                }
            }
        }).then(function (res) {
            if (res && res.code == 0) {
                console.log("[BilibiliExp] 又投了一个币");
            }
            return gmAjax({
                url: rewardUrl,
                method: 'GET'
            });
        }).then(function (res) {
            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"
                });
                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 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) {
        defer.reject(d);
    });
    return defer;
}
function get_sign(params, key) {
    params.appkey = "27eb53fc9058f8c3";
    params.build = 8960;
    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 = hex_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 (access_key.time < Date.now() - 2 * 24 * 3600000) {
        var oauthData = { access_key: access_key.key };
        var signed = get_sign(oauthData, "c2ed53a74eeefe3cf99fbd01d8c9c375");
        oauthData.sign = signed.sign;
        biliAjax({
            url: "https://passport.bilibili.com/api/oauth",
            type: 'GET',
            dataType: 'json',
            data: oauthData
        }).then(function (res) {
            if (res.code == 0) {
                var expire = data.access_info.expires * 1e3;
                access_key.time = Date.now();
                GM_setValue("access_key", access_key);
                if (expire - 5 * 24 * 360000 < Date.now()) {
                    return biliAjax({
                        url: "https://passport.bilibili.com/api/login/renewToken?" + signed.signedData,
                        type: 'GET',
                        dataType: 'json',
                        data: oauthData
                    });
                }
            }
        }).then(function (res) {
            if (res && (res.code == -101 || res.code == -658)) {
                GM_deleteValue('access_key');
                console.warn('[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": "http://link.acg.tv/forum.php",
            "sign": "67ec798004373253d60114caaad89a8c"
        }
    }).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');
        }
    });
}