Bgm观看进度同步

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

目前为 2021-02-14 提交的版本。查看 最新版本

// ==UserScript==
// @name         Bgm观看进度同步
// @namespace    http://tampermonkey.net/
// @version      0.2
// @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 = "lightseagreen";
                }
            }
        });
        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 = "hotpink";
                getUserProgress(user_id);
            }
        });
    }

    function getUserProgress(user_id){
        if(user_id == "" || access_token == ""){
            return;
        }

        let url_progress = api_uri + "/user/" + user_id + "/progress?access_token=" + access_token;
        console.log("url_progress", url_progress);
        GM_xmlhttpRequest({
           url: url_progress,
            method: "get",
            onload: function(res){
                let subjects = JSON.parse(res.responseText);
                console.log("已看番剧", subjects.length);
                for(let i in subjects){
                    let subject = subjects[i];
                    if(subject.subject_id == bgm_id){
                        let eps = subject.eps;
                        console.log("已看章节", eps.length);
                        for(let j in eps){
                            let ep = eps[j];
                            if(ep.id == ep_id && ep.status.css_name == "Watched"){
                                let button_watched = document.getElementById("button_watched");
                                button_watched.innerText = "已看过";
                                button_watched.style.backgroundColor = "lightseagreen";
                                watchedEp[ep_id] = true;
                                break;
                            }
                        }
                    }
                }
            }
        });
    }

    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 = "lightseagreen";
                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: hotpink; margin-left: 10px;";
        button_collect.innerText = "收藏";
        button_collect.onclick = function(){
            collectSubject(bgm_id);
        }

        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: hotpink; 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 collectSubject(subject_id){
        console.log("collectSubject", subject_id, access_token);
        if(subject_id == "" || access_token == ""){
            return;
        }

        let url_collect = api_uri + "/collection/" + subject_id + "/update?access_token=" + access_token;
        console.log("url_collect", url_collect);
        let body = "status=do";
        GM_xmlhttpRequest({
            url: url_collect,
            method: "post",
            data: body,
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            },
            onload: function(res){
                let json = JSON.parse(res.responseText);
                if(json.ep_status == 0){
                    let button_collect = document.getElementById("button_collect");
                    button_collect.innerText = "已收藏";
                    button_collect.style.backgroundColor = "lightseagreen";
                }
            }
        });
    }

    // 添加番剧信息
    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);
            }
        });

    }
})();

QingJ © 2025

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