IMDb Watcher

Watch movies & series for free on IMDb

当前为 2025-01-12 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name IMDb Watcher
  3. // @namespace https://www.imdb.com/
  4. // @icon https://vidsrc.net/template/vidsrc-ico.png
  5. // @version 2.0.1
  6. // @description Watch movies & series for free on IMDb
  7. // @author cevoj35548
  8. // @license MIT
  9. // @match https://*.imdb.com/title/*
  10. // @grant GM_xmlhttpRequest
  11. // @connect api.themoviedb.org
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. const sources = [
  18. {
  19. name: 'VidLink',
  20. urls: {
  21. movie: 'https://vidlink.pro/movie/{id}?autoplay=true',
  22. tv: 'https://vidlink.pro/tv/{id}/{season}/{episode}?autoplay=true'
  23. },
  24. tmdb: true,
  25. },
  26. {
  27. name: 'Embed.su',
  28. urls: {
  29. movie: 'https://embed.su/embed/movie/{id}/1/1',
  30. tv: 'https://embed.su/embed/tv/{id}/{season}/{episode}'
  31. },
  32. },
  33. {
  34. name: 'SuperEmbed',
  35. urls: {
  36. movie: 'https://multiembed.mov/?video_id={id}',
  37. tv: 'https://multiembed.mov/?video_id={id}&s={season}&e={episode}'
  38. },
  39. },
  40. {
  41. name: '2Embed',
  42. urls: {
  43. movie: 'https://www.2embed.stream/embed/movie/{id}',
  44. tv: 'https://www.2embed.stream/embed/tv/{id}/{season}/{episode}'
  45. },
  46. }
  47. ];
  48.  
  49. const buttonSpacing = 10; // Space between buttons
  50.  
  51. // helper to convert IMDb IDs to TMDb IDs
  52. async function getTmdbID(imdbId) {
  53. console.log(`Fetching TMDb ID for ${imdbId}`);
  54. return new Promise((resolve, reject) => {
  55. GM_xmlhttpRequest({
  56. method: "GET",
  57. url: `https://api.themoviedb.org/3/find/${imdbId}?api_key=8d6d91941230817f7807d643736e8a49&language=en-US&external_source=imdb_id`,
  58. onload: function (response) {
  59. const respJson = JSON.parse(response.responseText);
  60. const tmdbId =
  61. respJson.tv_results?.[0]?.id ||
  62. respJson.movie_results?.[0]?.id ||
  63. respJson.tv_episode_results?.[0]?.show_id;
  64. if (!tmdbId) {
  65. reject("No TMDb ID found");
  66. return;
  67. }
  68. console.log(`TMDb ID: ${tmdbId}`);
  69. resolve(tmdbId);
  70. },
  71. });
  72. });
  73. }
  74.  
  75. // extract season and episode numbers from the IMDb page
  76. function extractSeasonEpisode() {
  77. const seasonEpisodeDiv = document.querySelector('[data-testid="hero-subnav-bar-season-episode-numbers-section"]');
  78. if (seasonEpisodeDiv) {
  79. const seasonEpisodeText = seasonEpisodeDiv.textContent.trim();
  80. const match = seasonEpisodeText.match(/S(\d+).E(\d+)/);
  81. if (match) {
  82. return {
  83. season: match[1],
  84. episode: match[2],
  85. };
  86. }
  87. }
  88. return null;
  89. }
  90.  
  91. // extract the series ID from the IMDb page
  92. function extractSeriesId() {
  93. const seriesLink = document.querySelector('[data-testid="hero-title-block__series-link"]');
  94. if (seriesLink) {
  95. const href = seriesLink.getAttribute('href');
  96. const match = href.match(/\/title\/(tt\d+)\//);
  97. if (match) {
  98. return match[1];
  99. }
  100. }
  101. return null;
  102. }
  103.  
  104. // generate the URL for a specific source
  105. async function generateUrl(urls, isMovie, isEpisode, imdbId, seriesId, seasonEpisode, isTmdb) {
  106. imdbId = seriesId || imdbId;
  107. if (isTmdb) {
  108. imdbId = await getTmdbID(imdbId);
  109. }
  110.  
  111. if (isMovie && isEpisode) {
  112. return urls.movie.replace('{id}', imdbId);
  113. }
  114.  
  115. if (seasonEpisode && seriesId) {
  116. const { season, episode } = seasonEpisode;
  117. return urls.tv.replace('{id}', imdbId).replace('{season}', season).replace('{episode}', episode);
  118. } else {
  119. return urls.tv.replace('{id}', imdbId).replace('{season}', '1').replace('{episode}', '1');
  120. }
  121. }
  122.  
  123. // create the iframe only once
  124. const createIframe = (defaultUrl) => {
  125. const mainEl = document.querySelector('main');
  126. const iframe = document.createElement('iframe');
  127. iframe.setAttribute('style', 'height:800px; width:100%;');
  128. iframe.id = "Player";
  129. iframe.allowFullscreen = "true";
  130. iframe.webkitallowfullscreen = "true";
  131. iframe.mozallowfullscreen = "true";
  132. iframe.src = defaultUrl;
  133. mainEl.before(iframe);
  134. return iframe;
  135. };
  136.  
  137. const imdbId = window.location.pathname.split('/')[2];
  138. const isMovie = document.title.indexOf('TV Series') === -1;
  139. const isEpisode = document.title.indexOf('TV Episode') === -1;
  140. const seasonEpisode = extractSeasonEpisode();
  141. const seriesId = extractSeriesId();
  142.  
  143. // initialize the iframe with the first source as default
  144. (async () => {
  145. const defaultUrl = await generateUrl(
  146. sources[0].urls,
  147. isMovie,
  148. isEpisode,
  149. imdbId,
  150. seriesId,
  151. seasonEpisode,
  152. !!sources[0].tmdb
  153. );
  154. const iframe = createIframe(defaultUrl);
  155. document.querySelector('section.ipc-page-section').setAttribute('style', 'padding-top: 10px;')
  156.  
  157. // add buttons to switch sources
  158. const mainEl = document.querySelector('main');
  159. const buttonContainer = document.createElement('div');
  160. buttonContainer.style.position = 'absolute';
  161. buttonContainer.style.top = '10px';
  162. buttonContainer.style.right = '10px';
  163. buttonContainer.style.display = 'flex';
  164. buttonContainer.style.flexDirection = 'column';
  165. buttonContainer.style.zIndex = '1000';
  166.  
  167. let selectedButton = null;
  168.  
  169. sources.forEach((source, index) => {
  170. const button = document.createElement('button');
  171. button.textContent = `📽 Source - ${source.name}`;
  172. button.style.fontFamily = 'Arial';
  173. button.style.marginBottom = `${buttonSpacing}px`;
  174. button.style.padding = '10px';
  175. button.style.background = '#3B3A3C';
  176. button.style.color = '#ffffff';
  177. button.style.border = 'none';
  178. button.style.cursor = 'pointer';
  179. button.style.fontWeight = 'bold';
  180. button.style.borderRadius = '6px';
  181. button.style.boxShadow =
  182. '0 10px 8px rgb(0 0 0 / 0.05), 0 4px 3px rgb(0 0 0 / 0.1)';
  183.  
  184. // highlight selected button
  185. const highlightButton = (btn) => {
  186. if (selectedButton) {
  187. selectedButton.style.border = 'none';
  188. }
  189. selectedButton = btn;
  190. btn.style.border = '2px solid #FFD700';
  191. };
  192.  
  193. // button click handler
  194. button.addEventListener('click', async () => {
  195. highlightButton(button);
  196. button.textContent = "⏳ Loading...";
  197. button.disabled = true;
  198.  
  199. try {
  200. const url = await generateUrl(source.urls, isMovie, isEpisode, imdbId, seriesId, seasonEpisode, !!source.tmdb);
  201. console.log('Reloading iframe');
  202. document.querySelector('iframe#Player').remove()
  203. createIframe(url);
  204. } catch (error) {
  205. console.error("Error generating URL:", error);
  206. alert(`Failed to generate URL: ${error}`);
  207. } finally {
  208. button.textContent = `📽 Source - ${source.name}`;
  209. button.disabled = false;
  210. }
  211. });
  212.  
  213. // set the first button as highlighted
  214. if (index === 0) {
  215. highlightButton(button);
  216. }
  217.  
  218. buttonContainer.appendChild(button);
  219. });
  220.  
  221. // Add "Open in Popup" button
  222. const popupButton = document.createElement('button');
  223. popupButton.textContent = "⛶ Open in Popup";
  224. popupButton.style.position = 'absolute';
  225. popupButton.style.top = '10px';
  226. popupButton.style.left = '10px';
  227. popupButton.style.padding = '10px';
  228. popupButton.style.background = '#3B3A3C';
  229. popupButton.style.color = '#ffffff';
  230. popupButton.style.border = 'none';
  231. popupButton.style.cursor = 'pointer';
  232. popupButton.style.fontWeight = 'bold';
  233. popupButton.style.borderRadius = '6px';
  234. popupButton.style.boxShadow =
  235. '0 10px 8px rgb(0 0 0 / 0.02), 0 4px 3px rgb(0 0 0 / 0.1)';
  236. popupButton.addEventListener('click', () => {
  237. window.open(iframe.src, '_blank', 'width=800,height=600,scrollbars=yes');
  238. });
  239.  
  240. mainEl.style.position = 'relative';
  241. mainEl.appendChild(buttonContainer);
  242. mainEl.appendChild(popupButton);
  243. })();
  244. })();