// ==UserScript==
// @name IMDb Watcher
// @namespace https://www.imdb.com/
// @icon https://vidsrc.net/template/vidsrc-ico.png
// @version 1.3.0
// @description Adds buttons on IMDb to watch movies/series for free
// @author cevoj35548
// @license MIT
// @match https://*.imdb.com/title/*
// @grant GM_xmlhttpRequest
// @connect api.themoviedb.org
// ==/UserScript==
(function() {
'use strict';
const sources = [{
name: 'Embed.su',
urls: {
movie: 'https://embed.su/embed/movie/{id}/1/1',
tv: 'https://embed.su/embed/tv/{id}/{season}/{episode}'
},
color: '#9038ED',
textColor: '#f0f0f0'
},
{
name: 'SuperEmbed',
urls: {
movie: 'https://multiembed.mov/?video_id={id}',
tv: 'https://multiembed.mov/?video_id={id}&s={season}&e={episode}'
},
color: '#5009DE',
textColor: '#bad8eb'
},
{
name: 'VidLink',
urls: {
movie: 'https://vidlink.pro/movie/{id}',
tv: 'https://vidlink.pro/tv/{id}/{season}/{episode}'
},
tmdb: true,
color: '#5009DE',
textColor: '#bad8eb'
},
{
name: '2Embed',
urls: {
movie: 'https://www.2embed.stream/embed/movie/{id}',
tv: 'https://www.2embed.stream/embed/tv/{id}/{season}/{episode}'
},
color: '#0058a4',
textColor: '#bad8eb'
},
{
name: 'VidSrc',
urls: {
movie: 'https://vidsrc.net/embed/movie/{id}',
tv: 'https://vidsrc.net/embed/tv/{id}/{season}/{episode}'
},
color: '#125784',
textColor: '#bad8eb'
},
];
const cornerPadding = 20; // Distance from the corner of the screen
const buttonSpacing = 2; // Space between each button in the stack
function gmRequest(options) {
console.log("Requesting", options.url)
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
...options,
method: "GET",
onload: (response) => resolve(response),
onerror: (error) => reject(error),
});
});
}
// helper to convert imdb IDs to tmdb IDs
// source: https://greasyfork.org/en/scripts/437200
async function getTmdbID(imdbId) {
console.log(`Fetching Tmdb ID for ${imdbId}`);
return await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: "https://api.themoviedb.org/3/find/" + imdbId + "?api_key=8d6d91941230817f7807d643736e8a49&language=en-US&external_source=imdb_id",
onload: function(response) {
let respJson = JSON.parse(response.responseText);
let tmdbId = (respJson.tv_results?.[0]?.id) || (respJson.movie_results?.[0]?.id) || (respJson.tv_episode_results?.[0]?.show_id);
if (!tmdbId) {
reject("No Tmdb ID found");
return;
}
console.log(`Tmdb Id: ${tmdbId}`);
resolve(tmdbId);
},
});
});
}
// extract season and episode numbers from the IMDb page
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+).E(\d+)/);
if (match) {
return {
season: match[1],
episode: match[2]
};
}
}
return null;
}
// 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];
}
}
return null;
}
// generate the URL for a specific source
async function generateUrl(urls, isMovie, isEpisode, imdbId, seriesId, seasonEpisode, isTmdb) {
// get original ID
imdbId = seriesId || imdbId;
if (isTmdb) {
imdbId = await getTmdbID(imdbId);
}
if (isMovie && isEpisode) {
return urls.movie.replace('{id}', imdbId);
}
if (seasonEpisode && seriesId) {
const { season, episode } = seasonEpisode;
return urls.tv.replace('{id}', imdbId).replace('{season}', season).replace('{episode}', episode);
} else {
// console.log("Found no season/episode pair. Using s01e01")
return urls.tv.replace('{id}', imdbId).replace('{season}', '1').replace('{episode}', '1');
}
}
sources.reverse().forEach((source, index) => {
const button = document.createElement('button');
button.textContent = `📽 Watch - ${source.name}`;
button.style.fontFamily = 'Arial';
button.style.position = 'fixed';
button.style.bottom = `${cornerPadding + index * (40 + buttonSpacing)}px`;
button.style.right = `${cornerPadding}px`;
button.style.padding = '10px';
button.style.background = source.color;
button.style.color = source.textColor;
button.style.border = 'none';
button.style.cursor = 'pointer';
button.style.fontWeight = 'bold';
button.style.borderRadius = '6px';
button.style.zIndex = '9999';
button.style.filter = 'drop-shadow(0 10px 8px rgb(0 0 0 / 0.05)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))';
let originalText = null;
let disableBtn = () => {
originalText = button.textContent;
button.textContent = "⏳ Loading...";
button.disabled = true;
};
let enableBtn = () => {
button.textContent = originalText;
button.disabled = false;
};
// add click event to redirect to the source URL
button.addEventListener('click', () => {
disableBtn();
const imdbId = window.location.pathname.split('/')[2];
const isMovie = document.title.indexOf('TV Series') === -1;
const isEpisode = document.title.indexOf('TV Episode') === -1;
const seasonEpisode = extractSeasonEpisode();
const seriesId = extractSeriesId();
generateUrl(source.urls, isMovie, isEpisode, imdbId, seriesId, seasonEpisode, !!source.tmdb)
.then(url => {
console.log(`Opening ${url}`)
window.open(url, '_blank');
enableBtn();
})
.catch(error => {
console.error("Error generating URL:", error);
enableBtn();
alert(`Failed to generate URL: ${error}`);
});
});
document.body.appendChild(button);
});
})();