Greasy Fork is available in English.

AnimeGo Prev Next player

Prev, Next button for player on animego.org

// ==UserScript==
// @name         AnimeGo Prev Next player
// @name:ru      AnimeGo След. Пред. кнопки для проигрывателя
// @namespace    http://tampermonkey.net/
// @version      0.5.2
// @description  Prev, Next button for player on animego.org
// @description:ru  След., Пред. кнопки для проигрывателя на animego.org.
// @author       You
// @match        https://animego.org/**
// @icon         https://www.google.com/s2/favicons?sz=64&domain=animego.org
// @grant        none
// @license      MIT
// ==/UserScript==

const VIDEO_SELECTOR = ".video-player-main";
const SERIES_SELECTOR = "select[name='series']";

const $ = selector => document.querySelector(selector);

const seriesElem = $(SERIES_SELECTOR);
const posterImg = $(".anime-poster img");
const fullScreenButton = {
  width: 150,
  height: 80,
  offset: 5
};

function inRange(x, min, max) {
  return (x - min) * (x - max) <= 0;
}

function waitForElm(selector) {
  return new Promise(resolve => {
    if ($(selector)) {
      return resolve($(selector));
    }

    const observer = new MutationObserver(mutations => {
      if ($(selector)) {
        resolve($(selector));
        observer.disconnect();
      }
    });

    observer.observe(document, {
      attributeFilter: ["aria-hidden", "data-focus-method"],
      childList: true,
      subtree: true
    });
  });
}

const appendFixStyle = player => {
  const tStyle = document.querySelector("style");
  tStyle.innerHTML = `
    #video-carousel {
      display: flex !important;
    }
    .tns-liveregion.tns-visually-hidden {
      display: none;
    }
    .video-player .owl-nav, .video-player .tns-controls {
      background: #383838;
      z-index: 9;
    }
    .video-player-online.position-relative iframe::backdrop {
      position: absolute;
      left: 0;
      top: 0;
      width: 0;
      height: 0;
    }
  `;
  player.appendChild(tStyle);
};

const changeSeries = series => {
  $('.video-player-bar-series-list [data-id="' + series + '"]').click();
};

const getOptionInfo = option => {
  if (!option) return undefined;
  return {
    value: option.value,
    text: `<span>${option.textContent}</span>`
  };
};

const getPrevNextSeries = seriesElem => {
  const selected = seriesElem.value;
  const options = Array.from(seriesElem.querySelectorAll("option"));
  const values = options.map(option => option.value);
  const selectedIndex = values.indexOf(selected);
  const prev = getOptionInfo(options[selectedIndex - 1]) || undefined;
  const next = getOptionInfo(options[selectedIndex + 1]) || undefined;
  return { prev, next };
};

const elementExist = element =>
  typeof element != "undefined" && element != null;

const removeElement = element => {
  if (elementExist(element)) {
    element.parentNode.removeChild(element);
  }
};

class Default {
  constructor({ seriesElem, player }) {
    this.seriesElem = seriesElem;
    this.player = player;
    this.updButtons = this.updButtons.bind(this);
    this.getPrevButton = this.getPrevButton.bind(this);
    this.getNextButton = this.getNextButton.bind(this);
    this.appendStyle = this.appendStyle.bind(this);
    this.switchToPrev = this.switchToPrev.bind(this);
    this.switchToNext = this.switchToNext.bind(this);
    this.createContainer = this.createContainer.bind(this);
    this.handleFullScreenButtons = this.handleOnMessages.bind(this);
    this.handleSelectChange = this.handleSelectChange.bind(this);
    this.addListeners = this.addListeners.bind(this);

    this.createContainer();
    this.handleOnMessages();
    this.handleSelectChange();
    this.addListeners();
  }

  addListeners() {
    document.addEventListener('keypress', (evt) => {
      const nextKeys = ['n', 'N', 'т', 'T']
      if (nextKeys.includes(evt.key)) {
        this.switchToNext();
      }
      const prevKeys = ['p', 'P', 'з', 'З']
      if (prevKeys.includes(evt.key)) {
        this.switchToPrev();
      }
    })
  }

  switchToPrev() {
    const { prev } = getPrevNextSeries(this.seriesElem);
    changeSeries(prev.value);
    this.updButtons();
  }

  switchToNext() {
    const { next } = getPrevNextSeries(this.seriesElem);
    changeSeries(next.value);
    this.updButtons();
  }

  updButtons() {
    const { prev, next } = getPrevNextSeries(this.seriesElem);
    const container = $(".pn-container");
    const prevElem = container.querySelector(".pn-prev");
    const nextElem = container.querySelector(".pn-next");
    if (prev) {
      if (!elementExist(prevElem)) {
        const prevElemNew = this.getPrevButton();
        container.appendChild(prevElemNew);
      } else {
        prevElem.innerHTML = prev.text;
      }
    } else {
      removeElement(prevElem);
    }
    if (next) {
      if (!elementExist(nextElem)) {
        const nextElemNew = this.getNextButton();
        container.appendChild(nextElemNew);
      } else {
        nextElem.innerHTML = next.text;
      }
    } else {
      removeElement(nextElem);
    }
  }

  getPrevButton() {
    const { prev } = getPrevNextSeries(this.seriesElem);
    if (prev) {
      const prevElem = document.createElement("button");
      const posterImgSrc = posterImg.src;
      prevElem.style.backgroundImage = `url(${posterImgSrc})`;
      prevElem.innerHTML = prev.text;
      prevElem.classList.add("pn-button");
      prevElem.classList.add("pn-prev");
      prevElem.addEventListener("click", () => {
        this.switchToPrev();
      });
      return prevElem;
    }
    return undefined;
  }

