BilibiliExp

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

当前为 2019-11-30 提交的版本,查看 最新版本

// ==UserScript==
// @name        BilibiliExp
// @namespace   BilibiliExp
// @match       *://www.bilibili.com/video/*
// @match       *://link.acg.tv/forum.php*
// @version     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;
    }
    const coinUrl = "https://api.bilibili.com/x/web-interface/nav?build=0&mobi_app=web"
    const addCoinUrl = "https://api.bilibili.com/x/web-interface/coin/add"
    const shareUrl = "https://app.bilibili.com/x/v2/view/share/complete"
    const shareUrlPre = "https://app.bilibili.com/x/v2/view/share/click"
    const rewardUrl = "https://account.bilibili.com/home/reward"
    let totalCoin = 0
    let expToday = 0
    let aid = ""
    let bili_jct = getCookie("bili_jct")
    try {
        aid = unsafeWindow.vd ? unsafeWindow.vd.aid : unsafeWindow.aid
    } catch (error) {
        console.error("[BilibiliExp] aid 获取失败")
        return
    }
    let access_key = GM_getValue('access_key')
    if (access_key) {
        checkKeyStatus(access_key)
    } else {
        getKey()
    }
    if (aid) {
        gmAjax({
            url: rewardUrl,
            method: 'GET',
        }).then((res) => {
            if (res.code == 0) {
                expToday = 50 - res.data.coins_av
                if (!res.data.share_av && access_key) {
                    let 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",
                    }
                    let 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((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((res) => {
                        console.log("[BilibiliExp] " + res.data.toast)
                    })
                }
            } else {
                console.error("[BilibiliExp] 等级信息获取失败");
            }
        }).then(() => {
            return biliAjax({
                url: coinUrl,
                type: 'GET',
                dataType: 'json',
            })
        }).then((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(() => {
                        resolve()
                    }, 10000);
                })
            }
        }).then(() => {
            if (totalCoin >= 50 && expToday > 0) {
                console.log("[BilibiliExp] 准备投币")
                return addCoin()
            }
        }).then((res) => {
            if (res && res.code == 0) {
                console.log("[BilibiliExp] 投了一个币")
                expToday -= 10
                if (expToday > 0) {
                    return addCoin()
                }
            }
        }).then((res) => {
            if (res && res.code == 0) {
                console.log("[BilibiliExp] 又投了一个币")
            }
            return gmAjax({
                url: rewardUrl,
                method: 'GET',
            })
        }).then((res) => {
            if (res.code == 0) {
                let rewardInfo = res.data
                let link = document.createElement('link');
                link.rel = 'stylesheet';
                link.href = "//at.alicdn.com/t/font_1537779_4srood2g1uk.css";
                document.body.appendChild(link);
                let style = "color: inherit;display: inline-block;line-height: 1;"
                let spansData = []
                let 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"
                })
                let bar = document.getElementById("arc_toolbar_report")
                bar.style.height = "60px"
                let ops = document.createElement('div')
                ops.className = "ops"

                spansData.forEach((item) => {
                    let 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((resolve, reject) => {
        GM.xmlHttpRequest({
            method: opt.method,
            url: opt.url,
            data: opt.data ? opt.data : "",
            headers: opt.headers ? opt.headers : "",
            onload: function (response) {
                let 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) {
    let req
    opt.xhrFields = {
        withCredentials: true
    }
    opt.crossDomain = true
    let defer = createPromise()
    req = request(opt)
    req.done(function (d) {
        defer.resolve(d)
    })
    req.fail(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]]
    }
    let 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) {
        let oauthData = {
            access_key: access_key.key
        }
        let signed = get_sign(oauthData, "c2ed53a74eeefe3cf99fbd01d8c9c375")
        oauthData.sign = signed.sign
        biliAjax({
            url: "https://passport.bilibili.com/api/oauth",
            type: 'GET',
            dataType: 'json',
            data: oauthData
        }).then((res) => {
            if (res.code == 0) {
                let 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((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();
        let url = e.data.split(': ')[1];
        var key = url.match(/access_key=([a-f0-9]{32})/);
        if (key) {
            let access_key = {
                key: key[1],
                time: Date.now()
            }
            GM_setValue('access_key', access_key);
            console.log('[BilibiliExp] 成功获取 access_key: ' + access_key.key);
        }
    }
})
function getKey() {
    const 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((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')
        }
    })
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址