IMDB VidSrc Player

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

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

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