  getNextButton() {
    const { next } = getPrevNextSeries(this.seriesElem);
    if (next) {
      const nextElem = document.createElement("button");
      const posterImgSrc = posterImg.src;
      nextElem.style.backgroundImage = `url(${posterImgSrc})`;
      nextElem.innerHTML = next.text;
      nextElem.classList.add("pn-button");
      nextElem.classList.add("pn-next");
      nextElem.addEventListener("click", () => {
        this.switchToNext();
      });
      return nextElem;
    }
    return undefined;
  }

  appendStyle() {
    const tStyle = document.querySelector("style");
    tStyle.innerHTML = `
      .pn-button {
        position: absolute;
        top: calc(50%);
        outline: unset;
        border: 2px solid #fff;
        color: #fff;
        font-size: 16px;
        cursor: pointer;
        padding: 10px 20px;
        min-width: 150px;
        min-height: 60px;
        background: rgba(0, 0, 0, 0.2);
        border-radius: 3px;
        transition: all 0.3s;
        z-index: 10;
        box-shadow: rgb(255 255 255 / 0%) 0px 0px 10px;
        opacity: 0.1;

        background-position: center;
        background-size: cover;
        perspective: 1000px;
        perspective-origin: 50% 50%;
        transform: translate(0, -50%);

        display: flex;
        flex-direction: column;
        align-content: center;
        justify-content: center;
        align-items: center;

        & > span {
          margin-top: 4px;
          background: rgba(0, 0, 0, 0.8);
          padding: 2px 10px;
          border-radius: 3px;
        }

        &:after {
          display: flex;
          border: 1px solid white;
          border-radius: 3px;
          width: 32px;
          box-shadow: 0px 8px 12px #000;
          margin-top: 4px;
          text-shadow: 2px 2px rgba(0, 0, 0, 0.2);
          background: rgba(0, 0, 0, 0.8);
          align-content: center;
          justify-content: center;
        }
      }

      .pn-button:hover {
        opacity: 1;
        box-shadow: rgb(255 255 255 / 50%) 0px 0px 10px;

        &:after {
          animation: scale 1s linear infinite;
        }
      }

      .pn-button:hover,.pn-button:focus {
        outline: unset;
      }

      .pn-prev {
        left: 5px;

        &:after{
          content: "P";
        }
      }

      .pn-prev:hover {
        animation: scroll-to-bottom 100s linear infinite;
      }

      .pn-next {
        right: 5px;

        &:after{
          content: "N";
        }
      }

      .pn-next:hover {
        animation: scroll-to-top 100s linear infinite;
      }

      @keyframes scroll-to-top {
        100%{
          background-position: 0px -3000px;
        }
      }

      @keyframes scroll-to-bottom {
        100%{
          background-position: 0px 3000px;
        }
      }

      @keyframes scale {
        0% {
         scale: 1
        }
        100%{
         scale: 0.95
        }
      }
    `;
    this.player.appendChild(tStyle);
  }

  createContainer() {
    const pnContainer = document.createElement("DIV");
    pnContainer.classList.add("pn-container");
    const prev = this.getPrevButton();
    if (prev) pnContainer.appendChild(prev);
    const next = this.getNextButton();
    if (next) pnContainer.appendChild(next);
    this.appendStyle();
    this.player.insertBefore(pnContainer, this.player.firstChild);
  }

  handleOnMessages() {
    window.onmessage = event => {
      if (document.fullscreenElement) {
        if (event.data.title === "click") {
          const { height, width } = window.screen;
          const { clientX, clientY } = event.data.data;
          const targetCoords = {
            y: [
              height / 2 - fullScreenButton.height / 2,
              height / 2 + fullScreenButton.height / 2
            ],
            prevX: [0, fullScreenButton.width + fullScreenButton.offset],
            nextX: [
              width - (fullScreenButton.width + fullScreenButton.offset),
              width
            ]
          };
          const isPrevButton =
            inRange(clientX, targetCoords.prevX[0], targetCoords.prevX[1]) &&
            inRange(clientY, targetCoords.y[0], targetCoords.y[1]);
          const isNextButton =
            inRange(clientX, targetCoords.nextX[0], targetCoords.nextX[1]) &&
            inRange(clientY, targetCoords.y[0], targetCoords.y[1]);
          if (isPrevButton) {
            this.switchToPrev();
          }
          if (isNextButton) {
            this.switchToNext();
          }
        }
      }

      if (event.data.title === "keydown") {
        console.log(event);
      }
    };
  }

  handleSelectChange() {
    // This code looks like a HACK
    // Default select change does'nt work correctly
    // This code now work correctly!
    var element = document.querySelector(".video-player-bar-series-watch");
    var observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        if (mutation.attributeName === "data-watched-id") {
          setTimeout(() => {
            this.updButtons();
          }, 100);
        }
      });
    });

    observer.observe(element, { attributes: true });
  }
}

waitForElm(VIDEO_SELECTOR).then(player => {
  waitForElm(SERIES_SELECTOR).then(seriesElem => {
    waitForElm(".video-player-main iframe").then(() => {
      new Default({ seriesElem, player });
      appendFixStyle(document.body);
    });
  });
});