// ==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);
}
});
}
})();