Greasy Fork

Greasy Fork is available in English.

Bgm观看进度同步

同步动漫网站的观看进度到bgm.tv

当前为 2021-02-12 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Bgm观看进度同步
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  同步动漫网站的观看进度到bgm.tv
// @author       HinsChou
// @match        https://hinschou.github.io/bangumi.html?code=*
// @match        https://www.bilibili.com/bangumi/play/*
// @match        https://www.acfun.cn/bangumi/*
// @match        http://www.yhdm.io/v/*
// @match        http://tup.yhdm.io/*
// @match        https://www.dilidili99.com/p*
// @match        https://qian.zfa.wang/m3u8.php*
// @connect      bgm.tv
// @grant        GM_openInTab
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_addValueChangeListener
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand

// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
    // 授权回调页面
    let api_uri = "https://api.bgm.tv";
    let token_url = 'https://bgm.tv/oauth/access_token';
    let redirect_uri = "https://hinschou.github.io/bangumi.html";

    // 全局参数
    let bgm_id = ""; // bgm番剧id
    let access_token = ""; // 授权凭证
    let user_id = ""; // 用户id
    let div_bgm;
    // 动态获取
    let watchedEp = {};
    let ep_id = "";
    let is_iframe = false;

    if(document.URL.indexOf(redirect_uri) != -1){
        let code = document.URL.replace(redirect_uri + "?code=", "");
        console.log("授权页面", code);
        if(code.length == 40){ // 正确回调
            getAccessToken(code);
        }
    }else if(getIframe() != ""){
        console.log("视频页面");
        is_iframe = true;
        window.onload = function(){
            setTimeout(function(){
                web_video = getVideoIframe();
                listenVideo();
            }, 3000);
        };
    }else{ // 播放页面
        console.log("播放页面");
        initWeb();
    }

    createSetting();
    GM_registerMenuCommand("API设置", function(){
        let bgm_setting = document.getElementById("bgm_setting");
        bgm_setting.style.display = "block";
    }, "bgm.tv");

    function createSetting(){
        let style_input = ".bgm_input {" +
            "background-color: whitesmoke;" +
            "border: 1px solid deepskyblue; border-radius: 2px;" +
            "height: 24px; width: 180px;" +
            "padding: 0px 5px;" +
            "}";

        let style_button = ".bgm_button {" +
            "background-color: deepskyblue;" +
            "border: 1px solid transparent; border-radius: 2px;" +
            "height: 26px;" +
            "padding: 0px 5px; margin-top: 10px;" +
            "color: white;" +
            "}";

        let style_card = ".bgm_card {" +
            "background-color: white;" +
            "border: 1px solid deepskyblue; border-radius: 2px;" +
            "padding: 10px; text-align: left;" +
            "z-index: 99; position: absolute; width: 200px; display: none;" +
            "}";

        let style_p = ".bgm_p {" +
            "margin-top: 10px; margin-bottom: 10px;" +
            "}";

        GM_addStyle(style_input);
        GM_addStyle(style_button);
        GM_addStyle(style_card);
        GM_addStyle(style_p);

        let div_setting = document.createElement("div");
        div_setting.className = "bgm_card";
        div_setting.id = "bgm_setting";
        div_setting.style = "right: 60px; top: 60px;";

        let p_app_id = document.createElement("p");
        p_app_id.className = "bmg_p";
        p_app_id.innerText = "App ID";
        p_app_id.style = "margin-bottom: 10px;";

        let input_app_id = document.createElement("input");
        input_app_id.className = "bgm_input";
        input_app_id.id = "input_app_id";

        let p_app_secret = document.createElement("p");
        p_app_secret.className = "bmg_p";
        p_app_secret.innerText = "App Secret";
        p_app_secret.style = "margin-top: 10px; margin-bottom: 10px;";

        let input_app_secret = document.createElement("input");
        input_app_secret.className = "bgm_input";
        input_app_secret.id = "input_app_secret";

        let button_save = document.createElement("button");
        button_save.className = "bgm_button";
        button_save.innerText = "保存";
        button_save.onclick = function(){
            let input_app_id = document.getElementById("input_app_id");
            let input_app_secret = document.getElementById("input_app_secret");
            if(input_app_id.value == "" || input_app_secret.value == ""){
                alert("请输入");
            }else{
                GM_setValue("app_id", input_app_id.value);
                GM_setValue("app_secret", input_app_secret.value);
                openAccessTab();

                let bgm_setting = document.getElementById("bgm_setting");
                bgm_setting.style.display = "none";
            }
        };

        let button_close = document.createElement("button");
        button_close.className = "bgm_button";
        button_close.innerText = "取消";
        button_close.style = "margin-left: 20px; background-color: gray; border: 1px solid gray";
        button_close.onclick = function(){
            let bgm_setting = document.getElementById("bgm_setting");
            bgm_setting.style.display = "none";
        };

        div_setting.appendChild(p_app_id);
        div_setting.appendChild(input_app_id);
        div_setting.appendChild(p_app_secret);
        div_setting.appendChild(input_app_secret);

        div_setting.appendChild(button_save);
        div_setting.appendChild(button_close);

        document.body.appendChild(div_setting);

        let app_id = GM_getValue("app_id", "");
        let app_secret = GM_getValue("app_secret", "");

        input_app_id.value = app_id;
        input_app_secret.value = app_secret;
    }

    function getVideoIframe(){
        switch(getIframe()){
            case "yhdm":
                return document.querySelector("video.dplayer-video-current");
            case "dilidili":
                return document.querySelector("video.leleplayer-video-current");
        }
    }

    function getIframe(){
        if(document.URL.indexOf("http://tup.yhdm.io") != -1){
            return "yhdm";
        }else if(document.URL.indexOf("https://qian.zfa.wang") != -1){
            return "dilidili";
        }
        return "";
    }

    // 看过章节
    function watchEp(ep_id){
        if(ep_id == "" || access_token == "" || watchedEp[ep_id]){
            return;
        }
        console.log("看过章节", ep_id);

        let url_watch = api_uri + "/ep/" + ep_id + "/status/watched?access_token=" + access_token;
        console.log("url_watch", url_watch);
        GM_xmlhttpRequest({ // 标记章节为看过
            url: url_watch,
            method: "get",
            onload: function(res){
                console.log("watched", res.responseText);
                let jsonRes = JSON.parse(res.responseText);
                if(jsonRes.code == 200){
                let button_watched = document.getElementById("button_watched");
                button_watched.innerText = "已看过";
                button_watched.style.backgroundColor = "hotpink";
                }
            }
        });
        watchedEp[ep_id] = true;
    }

    function getEpId(subject_id){
        // 预告不查询
        let badge = document.querySelector(".ep-item.cursor div.badge");
        if(subject_id == "" || (badge != null && badge.innerText == "预告")){
            return;
        }
        console.log("查询番剧章节", subject_id);

        let sort = getEpSort();
        let url_ep = api_uri + "/subject/" + subject_id + "/ep";
        console.log("url_ep", url_ep);
        GM_xmlhttpRequest({ // 查询番剧所有章节
            url: url_ep,
            method: "get",
            onload: function(res){
                let json_eps = JSON.parse(res.responseText);
                if(typeof json_eps.eps == "undefined"){
                    return;
                }
                let eps = json_eps.eps;
                console.log("番剧章节数", eps.length);
                // 获取章节id
                let ep = {};
                for(let i in eps){
                    if(eps[i].sort == (sort + 1) || i == sort){
                        ep = eps[i];
                        break;
                    }
                }

                ep_id = ep.id;
                let ep_name = ep.name_cn;
                if(ep_name == ""){
                    ep_name = ep.name;
                }
                console.log("章节ID", ep_id, ep_name);
                let input_ep_id = document.getElementById("input_ep_id");
                input_ep_id.value = ep_id;
                let p_ep_name = document.getElementById("p_ep_name");
                p_ep_name.innerText = ep_name;

                let button_watched = document.getElementById("button_watched");
                button_watched.innerText = "未看过";
                button_watched.style.backgroundColor = "lightseagreen";
            }
        });
    }

    function getSubjectId(title){
        if(bgm_id != ""){
            return;
        }

        let title_old = title;
        console.log("搜索标题", title);
        // 提取番剧主标题
        if(title.indexOf(" ") != -1){
            title = title.substring(0, title.indexOf(" "));
        }else if(title.indexOf(":") != -1){
            title = title.substring(0, title.indexOf(":"));
        }

        // 搜索标题
        var url_search = api_uri + "/search/subject/" + encodeURI(title) + "?type=2";
        console.log("url_search", url_search);
        let input_subject_id = document.getElementById("input_subject_id");
        input_subject_id.placeholder = "搜索标题中";
        GM_xmlhttpRequest({
            url:  url_search,
            method: "get",
            onload: function(res){
                let json_search = JSON.parse(res.responseText);
                if(typeof json_search.list != "undefined"){
                    let subjects = json_search.list;
                    console.log("搜索结果个数", subjects.length, "user_id", user_id);

                    if(json_search.list.length == 1){ // 单个结果
                        let id = json_search.list[0].id;
                        let name = json_search.list[0].name_cn;
                        if(name == ""){
                            name = json_search.list[0].name;
                        }
                        console.log("搜索标题的番剧ID", id, name);
                        setSubject(id, name);

                    } else if(user_id != ""){ // 多个结果
                        // 通过标题判断
                        input_subject_id.placeholder = "对比标题";
                        for(let i in subjects){
                            if(subjects[i].name_cn == title_old){ // 中文标题一致
                                let id = subjects[i].id;
                                let name = subjects[i].name_cn;
                                console.log("对比标题的番剧ID", id, name);
                                setSubject(id, name);
                                return;
                            }
                        }

                        // 通过收藏判断
                        input_subject_id.placeholder = "对比收藏";
                        getCollection("", subjects);

                        // 未找到
                        input_subject_id.placeholder = "未找到";
                    }
                }

            }
        });
    }

    function getCollection(id, subjects){
        var url_collection = api_uri + "/user/" + user_id + "/collection?cat=watching";
        console.log("url_collection", url_collection);

        GM_xmlhttpRequest({ // 查询用户收藏
            url: url_collection,
            method: "get",
            onload: function(res){
                let collections = JSON.parse(res.responseText);
                console.log("我的收藏个数", collections.length);

                if(id != ""){
                    setCollection(id, collections);
                }else{
                    for(let i in subjects){
                        for(let j in collections){
                            // 搜索id和在看收藏id一致
                            if(subjects[i].id == collections[j].subject_id){
                                let id = subjects[i].id;
                                let name = subjects[i].name_cn;
                                if(name == ""){
                                    name = subjects[i].name;
                                }
                                console.log("对比收藏的番剧ID", id, name);
                                setSubject(id, name);
                                return;
                            }
                        }
                    }
                }
            }
        });
    }

    function setCollection(id, collections){
        let button_collect = document.getElementById("button_collect");
        for(let j in collections){
            if(id == collections[j].subject_id){ // 已收藏
                button_collect.innerText = "已收藏";
                button_collect.style.backgroundColor = "hotpink";
                break;
            }
        }
    }

    function listenVideo(){
        // 进度监听
        console.log("web_video", web_video);
        if(web_video != null){
            web_video.addEventListener("timeupdate", function(){
                // 观看进度大于85%, 标记为看完
                let progress = web_video.currentTime / web_video.duration;
//                 console.log("addEventListener timeupdate", progress, is_iframe);
                if(is_iframe){
                    GM_setValue("timeupdate", progress);
                }else if(progress > 0.85){
                    watchEp(ep_id);
                }
            });
            web_video.addEventListener("loadstart", function(){
                setTimeout(function(){
                    getEpId(bgm_id);
                }, 1000);
            });
        }

        console.log("is_iframe", is_iframe);
        if(!is_iframe){
            GM_addValueChangeListener("timeupdate", function(name, old_value, new_value, remote){
//                 console.log("GM_addValueChangeListener timeupdate", new_value);
                if(new_value > 0.85){
                    watchEp(ep_id);
                }
            });
        }
    }

    // 获取网站类型
    function getWebType(){
        if(document.URL.indexOf("https://www.bilibili.com") != -1){
            return "bilibili";
        }else if(document.URL.indexOf("https://www.acfun.cn") != -1){
            return "acfun";
        }else if(document.URL.indexOf("http://www.yhdm.io") != -1){
            return "yhdm";
        }else if(document.URL.indexOf("https://www.dilidili99.com") != -1){
            return "dilidili";
        }
    }

    // 网站属性
    var inputDiv; // 输入框位置
    var title = ""; // 番剧标题
    var web_video; // 播放视频
    function getWebsiteId(){
        console.log("getWebsiteId");
        switch(getWebType()){
            case "bilibili":
                inputDiv = document.querySelector('#toolbar_module');
                title = document.querySelector('.media-title').innerText;
                web_video = document.querySelector(".bilibili-player-video video");
                break;
            case "acfun":
                inputDiv = document.querySelector("div.player-extend-wrap");
                title = document.querySelector(".part-title").innerText;
                web_video = document.querySelector(".container-video video");
                break;
            case "yhdm":
                title = document.querySelector(".gohome.l h1 a").innerText;
                inputDiv = document.querySelector("div.share.l");
                break;
            case "dilidili":
                title = document.querySelector(".video_title h2.title").innerText;
                inputDiv = document.querySelector("div.player_detail");
                break;
        }
    }

    // 章节序号
    function getEpSort(){
        let sort = 0;
        let ep_title = "";
        switch(getWebType()){
            case "bilibili":
                sort = parseInt(document.querySelector(".ep-item.cursor span").title) - 1;
                break;
            case "acfun":
                sort = parseInt(document.querySelector("li.single-p.active").attributes["data-index"].value);
                break;
            case "yhdm":
                ep_title = document.querySelector(".movurls ul li.sel a").innerText;
                sort = parseInt(ep_title.substring(1, ep_title.length - 1)) - 1;
                break;
            case "dilidili":
                ep_title = document.querySelector("ul.content_playlist li.active a").innerText;
                sort = parseInt(ep_title.substring(1, ep_title.length - 1)) - 1;
                break;
        };
        console.log("章节索引", sort);
        return sort;
    }

    // 设置番剧ID
    function setSubject(id, name){
        console.log("设置番剧信息", id, name);

        bgm_id = id;
        GM_setValue(title, id);
        let input_subject_id = document.getElementById("input_subject_id");
        input_subject_id.value = id;

        if(name != ""){
            let p_subject_name = document.getElementById("p_subject_name");
            p_subject_name.innerText = name;
            GM_setValue(id, name);
        }

        getCollection(id, []);
        getEpId(id);
    }

    // 创建番剧信息板块
    function createBgmDiv(){
        let div_bgm = document.createElement("div");
        div_bgm.style = "float: right;";

        let button_show = document.createElement("button");
        button_show.className = "bgm_button";
        button_show.style = "margin-top: 5px; margin-right: 5px;";
        button_show.onclick = function(){
            let bgm_card = document.getElementById("bgm_card");
//             console.log("button_show", bgm_card.style.display);
            if(bgm_card.style.display == "block"){
                bgm_card.style.display = "none";
            }else{
                bgm_card.style.display = "block";
            }
        };
        button_show.innerText = "番组计划";

        let bgm_card = document.createElement("div");
        bgm_card.className = "bgm_card";
        bgm_card.id = "bgm_card";
        bgm_card.style = "margin-top: 5px;"

        let p_subject = document.createElement("p");
        p_subject.innerText = "番剧标题";
        p_subject.className = "bgm_p";
        p_subject.style.marginTop = "0px";
        p_subject.id = "p_subject_name";

        let input_bgm = document.createElement("input");
        input_bgm.placeholder = "番剧ID";
        input_bgm.id = "input_subject_id";
        input_bgm.className = "bgm_input"

        let button_bgm = document.createElement("button");
        button_bgm.innerText = "保存";
        button_bgm.className = "bgm_button";
        button_bgm.onclick = function(){
            let id = input_bgm.value;
            console.log("修改的番剧ID", id);
            setSubject(id, "");
        };

        let button_collect = document.createElement("button");
        button_collect.className = "bgm_button";
        button_collect.id = "button_collect";
        button_collect.style = "background-color: lightseagreen; margin-left: 10px;";
        button_collect.innerText = "未收藏";

        let p_ep = document.createElement("p");
        p_ep.innerText = "章节标题";
        p_ep.className = "bgm_p";
        p_ep.id = "p_ep_name";

        let input_ep = document.createElement("input");
        input_ep.placeholder = "章节ID";
        input_ep.id = "input_ep_id";
        input_ep.className = "bgm_input"

        let button_ep = document.createElement("button");
        button_ep.innerText = "保存";
        button_ep.className = "bgm_button";
        button_ep.onclick = function(){
            ep_id = input_ep.value;
            console.log("修改的章节ID", ep_id);
        };

        let button_watched = document.createElement("button");
                button_watched.className = "bgm_button";
        button_watched.id = "button_watched";
        button_watched.style = "background-color: lightseagreen; margin-left: 10px;";
        button_watched.innerText = "未看过";

        bgm_card.appendChild(p_subject);
        bgm_card.appendChild(input_bgm);
        bgm_card.appendChild(button_bgm);
        bgm_card.appendChild(button_collect);

        bgm_card.appendChild(p_ep);
        bgm_card.appendChild(input_ep);
        bgm_card.appendChild(button_ep);
        bgm_card.appendChild(button_watched);

        div_bgm.appendChild(button_show);
        div_bgm.appendChild(bgm_card);

        return div_bgm;
    }

    // 添加番剧信息
    function insertBangumiInfo(){
        let div_bgm = createBgmDiv();

        setTimeout(function(){
            // 获取番剧标题
            getWebsiteId();
            let id = GM_getValue(title, "");
            let name = GM_getValue(id, "");
            console.log("保存的番剧ID", id, name);

            listenVideo();

            // 手动保存输入框
            inputDiv.appendChild(div_bgm);
            if(id == ""){ // 查询番剧id
                getSubjectId(title);
            }else{
                setSubject(id, name);
            }

        }, 3000);
    }


    // 初始化页面
    function initWeb(){
        // 获取token
        access_token = GM_getValue("access_token", "");
        user_id = GM_getValue("user_id", "");
        let expires_end = GM_getValue("expires_end", 0);
        console.log("授权凭证", access_token, user_id, expires_end);
        // 未授权或授权过期
        if(access_token == "" || new Date().getTime() > expires_end || user_id == ""){
            openAccessTab();
        }else{
            // 手动设置 输入框
            window.onload = function(){
                insertBangumiInfo();
            };
        }
    }

    // 打开授权页面
    function openAccessTab(){
        let app_id = GM_getValue("app_id", "");
        if(app_id == ""){
            console.log("未设置API");
            return;
        }
        let access_url = "https://bgm.tv/oauth/authorize?client_id=" + app_id + "&response_type=code";
        GM_openInTab(access_url, {
            active: true,
            insert: true
        });

        setTimeout(function(){
            GM_addValueChangeListener("access_token", function(name, old_value, new_value, remote){
                console.log("ValueChange", name, new_value);
                access_token = GM_getValue("access_token", "");
                user_id = GM_getValue("user_id", "");
                if(access_token != ""){
                    insertBangumiInfo();
                }
            });
            console.log("GM_addValueChangeListener user_id");
        }, 1000);
    }

    // 通过授权码获取授权凭证
    function getAccessToken(code){
        let app_id = GM_getValue("app_id", "");
        let app_secret = GM_getValue("app_secret", "");

        let body = 'grant_type=authorization_code'+
        '&client_id=' + app_id +
        '&client_secret=' + app_secret +
        '&code=' + code +
        '&redirect_uri=' + redirect_uri;
        console.log(body);
        // 获取token
        GM_xmlhttpRequest({
            url: token_url,
            method: "post",
            data: body,
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            },
            onload: function(res){
                console.log("responseText", res.responseText);
                if(typeof res.responseText == "undefined"){
                    document.body.innerText = "授权失败, 请刷新此页面";
                    return;
                }
                let jsonToken = JSON.parse(res.responseText);
                // 保存token
                if(typeof jsonToken.access_token != undefined){
                    GM_setValue("expires_end", jsonToken.expires_in * 1000 + new Date().getTime());
                    GM_setValue("user_id", jsonToken.user_id);
                    GM_setValue("refresh_token", jsonToken.refresh_token);
                    // GM_setValue("token_type", res.response.token_type);

                    // 最后设置, 触发监听器
                    GM_setValue("access_token", jsonToken.access_token);
                    console.log("set access_token", jsonToken.access_token);
                    document.body.innerText = "授权成功, 请关闭此页面";
                }
            },
            onerror: function(error){
                console.log(error);
            }
        });

    }
})();