Greasy Fork is available in English.

Shiki OtherAnime

Adds other external anime online player to the shikimori

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name            Shiki OtherAnime
// @name:ru         Shiki OtherAnime
// @namespace       shikiOtherAnime
// @version         0.2.2
// @description     Adds other external anime online player to the shikimori
// @description:ru  Добавляет возможность смотреть аниме в стороннем плеере в любой озвучке прямо на сайте shikimori
// @author          Blank
// @match           *://shikimori.tld/*
// @match           *://shikimori.one/*
// @match           *://shikimori.me/*
// @run-at          document-end
// @grant           GM.xmlHttpRequest
// @noframes
// ==/UserScript==

// Notes:
// - fully compatible with shikiAnilibria script
// - autopause does not require videoShortcuts script (player handles postMessage itself)
// - videoShortcuts script is recommended for watching boring moments at 1.25 - 16x speed ^_^

(function main() {
  'use strict';

  const log = (...args) => console.log(`${GM.info.script.name}:`, ...args);
  log('start');

  if (!document.querySelector('#watch-online-style')) {
    const style = document.createElement('style');
    style.id = 'watch-online-style';
    style.textContent = `
.watch-online-iframe {
  display: none;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 80vw;
  height: 45vw;
  z-index: 9001;
}
.watch-online-line {
  padding-bottom: 5px;
}
#watch-online-overlay {
  display: none;
  position: fixed;
  width: 100%;
  height: 100%;
  z-index: 9000;
  background: rgba(0, 0, 0, 0.85);
}`;
    document.querySelector('head').append(style);
  }

  // xmlHttpRequest
  const request = async (details) => new Promise((resolve, reject) => {
    GM.xmlHttpRequest({
      method: 'GET',
      responseType: 'json',
      anonymous: true,
      ...details,
      onload(responseObject) {
        resolve(responseObject);
      },
      onerror(responseObject) {
        reject(responseObject);
      },
    });
  });

  const reduceResults = ({ results }, title, sId) => {
    let filteredResults;
    let exactMatch = true;
    if (sId) {
      filteredResults = results.filter((a) => a.shikimori_id === sId);
    } else {
      const prepare = (t) => t.toLowerCase().replace(/[ ,:'`]/g, '');
      const origTitle = prepare(title);
      filteredResults = results.filter((a) => prepare(a.title_orig) === origTitle);
      if (!filteredResults.length) {
        exactMatch = false;
        const firstTitle = prepare(results[0].title_orig);
        filteredResults = results.filter((a) => prepare(a.title_orig) === firstTitle);
      }
    }
    filteredResults.sort((a, b) => b.episodes_count - a.episodes_count);
    return { ...filteredResults[0], exactMatch };
  };

  const getOtherResults = async (ogTitle, sId) => {
    const apiUrl = 'https://metamedia.glitch.me/api/search';
    const token = '3o52d19319b8dafoa194c9a77ofc9a3d8obfe1bd3eoe28b7cfd7aacebaaf4f6b';
    const url = `${apiUrl}?${sId ? `shikimori_id=${sId}` : `title=${encodeURI(ogTitle)}`}&sign=${token.replace(/o/g, 0)}`;
    let res;
    try {
      res = await request({ url });
      if (res.status !== 200) {
        log(`response error code ${res.status}: ${url}`);
        return null;
      }
    } catch (er) {
      log(`request error: ${url}`);
      return null;
    }
    const { response } = res;
    if (!response || !response.total || !response.results || !response.results.length) {
      if (sId) {
        log(`not found by shikimori_id (${sId})`);
        if (ogTitle) return getOtherResults(ogTitle);
      } else {
        log(`not found by ogTitle (${ogTitle})`);
      }
      return null;
    }
    return reduceResults(response, ogTitle, sId);
  };

  const addEventListeners = ({ overlay, iframe, otherAnime }) => {
    overlay.addEventListener('click', (e) => {
      if (e.target !== e.currentTarget) return;
      overlay.style.display = 'none';
      iframe.style.display = 'none';
      iframe.contentWindow.postMessage({ key: 'kodik_player_api', value: { method: 'pause' } }, '*');
    }, { passive: true });
    otherAnime.addEventListener('click', () => {
      overlay.style.display = 'block';
      iframe.style.display = 'block';
    }, { passive: true });
  };

  const createElements = ({
    src, count, exact, id, sId,
  }) => {
    let overlay = document.querySelector('#watch-online-overlay');
    if (!overlay) {
      overlay = document.createElement('div');
      overlay.id = 'watch-online-overlay';
      document.body.prepend(overlay);
    }
    let iframe = document.querySelector('#watch-online-iframe-other');
    if (!iframe) {
      iframe = document.createElement('iframe');
      iframe.id = 'watch-online-iframe-other';
      iframe.className = 'watch-online-iframe';
      iframe.src = `${src}?translations=true`;
      iframe.allow = 'fullscreen';
      overlay.append(iframe);
    }
    const contestBlock = document.querySelector('.block.contest_winners');
    if (contestBlock) {
      let contestHeader = document.querySelector('#watch-online-contest-subheadline');
      if (!contestHeader) {
        contestHeader = document.createElement('div');
        contestHeader.id = 'watch-online-contest-subheadline';
        contestHeader.className = 'subheadline';
        contestHeader.textContent = 'Турниры';
        contestBlock.prepend(contestHeader);
      }
    }
    let otherAnime = document.querySelector('#watch-online-a-other');
    if (!otherAnime) {
      let block = document.querySelector('#watch-online-block');
      if (!block) {
        block = document.createElement('div');
        block.id = 'watch-online-block';
        block.className = 'block';
        const subheadline = document.createElement('div');
        subheadline.className = 'subheadline';
        subheadline.textContent = 'Онлайн просмотр';
        block.append(subheadline);
        const targetBlock = document.querySelector('.block[itemprop="aggregateRating"]');
        targetBlock.parentElement.insertBefore(block, targetBlock.nextElementSibling);
      }
      const line = document.createElement('div');
      line.className = 'watch-online-line';
      otherAnime = document.createElement('a');
      otherAnime.id = 'watch-online-a-other';
      otherAnime.className = 'b-link';
      otherAnime.title = `Смотреть (точное совпадение ${exact ? '' : 'не '}найдено)`;
      otherAnime.textContent = `Другой плеер${exact ? '*' : ''} [${count ? `1-${count}` : '1'}]`;
      const aSite = document.createElement('a');
      aSite.className = 'b-link';
      aSite.target = '_blank';
      aSite.title = 'Смотреть на сайте amove';
      aSite.href = `https://amove.my.to/#${sId ? `shikimori-${sId}&${id}` : id}`;
      aSite.textContent = ' ↗ ';
      line.append(otherAnime, aSite);
      block.append(line);
    }
    addEventListeners({ overlay, iframe, otherAnime });
  };

  // new anime handler
  const newAnimeShow = async () => {
    const overlay = document.querySelector('#watch-online-overlay');
    const iframe = document.querySelector('#watch-online-iframe-other');
    const otherAnime = document.querySelector('#watch-online-a-other');
    if (overlay && iframe && otherAnime) {
      addEventListeners({ overlay, iframe, otherAnime });
      return;
    }
    const ogTitle = document.querySelector('head > meta[property = "og:title"]').content;
    const rawId = document.URL.substring(document.URL.lastIndexOf('/') + 1, document.URL.indexOf('-'));
    const sId = Number.isNaN(+rawId[0]) ? rawId.substring(1) : rawId;
    const result = await getOtherResults(ogTitle, sId);
    if (!result) {
      log('other anime not found');
      return;
    }
    createElements({
      src: result.link,
      count: result.episodes_count,
      exact: result.exactMatch,
      id: result.id,
      sId: result.shikimori_id,
    });
  };

  // observer fire when html changes its body
  const observer = new MutationObserver((mutationsList) => {
    mutationsList.forEach((mutationRecord) => mutationRecord.addedNodes.forEach((node) => {
      if (node.classList.contains('p-animes-show')) newAnimeShow();
    }));
  });
  observer.observe(document.querySelector('html'), { childList: true });
  if (document.body.classList.contains('p-animes-show')) newAnimeShow();
}());