Greasy Fork is available in English.

Anime Songs - AniList Player

This Script allows You to play Openings and Endings directly on AniList

スクリプトをインストール?
作者が勧める他のスクリプト

AniList - Activity Assistantも気に入るかもしれません。

スクリプトをインストール
このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         Anime Songs - AniList Player
// @namespace    Openings and Endings Player
// @version      2.2.1
// @description  This Script allows You to play Openings and Endings directly on AniList
// @author       NurarihyonMaou
// @match        https://anilist.co/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=anilist.co
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @require      http://code.jquery.com/jquery-3.5.1.min.js

// ==/UserScript==

const $ = window.jQuery;

let result;

let openings, endings;

let audioUrls = ['https://animethemes.moe/audio/', 'https://a.animethemes.moe/', 'https://beta.animethemes.moe/audio/'];
let videoUrls = ['https://animethemes.moe/video/', 'https://v.animethemes.moe/', 'https://beta.animethemes.moe/video/'];

let urlsIndex = GM_getValue("urlsIndex") ?? 1;

//let audioURLAddon = "-NCBD1080";

let url = "https://graphql.anilist.co";

let AnimeID;

let AniID = { id: parseInt(window.location.pathname.split("/")[2]) };

const VideoIcon = `<svg class="svg-inline--fa fa-solid fa-video"aria-hidden="true" data-fa-processed="" data-prefix="fa" data-icon="video" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 128C0 92.7 28.7 64 64 64H320c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128zM559.1 99.8c10.4 5.6 16.9 16.4 16.9 28.2V384c0 11.8-6.5 22.6-16.9 28.2s-23 5-32.9-1.6l-96-64L416 337.1V320 192 174.9l14.2-9.5 96-64c9.8-6.5 22.4-7.2 32.9-1.6z"/></svg>`;
const MusicIcon = `<svg class="svg-inline--fa fa-solid fa-music"aria-hidden="true" data-fa-processed="" data-prefix="fa" data-icon="music" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M499.1 6.3c8.1 6 12.9 15.6 12.9 25.7v72V368c0 44.2-43 80-96 80s-96-35.8-96-80s43-80 96-80c11.2 0 22 1.6 32 4.6V147L192 223.8V432c0 44.2-43 80-96 80s-96-35.8-96-80s43-80 96-80c11.2 0 22 1.6 32 4.6V200 128c0-14.1 9.3-26.6 22.8-30.7l320-96c9.7-2.9 20.2-1.1 28.3 5z"/></svg>`;

const ToggleStyle = `.AudioMode {
	width:45px;
	height:15px;
	background-color: grey;
	position: relative;
	transition: ease-in-out 0.5s;
  display: flex;
  justify-content: center;
  align-items: center;
}
.circle {
	border-radius:50%;
	width: 30px;
	max-height: 30px;
	color:red;
	z-index: 9999;
	position: absolute;
	left:0;
	transition: ease-in-out 0.5s;
}
.circle svg {
	transition: ease-in-out 0.5s;
	color:red;
}
.active{
	left:50%;
}
`;

GM_addStyle(ToggleStyle);

let AudioMode = GM_getValue("AudioMode") ?? false;

async function getAID() {
  AniID = { id: parseInt(window.location.pathname.split("/")[2]) };

  let query = `
          query ($id: Int) {
          Media (id: $id, type: ANIME) {
          idMal
          }
          }
          `;

  let options = {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
    },
    body: JSON.stringify({
      query: query,
      variables: AniID,
    }),
  };

  await fetch(url, options)
    .then(handleResponse)
    .then(handleData)
    .catch(handleError);

  function handleResponse(response) {
    return response.json().then(function (json) {
      return response.ok ? json : Promise.reject(json);
    });
  }

  function handleData(data) {
    AnimeID = data.data.Media.idMal;
  }

  function handleError(error) {
    console.error(error);
  }
}

