Greasy Fork

Anisongs

Adds Anisongs to anime entries on AniList

目前为 2020-05-13 提交的版本。查看 最新版本

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