Greasy Fork

Greasy Fork is available in English.

bangumi动画和豆瓣及MAL评分对比

show subject score information of douban and MAL in bangumi.tv

当前为 2017-07-30 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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.3
// @grant       GM_addStyle
// @grant       GM_registerMenuCommand
// @grant       GM_xmlhttpRequest
// @require     https://cdn.staticfile.org/fuse.js/2.6.2/fuse.min.js
// @require     https://cdn.staticfile.org/bluebird/3.5.0/bluebird.min.js
// ==/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 = `<a title="强制刷新豆瓣和MAL评分" class="e-userjs-score-ctrl e-userjs-score-fresh">O</a>
      <a title="清除所有评分缓存" class="e-userjs-score-ctrl e-userjs-score-clear">X</a>
`
    $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 && infoList.length) {
      for (let i = 0, len = infoList.length; i < len; i++) {
        let el = infoList[i]
        if (el.innerHTML.match(/放送开始|上映年度/)) {
          subjectInfo.startDate = dealDate(el.textContent.split(':')[1].trim())
        }
        if (el.innerHTML.match('播放结束')) {
          subjectInfo.endDate = dealDate(el.textContent.split(':')[1].trim())
        }
      }
    }
    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}:<span class="num">${siteScoreInfo.averageScore}</span> <span class="desc" style="visibility:hidden">还行</span> <a href="${siteScoreInfo.subjectURL}" target="_blank" class="l">${siteScoreInfo.ratingsCount} 人评分</a>
`
      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)
    }
  }
}());