// ==UserScript==
// @name bangumi anime score compare
// @name:zh-CN bangumi动画和豆瓣及MAL评分对比
// @namespace https://github.com/22earth
// @description show subject score information of douban and MAL in bangumi.tv
// @description:zh-cn bangumi动画页面显示豆瓣和MAL的评分
// @include /^https?:\/\/(bangumi|bgm|chii)\.(tv|in)\/subject\/.*$/
// @version 0.1.2
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @require https://cdn.staticfile.org/fuse.js/2.6.2/fuse.min.js
// @downloadURL none
// ==/UserScript==
(function() {
if (!document.querySelector('.focus.chl.anime')) return;
if (GM_registerMenuCommand) {
GM_registerMenuCommand('\u6e05\u9664\u8bc4\u5206\u7f13\u5b58', clearInfoStorage, 'c');
}
const USERJS_PREFIX = 'E_USERJS_ANIME_SCORE_'
const UPDATE_INTERVAL = 24 * 60 * 60 * 1000
const CLEAR_INTERVAL = UPDATE_INTERVAL * 7
const E_ENABLE_AUTO_SHOW_SCORE_INFO = true;
const TIMEOUT = 10 * 1000;
let tempList = window.location.pathname.match(/subject\/(\d*)/)
if (!tempList) return;
const SUBJECT_BGM_ID = tempList[1]
initControlDOM(document.querySelector('#panelInterestWrapper h2'))
toggleLoading()
//auto fetch
init()
function addStyle(css) {
if (css) {
GM_addStyle(css);
}
else {
//.e-userjs-score-ctrl {display:none;color:#f09199;font-weight:800;}
GM_addStyle(`
.e-userjs-score-ctrl {color:#f09199;font-weight:800;float:right;}
.e-userjs-score-ctrl:hover {cursor: pointer;}
.e-userjs-score-clear {margin-right: 12px;}
.e-userjs-score-loading { width: 208px; height: 13px; background-image: url("/img/loadingAnimation.gif"); }'
`)
}
}
function initControlDOM($target) {
const rawHTML = `O
X
`
$target.innerHTML = $target.innerHTML + rawHTML
addStyle()
document.querySelector('.e-userjs-score-clear').addEventListener('click', clearInfoStorage, false)
document.querySelector('.e-userjs-score-fresh').addEventListener('click', function() {
if (E_ENABLE_AUTO_SHOW_SCORE_INFO) {
const subjectInfo = getSubjectInfo()
toggleLoading()
let $info = document.querySelectorAll('.e-userjs-score-compare')
for (let i = 0, len = $info.length; i < len; i++) {
$info[i].remove()
}
entryDouban(subjectInfo)
entryMAL(subjectInfo)
} else {
init()
}
}, false)
}
function toggleLoading(hidden) {
let $div = document.querySelector('.e-userjs-score-loading')
if (!$div) {
$div = document.createElement('div')
$div.classList.add('e-userjs-score-loading')
let $panel = document.querySelector('.SidePanel.png_bg');
$panel.appendChild($div)
}
if (hidden) {
$div.style.display = 'none';
} else {
$div.style.cssText = '';
}
}
function getSubjectInfo() {
function dealDate(dateStr) {
return dateStr.replace(/年|月|日/g, '/').replace(/\$/, '')
}
let subjectInfo = {}
subjectInfo.subjectName = document.querySelector('h1>a').textContent.trim()
let infoList = document.querySelectorAll('#infobox>li')
if (infoList.length) {
infoList.forEach(function(el) {
if (el.innerHTML.match(/放送开始|上映年度/)) {
subjectInfo.startDate = dealDate(el.textContent.split(':')[1].trim())
return
}
if (el.innerHTML.match('播放结束')) {
subjectInfo.endDate = dealDate(el.textContent.split(':')[1].trim())
return
}
})
}
return subjectInfo
}
function entryDouban(subjectInfo) {
const url = `https://api.douban.com/v2/movie/search?q=${subjectInfo.subjectName}`
searchSubjectJSON(url).then((info) => {
if (info && info.subjects && info.subjects.length) {
const opts = {
keys: ['original_title', 'year']
}
let fuse = new Fuse(info.subjects, opts)
let year = ''
if (subjectInfo.startDate) {
year = new Date(subjectInfo.startDate).getFullYear()
}
let results = fuse.search(subjectInfo.subjectName + year ? ' ' + year : '')
if (results && results.length) {
const dURL = `https://api.douban.com/v2/movie/subject/${results[0].id}`
return searchSubjectJSON(dURL)
} else {
throw new Error("No match results")
}
} else {
throw new Error("Invalid results")
}
})
.then((info) => {
if (info && info.original_title) {
let siteScoreInfo = {
site: '豆瓣评分',
averageScore: info.rating.average,
subjectURL: `https://movie.douban.com/subject/${info.id}/`,
ratingsCount: info.ratings_count,
}
insertScoreInfo(siteScoreInfo)
localStorage.setItem(USERJS_PREFIX + 'DOUBAN' + '_' + SUBJECT_BGM_ID, JSON.stringify({
info: siteScoreInfo,
date: (new Date()).getTime()
}))
} else {
throw new Error("Invalid results")
}
})
.catch((err) => {
console.log('err', err);
})
}
function entryMAL(subjectInfo) {
let siteScoreInfo = {
site: 'MAL评分'
}
//var name = encodeURIComponent('xxx')
var url = `https://myanimelist.net/search/prefix.json?type=anime&keyword=${subjectInfo.subjectName}&v=1`
//var myanimelistSearchURL = `https://myanimelist.net/anime.php?q=${name}`
searchSubjectJSON(url).then((info) => {
const opts = {
keys: ['payload.aired']
}
let year = ''
let results = info.categories[0].items
let fuse = new Fuse(results, opts)
if (subjectInfo.startDate) {
year = new Date(subjectInfo.startDate).getFullYear()
}
if (year) {
results = fuse.search(year + '')
}
if (results && results.length) {
//const dURL = `https://myanimelist.net/includes/ajax.inc.php?t=64&id=${results[0].id}`
siteScoreInfo.subjectURL = results[0].url
return searchSubjectHTML(results[0].url)
} else {
throw new Error("No match results")
}
})
.then((info) => {
let parser = new DOMParser()
let $doc = parser.parseFromString(info, "text/html");
let $score = $doc.querySelector('.fl-l.score')
if ($score) {
//siteScoreInfo.averageScore = parseFloat($score.textContent.trim()).toFixed(1)
siteScoreInfo.averageScore = $score.textContent.trim()
if ($score.dataset.user) {
siteScoreInfo.ratingsCount = $score.dataset.user.replace('users', '').trim()
} else {
throw new Error("Invalid score info")
}
insertScoreInfo(siteScoreInfo)
localStorage.setItem(USERJS_PREFIX + 'MAL' + '_' + SUBJECT_BGM_ID, JSON.stringify({
info: siteScoreInfo,
date: (new Date()).getTime()
}))
} else {
throw new Error("Invalid results")
}
})
.catch((err) => {
console.log('err', err);
})
}
function searchSubjectJSON(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
timeout: TIMEOUT,
url: url,
onreadystatechange: function (response) {
if (response.readyState === 4 && response.status === 200) {
resolve(JSON.parse(response.responseText));
}
},
onerror: function (err) {
reject(err)
},
ontimeout: function (err) {
reject(err)
}
});
})
}
function searchSubjectHTML(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
timeout: TIMEOUT,
url: url,
onreadystatechange: function(response) {
if (response.readyState === 4 && response.status === 200) {
//let parser = new DOMParser();
//let $doc = parser.parseFromString(response.responseText, "text/html");
resolve(response.responseText)
}
},
onerror: function (err) {
reject(err)
},
ontimeout: function (err) {
reject(err)
}
});
})
}
/**
* @param {Object} siteScoreInfo
*/
function insertScoreInfo(siteScoreInfo) {
/*
*siteScoreInfo = siteScoreInfo || {
* site: 'douban',
* averageScore: 6.66,
* subjectURL: 'https://movie.douban.com/subject/26649919/',
* collectionCount: 200
*}
*/
let $panel = document.querySelector('.SidePanel.png_bg');
if ($panel) {
// 两位小数
siteScoreInfo.averageScore = parseFloat(siteScoreInfo.averageScore).toFixed(2)
let $div = document.createElement('div')
$div.classList.add('frdScore')
$div.classList.add('e-userjs-score-compare')
$div.innerHTML = `${siteScoreInfo.site}:${siteScoreInfo.averageScore} 还行 ${siteScoreInfo.ratingsCount} 人评分
`
toggleLoading(true)
$panel.appendChild($div)
}
}
function readScoreInfo(site) {
let scoreInfo = window.localStorage.getItem(USERJS_PREFIX + site.toUpperCase() + '_' + SUBJECT_BGM_ID)
if (scoreInfo) {
scoreInfo = JSON.parse(scoreInfo)
if (new Date() - new Date(scoreInfo.date) < UPDATE_INTERVAL) {
return scoreInfo
}
}
}
function checkInfoUpdate() {
let time = localStorage.getItem(USERJS_PREFIX + 'LATEST_UPDATE_TIME')
let now = new Date();
if (!time) {
localStorage.setItem(USERJS_PREFIX + 'LATEST_UPDATE_TIME', now.getTime())
return
} else if (now - new Date(time) > CLEAR_INTERVAL) {
clearInfoStorage()
}
}
function clearInfoStorage() {
let now = new Date();
for (var key in localStorage) {
if (key.match(USERJS_PREFIX)) {
console.log(localStorage.getItem(key));
localStorage.removeItem(key)
}
}
//localStorage.setItem(USERJS_PREFIX + 'LATEST_UPDATE_TIME', now.getTime())
}
function init() {
checkInfoUpdate()
const subjectInfo = getSubjectInfo()
const scoreInfoDouban = readScoreInfo('douban')
const scoreInfoMAL = readScoreInfo('mal')
if (!scoreInfoDouban) {
entryDouban(subjectInfo)
} else {
insertScoreInfo(scoreInfoDouban.info)
}
if (!scoreInfoMAL) {
entryMAL(subjectInfo)
} else {
insertScoreInfo(scoreInfoMAL.info)
}
}
}());