async function returnThemes() {
  await getAID();

  let response = $.ajax({
    url: `https://api.jikan.moe/v4/anime/${AnimeID}/themes`,
    method: "GET",
    success: function (data) {
      return data;
    },
    error: function (error) {
      alert("Something went wrong.\n" + error.errorMessage);
    },
  });
  return response;
}

function appendThemes() {
  if ($("body").find("div.openings").length == 0)
    $("body div.characters").after(
      "<div class='openings'><h2>Openings</h2></div>"
    );
  else {
    $("body div.characters").after($("body").find("div.openings"));
    $("body").find("div.openings").html("<h2>Openings</h2>");
  }
  if ($("body").find("div.endings").length == 0)
    $("body div.openings").after("<div class='endings'><h2>Endings</h2></div>");
  else {
    $("body div.openings").after($("body").find("div.endings"));
    $("body").find("div.endings").html("<h2>Endings</h2>");
  }

  themes.data.openings.forEach((opening) => {
    $("body").find("div.openings").append(`<ul class='tag'>${opening}</ul>`);
  });
  themes.data.endings.forEach((ending) => {
    $("body").find("div.endings").append(`<ul class='tag'>${ending}</ul>`);
  });

  if (parseInt(AniID.id) != 112323) {
    if ($("body").find("div.AudioMode").length == 0)
      $("body div.openings").before(
        `<input id="urlsIndex" type="range" value="${urlsIndex}" min="0" max="2"/> URL Slider - use this when the Player(s) don't work<div class="AudioMode"><button class="${
          !AudioMode ? "circle" : "circle active"
        }">${AudioMode ? MusicIcon : VideoIcon}</button></div></br>`
      );
    else $("body div.openings").before($("body").find("div.AudioMode"));
  }
}

$("body").on("click", "div.AudioMode", function () {
  AudioMode = !AudioMode;

  if (AudioMode) {
    $(this).children().addClass("active");
    $(this).children().html(MusicIcon);
  } else {
    $(this).children().removeClass("active");
    $(this).children().html(VideoIcon);
  }

  GM_setValue("AudioMode", AudioMode);

  $("body").find(".AnimeThemesPlayer").remove();

  loadVideos();
});

$("body").on("change", "input#urlsIndex", function () {

    urlsIndex = document.getElementById('urlsIndex').value;

    GM_setValue("urlsIndex", urlsIndex);

    $("body").find(".AnimeThemesPlayer").remove();

    loadVideos();
  });

