Greasy Fork

Greasy Fork is available in English.

Anisongs

Adds Anisongs to anime entries on AniList

当前为 2020-05-13 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name Anisongs
// @namespace Morimasa
// @author Morimasa
// @description Adds Anisongs to anime entries on AniList
// @match https://anilist.co/*
// @version 1.08
// @license GPL-3.0-or-later
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// ==/UserScript==
(() => {
const options = {
  cacheName: 'anison', // name in localstorage
  cacheLife: 604800000, // 1 week in ms
  class: 'anisongs', // container class
  styleAttr: "data-v-202cfa27" // tag el attr from anilist to get the same style
}

const temp = {
  last: null,
  target: null
}

const Cache = {
  add(id, data) {
    let cache = JSON.parse(localStorage.getItem(options.cacheName)) || {}
    cache[id] = data
    localStorage.setItem(options.cacheName, JSON.stringify(cache))
  },
  get(id) {
    let cache = localStorage.getItem(options.cacheName)
    if (cache)
      return JSON.parse(cache)[id] || {time:0}
    else
      return {time:0}
  }
}

const API = {
  async getMalId(id) {
    const query = "query($id:Int){Media(id:$id){idMal}}"
    const vars = {id}
    const options = {
		  method: 'POST',
		  body: JSON.stringify({query: query, variables: vars}),
    }
    const resp = await request("https://graphql.anilist.co", options)
    try {
      return resp.data.Media.idMal
    }
    catch {
      console.error("anisongs: Error getting malId")
      return null
    }
  },
  async getSongs(mal_id) {
    const splitSongs = list => list.flatMap(e => e.split(/\#\d{1,2}\s/)).filter(e => e!=="")
    let {opening_themes, ending_themes} = await request(`https://api.jikan.moe/v3/anime/${mal_id}/`)
    opening_themes = splitSongs(opening_themes)
    ending_themes = splitSongs(ending_themes)
    return {opening_themes, ending_themes}
  }
}

function request(url, options={}) {
  return new Promise((resolve, reject) => {
    GM_xmlhttpRequest({
      url: url,
      method: options.method || "GET",
      headers: options.headers || {Accept: "application/json",
                                   "Content-Type": "application/json"},
      responseType: options.responseType || "json",
      data: options.body || options.data,
      onload: res => {
        resolve(res.response)
      },
      onerror: reject
    })
  })
}

function insert(songs, parent) {
  if (!songs || !songs.length){
    let node = document.createElement('div');
    node.innerText = 'No songs to show (つ﹏<)・゚。';
    node.style.textAlign = "center";
    parent.appendChild(node);
    return 0;
  }
  else{
    songs.forEach( (song, i) =>{
      let node = document.createElement('p');
      node.innerText = `${i+1}. ${song}`;
      node.setAttribute(options.styleAttr, '');
      node.classList = "tag";
      parent.appendChild(node);
    })
  }
}

function createTargetDiv(text, target, pos) {
  let el = document.createElement('div');
  el.appendChild(document.createElement('h2'));
  el.children[0].innerText = text;
  el.classList = options.class;
  target.insertBefore(el, target.children[pos]);
  return el;
}

function placeData(data) {
  cleaner(temp.target);
  let op = createTargetDiv('Openings', temp.target, 0);
  let ed = createTargetDiv('Endings', temp.target, 1);
  insert(data.opening_themes, op);
  insert(data.ending_themes, ed);
}

function cleaner(target) {
  if (!target) return;
  let el = target.querySelectorAll(`.${options.class}`);
  el.forEach((e) => {
    target.removeChild(e)
  })
}

async function launch(currentid) {
  // get from cache and check TTL
  const cache = Cache.get(currentid);
  const TTLpassed = (cache.time + options.cacheLife) < +new Date();
  if (TTLpassed){
    const mal_id = await API.getMalId(currentid);
    if (mal_id) {
      const {opening_themes, ending_themes} = await API.getSongs(mal_id);
      // add songs to cache if they're not empty
      if (opening_themes.length || ending_themes.length) {
        Cache.add(currentid, {opening_themes, ending_themes, time: +new Date()});
      }
      // place the data onto site
      placeData({opening_themes, ending_themes});
      return "Downloaded songs"
    }
    else {
      return "No malid"
    }
  }
  else {
    // place the data onto site
    placeData(cache);
    return "Used cache"
  }
}

let observer = new MutationObserver(() => {
    let currentpath = window.location.pathname.split("/");
    if (currentpath[1] === 'anime') {
      let currentid = currentpath[2];
      let location = currentpath.pop();
      if (location!=='') temp.last=0;
      temp.target = document.querySelectorAll('.grid-section-wrap')[2];
      if(temp.last!==currentid && location==='' && temp.target) {
        temp.last = currentid;
        launch(currentid).then(e => console.log(`Anisongs: ${e}`));
      }
     }
   else if (currentpath[1] === 'manga'){
    cleaner(temp.target);
    temp.last = 0;
   }
   else
     temp.last = 0;
});
observer.observe(document.getElementById('app'), {childList: true, subtree: true});
})()