IMDB VidSrc Player

Add a multi-server video player directly into IMDB movie/series pages.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         IMDB VidSrc Player
// @version      1.2.3
// @description  Add a multi-server video player directly into IMDB movie/series pages.
// @author       https://github.com/atefr
// @license      MIT
// @match        https://www.imdb.com/title/*
// @match        https://m.imdb.com/title/*
// @icon         https://m.media-amazon.com/images/G/01/imdb/images-ANDW73HA/favicon_desktop_32x32._CB1582158068_.png
// @connect      imdb.com
// @connect      m.imdb.com
// @connect      vidsrc-embed.ru
// @connect      vidsrc-embed.su
// @connect      111movies.com
// @connect      2embed.cc
// @connect      www.2embed.cc
// @connect      vidfast.pro
// @namespace https://greasyfork.org/users/1397577
// ==/UserScript==

(function () {
  "use strict";

  // Utility function to log errors
  const logError = (error) => console.error(`Error: ${error.message || error}`);
  let cachedNextData = null;

  const SERVER_STORAGE_KEY = "imdb-vidsrc-server";
  const SERVERS = [
    {
      id: "vidsrc-embed-ru",
      name: "VidSrc",
      buildUrl: (context) =>
        buildVidsrcEmbedUrl("https://vidsrc-embed.ru", context),
    },
    {
      id: "vidsrc-embed-su",
      name: "VidSrc (su)",
      buildUrl: (context) =>
        buildVidsrcEmbedUrl("https://vidsrc-embed.su", context),
    },
    {
      id: "111movies",
      name: "111Movies",
      buildUrl: (context) => build111MoviesUrl(context),
    },
    {
      id: "2embed",
      name: "2Embed",
      buildUrl: (context) => build2EmbedUrl(context),
    },
    {
      id: "vidfast",
      name: "VidFast",
      buildUrl: (context) => buildVidfastUrl(context),
    },
  ];

  insertPlayer();

  function extractSeasonEpisode() {
    const seasonEpisodeDiv = document.querySelector(
      '[data-testid="hero-subnav-bar-season-episode-numbers-section"]',
    );
    if (seasonEpisodeDiv) {
      const seasonEpisodeText = seasonEpisodeDiv.textContent.trim();
      const match = seasonEpisodeText.match(/S(\d+)\s*.\s*E(\d+)/);
      if (match) {
        return {
          season: match[1],
          episode: match[2],
        };
      }
    }
    const nextData = getNextData();
    const episodeNumber =
      nextData?.props?.pageProps?.aboveTheFoldData?.series?.episodeNumber;
    if (episodeNumber?.seasonNumber && episodeNumber?.episodeNumber) {
      return {
        season: String(episodeNumber.seasonNumber),
        episode: String(episodeNumber.episodeNumber),
      };
    }
    return null;
  }

  // Function to extract the series ID from the IMDb page
  function extractSeriesId() {
    const seriesLink = document.querySelector(
      '[data-testid="hero-title-block__series-link"]',
    );
    if (seriesLink) {
      const href = seriesLink.getAttribute("href");
      const match = href.match(/\/title\/(tt\d+)\//);
      if (match) {
        return match[1];
      }
    }
    const nextData = getNextData();
    const seriesId =
      nextData?.props?.pageProps?.aboveTheFoldData?.series?.series?.id;
    if (seriesId) {
      return seriesId;
    }
    return null;
  }

  // Common function to insert the video player into the IMDB page
  function insertPlayer() {
    const imdbId = window.location.pathname.split("/")[2];
    const context = buildContentContext(imdbId);
    const container = createPlayerContainer(context);

    const targetLocation = document.querySelector("main");
    if (targetLocation) {
      targetLocation.before(container);
    } else {
      logError("Target location for player insertion not found on the page");
      document.body.prepend(container);
    }
  }

  function getContentType() {
    const ogType = document
      .querySelector('meta[property="og:type"]')
      ?.getAttribute("content");
    if (ogType === "video.episode") {
      return "episode";
    }
    if (ogType === "video.tv_show") {
      return "series";
    }

    const nextData = getNextData();
    const titleType = nextData?.props?.pageProps?.aboveTheFoldData?.titleType;
    if (titleType?.id === "tvEpisode" || titleType?.isEpisode) {
      return "episode";
    }
    if (titleType?.id === "tvSeries" || titleType?.isSeries) {
      return "series";
    }

    const title = document.title || "";
    const isEpisode = title.includes("TV Episode");
    const isSeries = title.includes("TV Series");
    if (isEpisode) {
      return "episode";
    }
    if (isSeries) {
      return "series";
    }
    return "movie";
  }

  function buildContentContext(imdbId) {
    const contentType = getContentType();
    const seasonEpisode = extractSeasonEpisode();
    return {
      imdbId,
      contentType,
      season: seasonEpisode ? seasonEpisode.season : null,
      episode: seasonEpisode ? seasonEpisode.episode : null,
      seriesId: extractSeriesId() || imdbId,
    };
  }

  function buildVidsrcEmbedUrl(baseUrl, context) {
    if (context.contentType === "movie") {
      return `${baseUrl}/embed/${context.imdbId}/`;
    }
    if (context.contentType === "series") {
      return `${baseUrl}/embed/tv?imdb=${context.seriesId}`;
    }
    if (context.season && context.episode) {
      return `${baseUrl}/embed/${context.seriesId}/${context.season}-${context.episode}/`;
    }
    return null;
  }

  function build111MoviesUrl(context) {
    if (context.contentType === "movie") {
      return `https://111movies.com/movie/${context.imdbId}`;
    }
    const episode = getPreferredEpisode(context);
    if (episode) {
      return `https://111movies.com/tv/${context.seriesId}/${episode.season}/${episode.episode}`;
    }
    return null;
  }

  function build2EmbedUrl(context) {
    if (context.contentType === "movie") {
      return `https://www.2embed.cc/embed/${context.imdbId}`;
    }
    if (context.contentType === "series") {
      return `https://www.2embed.cc/embedtvfull/${context.seriesId}`;
    }
    if (context.season && context.episode) {
      return `https://www.2embed.cc/embedtv/${context.seriesId}?s=${context.season}&e=${context.episode}`;
    }
    return null;
  }

  function buildVidfastUrl(context) {
    if (context.contentType === "movie") {
      return `https://vidfast.pro/movie/${context.imdbId}`;
    }
    const episode = getPreferredEpisode(context);
    if (episode) {
      return `https://vidfast.pro/tv/${context.seriesId}/${episode.season}/${episode.episode}`;
    }
    return null;
  }

  function getSavedServerId(availableServers) {
    const savedId = localStorage.getItem(SERVER_STORAGE_KEY);
    return availableServers.some((entry) => entry.server.id === savedId)
      ? savedId
      : availableServers[0].server.id;
  }

  function getNextData() {
    if (cachedNextData !== null) {
      return cachedNextData;
    }
    const script = document.getElementById("__NEXT_DATA__");
    if (!script || !script.textContent) {
      cachedNextData = null;
      return cachedNextData;
    }
    try {
      cachedNextData = JSON.parse(script.textContent);
    } catch (error) {
      logError(error);
      cachedNextData = null;
    }
    return cachedNextData;
  }

  function getPreferredEpisode(context) {
    if (context.season && context.episode) {
      return {
        season: context.season,
        episode: context.episode,
      };
    }
    if (context.contentType !== "series") {
      return null;
    }
    return {
      season: "1",
      episode: "1",
    };
  }

  function injectStyles() {
    if (document.getElementById("gm-vidsrc-style")) {
      return;
    }
    const style = document.createElement("style");
    style.id = "gm-vidsrc-style";
    style.textContent = `
            #gm-vidsrc-player {
                width: 100%;
                padding: 12px 16px 16px;
                background: #101010;
                border: 1px solid #1f1f1f;
                border-radius: 4px;
                box-shadow: 0 8px 16px rgba(0, 0, 0, 0.35);
                color: #f5f5f5;
                box-sizing: border-box;
            }

            #gm-vidsrc-header {
                display: flex;
                align-items: center;
                justify-content: space-between;
                gap: 12px;
                margin-bottom: 12px;
                flex-wrap: wrap;
            }

            #gm-vidsrc-title {
                font-size: 12px;
                letter-spacing: 0.18em;
                text-transform: uppercase;
                color: #f5c518;
            }

            #gm-vidsrc-controls {
                display: flex;
                align-items: center;
                gap: 8px;
            }

            #gm-vidsrc-controls label {
                font-size: 10px;
                text-transform: uppercase;
                letter-spacing: 0.12em;
                color: #b3b3b3;
            }

            #gm-vidsrc-controls select {
                background: #1a1a1a;
                color: #f5f5f5;
                border: 1px solid #2a2a2a;
                border-radius: 6px;
                padding: 6px 12px;
                font-size: 13px;
            }

            #gm-vidsrc-controls select:focus {
                outline: 2px solid #f5c518;
                outline-offset: 2px;
            }

            #gm-vidsrc-iframe {
                width: 100%;
                height: 65vh;
                min-height: 360px;
                border: 0;
                border-radius: 4px;
                background: #000;
            }

            #gm-vidsrc-message {
                padding: 18px;
                border-radius: 6px;
                background: #171717;
                border: 1px dashed #2a2a2a;
                font-size: 13px;
                color: #d0d0d0;
                display: none;
            }

            @media (max-width: 768px) {
                #gm-vidsrc-player {
                    margin: 0 0 20px;
                    padding: 12px 12px 14px;
                    border-radius: 0;
                }

                #gm-vidsrc-iframe {
                    height: 50vh;
                    min-height: 280px;
                }
            }
        `;
    document.head.appendChild(style);
  }

  function createPlayerContainer(context) {
    injectStyles();

    const container = document.createElement("section");
    container.id = "gm-vidsrc-player";

    const header = document.createElement("div");
    header.id = "gm-vidsrc-header";

    const title = document.createElement("div");
    title.id = "gm-vidsrc-title";
    title.textContent = "Stream";

    const controls = document.createElement("div");
    controls.id = "gm-vidsrc-controls";

    const label = document.createElement("label");
    label.setAttribute("for", "gm-vidsrc-server");
    label.textContent = "Server";

    const select = document.createElement("select");
    select.id = "gm-vidsrc-server";

    controls.appendChild(label);
    controls.appendChild(select);
    header.appendChild(title);
    header.appendChild(controls);
    container.appendChild(header);

    const availableServers = SERVERS.map((server) => ({
      server,
      url: server.buildUrl(context),
    })).filter((entry) => entry.url);

    const message = document.createElement("div");
    message.id = "gm-vidsrc-message";
    container.appendChild(message);

    if (!availableServers.length) {
      message.textContent =
        context.contentType === "episode"
          ? "Season or episode info is missing on this page."
          : "No available servers support this title.";
      message.style.display = "block";
      return container;
    }

    const savedServerId = getSavedServerId(availableServers);
    availableServers.forEach((entry) => {
      const option = document.createElement("option");
      option.value = entry.server.id;
      option.textContent = entry.server.name;
      if (entry.server.id === savedServerId) {
        option.selected = true;
      }
      select.appendChild(option);
    });
    select.disabled = availableServers.length === 1;

    const iframe = createIframe();
    const initialEntry =
      availableServers.find((entry) => entry.server.id === savedServerId) ||
      availableServers[0];
    iframe.src = initialEntry.url;
    container.appendChild(iframe);

    select.addEventListener("change", () => {
      const entry = availableServers.find(
        (item) => item.server.id === select.value,
      );
      if (!entry) {
        return;
      }
      localStorage.setItem(SERVER_STORAGE_KEY, entry.server.id);
      iframe.src = entry.url;
    });

    return container;
  }

  // Helper function to create an iframe for embedding the video player
  function createIframe() {
    const iframe = document.createElement("iframe");
    iframe.id = "gm-vidsrc-iframe";
    iframe.allowFullscreen = true;
    iframe.loading = "lazy";
    iframe.setAttribute("webkitallowfullscreen", "true");
    iframe.setAttribute("mozallowfullscreen", "true");
    return iframe;
  }
})();