function loadVideos() {
  let Song = 0;

  (function init() {
    openings = document.getElementsByClassName("openings");
    endings = document.getElementsByClassName("endings");
    if (openings.length > 0 || endings.length > 0) {
      GM_xmlhttpRequest({
        method: "GET",
        url:
          "https://api.animethemes.moe/anime?filter[has]=resources&filter[site]=MyAnimeList&filter[external_id]=" +
          AnimeID +
          "&include=animethemes.animethemeentries.videos,animethemes.song.artists",
        data: AnimeID,
        headers: { "Content-Type": "application/json" },
        onload: function (response) {
          result = JSON.parse(response.responseText);

          openings = openings.length > 0 ? openings : endings;
          if ($("body .AnimeThemesPlayer").length == 0) {
            $(openings).append(
              AudioMode
                ? "<audio style='display: none' class='AnimeThemesPlayer' width='500px' height='300' src=''controls autoplay/>"
                : "<video style='display: none' class='AnimeThemesPlayer' width='500px' height='300' src='' allowfullscreen controls autoplay/>"
            );
          }

          $("body div.IMOE").remove();

          if ($("body").find("a#AnimeThemesLink").length == 0)
            $("body")
              .find("div.openings")
              .before(
                `<a id="AnimeThemesLink" href="https://animethemes.moe/anime/${result.anime[0].slug}">AnimeThemes - Videos Source</a></br></br>`
              );

          for (Song; Song < result.anime[0].animethemes.length; Song++) {
            let CurrSongMatch = false;
            $("div.openings, div.endings")
              .children(".tag")
              .filter(function (x) {
                let entriesCount = $("div.openings, div.endings").children(
                  ".tag"
                ).length;
                let currSong = $(this);

                let safetyIndicator = result.anime[0].animethemes[Song]
                .animethemeentries[0].nsfw
                ? "NSFW"
                : "";
              safetyIndicator +=
                safetyIndicator == "NSFW"
                  ? result.anime[0].animethemes[Song].animethemeentries[0]
                      .spoiler
                    ? " && Spoilers "
                    : " "
                  : result.anime[0].animethemes[Song].animethemeentries[0]
                      .spoiler
                  ? "Spoilers "
                  : " ";

                if (
                  $(currSong)
                    .text()
                    .toLowerCase()
                    .split(" by ")[0]
                    .replace(/\(([^)]+)\)/, "")
                    .replace(/[“"”]+/g, "")
                    .replace(/\#\d*:/g, "")
                    .replace(/\d*\:/g, "")
                    .replace(/[^\w\s]/gi, " ")
                    .replace(/ /g, "")
                    .trim() ==
                  result.anime[0].animethemes[Song].song.title
                    .toLowerCase()
                    .replace(/\(([^)]+)\)/, "")
                    .replace(/\#\d*:/g, "")
                    .replace(/[^\w\s]/gi, " ")
                    .replace(/^0+/, "")
                    .replace(/ /g, "")
                    .trim()
                ) {

                  CurrSongMatch = true;
                  $(currSong).html(
                    AudioMode
                      ? "<span style='color: red;'>" +
                          safetyIndicator +
                          "</span><a href='"+audioUrls[urlsIndex]+
                          result.anime[0].animethemes[Song].animethemeentries[0]
                            .videos[0].filename +
                          ".ogg'>" +
                          $(currSong).text() +
                          "</a>"
                      : "<span style='color: red;'>" +
                          safetyIndicator +
                          "</span><a href='"+videoUrls[urlsIndex]+
                          result.anime[0].animethemes[Song].animethemeentries[0]
                            .videos[0].basename +
                          "'>" +
                          $(currSong).text() +
                          "</a>"
                  );
                }

                if (x == entriesCount - 1 && CurrSongMatch == false) {
                  if ($("div.IMOE").length == 0) {
                    if ($("div.endings").length > 0) {
                      $("div.endings").after(
                        '<div class="IMOE" style="margin-bottom: 30px;"><h2>Additional OPs and EDs</h2></div>'
                      );
                    } else if ("div.openings".length > 0) {
                      $("div.endings").after(
                        '<div class="IMOE" style="margin-bottom: 30px;"><h2>Additional OPs and EDs</h2></div>'
                      );
                    } else {
                      $("div.characters").after(
                        '<div class="IMOE" style="margin-bottom: 30px;"><h2>Additional OPs and EDs</h2></div>'
                      );
                    }
                  }
                  let CurrentSongArtists = "";
                  for (
                    let Artists = 0;
                    Artists <
                    result.anime[0].animethemes[Song].song.artists.length;
                    Artists++
                  ) {
                    CurrentSongArtists +=
                      result.anime[0].animethemes[Song].song.artists[Artists]
                        .name + " ";
                  }
                  if (CurrentSongArtists == "") CurrentSongArtists = "???";
                  $("div.IMOE").append(
                    AudioMode
                      ? "<span style='color: red;'>" +
                          safetyIndicator +
                          "</span><ul class='tag'><a href='"+audioUrls[urlsIndex]+
                          result.anime[0].animethemes[Song].animethemeentries[0]
                            .videos[0].filename +
                          ".ogg'>" +
                          result.anime[0].animethemes[Song].song.title +
                          " by " +
                          CurrentSongArtists +
                          "</a></ul>"
                      : "<span style='color: red;'>" +
                          safetyIndicator +
                          "</span><ul class='tag'><a href='"+videoUrls[urlsIndex]+
                          result.anime[0].animethemes[Song].animethemeentries[0]
                            .videos[0].basename +
                          "'>" +
                          result.anime[0].animethemes[Song].song.title +
                          " by " +
                          CurrentSongArtists +
                          "</a></ul>"
                  );
                  CurrentSongArtists = "";
                }
              });
          }
        },
        onerror: function (error) {
          console.log(error);
        },
      });
    } else {
      setTimeout(init, 0);
    }
  })();
}

function SetArifureta() {
  $("body").find(".AudioMode").remove();
  $("body").find("#AnimeThemesLink").remove();

  if ($("body iframe.AnimeThemesPlayer").length == 0) {
    $("body")
      .find("div.openings")
      .append(
        "<iframe style='display: none' class='AnimeThemesPlayer' width='500px' height='300' src='' allowfullscreen/>"
      );
  }

  if ($("body").find("a#MyYTChannel").length == 0)
    $("body")
      .find("div.openings")
      .before(
        `<a id="MyYTChannel" href="https://www.youtube.com/c/NurarihyonMaou">My YT Channel</a></br></br>`
      );

  $("div.openings")
    .children(".tag")
    .html(
      '<a href="https://www.youtube.com/embed/s0vm9JVjexY?autoplay=1">"Daylight" by MindaRyn</a>'
    );

  $("div.endings")
    .children(".tag")
    .html(
      '<a href="https://www.youtube.com/embed/5aJWWk9B78E?autoplay=1">"Gedou Sanka (外道讃歌)" by FantasticYouth</a>'
    );
}

let themes;

async function loadThemesAndVideos() {
  themes = await returnThemes();
  appendThemes();

  if (parseInt(AniID.id) == 112323) SetArifureta();
  else loadVideos();
}

$(window).on("load", async function () {
  if (window.location.pathname.split("/")[1] === "anime") {
    await loadThemesAndVideos();

    $("body").on("click", ".tag", function (e) {
      e.preventDefault();

      if ($(this).children().length == 0) {
        alert(
          "Sadly, this OP/ED is Missing or there's a bug in the Script (Check Instruction)"
        );
      } else if (
        $("body .AnimeThemesPlayer").css("display") == "none" ||
        $(this).children("a").attr("href") !=
          $("body .AnimeThemesPlayer").attr("src")
      ) {
        $("body .AnimeThemesPlayer").css("display", "block");

        $("body .AnimeThemesPlayer").attr(
          "src",
          $(this).children("a").attr("href")
        );
      } else if (
        $(this).children("a").attr("href") ==
        $("body .AnimeThemesPlayer").attr("src")
      ) {
        $("body .AnimeThemesPlayer").css("display", "none");
        $("body .AnimeThemesPlayer").attr("src", "");
      }
    });
  }
});

let oldURL;

(function () {
  let pushState = history.pushState;
  let replaceState = history.replaceState;

  history.pushState = function () {
    pushState.apply(history, arguments);
    window.dispatchEvent(new Event("pushstate"));
    window.dispatchEvent(new Event("locationchange"));
  };

  history.replaceState = function () {
    replaceState.apply(history, arguments);
    window.dispatchEvent(new Event("replacestate"));
    window.dispatchEvent(new Event("locationchange"));
  };

  window.addEventListener("popstate", function () {
    window.dispatchEvent(new Event("locationchange"));
  });

  $(window).on("load", function () {
    GM_setValue("oldURL", $(location).attr("pathname").split("/")[1]);
  });

  window.addEventListener("locationchange", function () {
    oldURL = GM_getValue("oldURL");

    if (
      oldURL != "anime" &&
      $(location).attr("pathname").split("/")[1] == "anime"
    ) {
      GM_setValue("oldURL", $(location).attr("pathname").split("/")[1]);
      location.reload();
    } else if (
      oldURL == "anime" &&
      $(location).attr("pathname").split("/")[1] == "anime"
    ) {
      loadThemesAndVideos();
    } else {
      GM_setValue("oldURL", $(location).attr("pathname").split("/")[1]);
    }
  });
})();