Torrenter

Adds links to torrent sites on popular movie websites.

  1. // ==UserScript==
  2. // @name Torrenter
  3. // @namespace http://www.google.com/search?q=mabakay
  4. // @version 2.3.6
  5. // @description Adds links to torrent sites on popular movie websites.
  6. // @description:pl Dodaje linki do stron z torrentami na popularnych stronach o filmach.
  7. // @author mabakay
  8. // @copyright 2010 - 2024, mabakay
  9. // @date 28 Oct 2024
  10. // @license MIT
  11. // @run-at document-end
  12. // @icon64URL https://raw.githubusercontent.com/mabakay/torrenter/master/torrenter_64.png
  13. // @supportURL https://github.com/mabakay/torrenter
  14. // @match http://www.filmweb.pl/*
  15. // @match https://www.filmweb.pl/*
  16. // @match http://release24.pl/*
  17. // @match https://release24.pl/*
  18. // @match http://www.imdb.com/*
  19. // @match https://www.imdb.com/*
  20. // @match http://www.rottentomatoes.com/*
  21. // @match https://www.rottentomatoes.com/*
  22. // @require https://openuserjs.org/src/libs/sizzle/GM_config.min.js
  23. // @grant GM_getValue
  24. // @grant GM_setValue
  25. // @grant GM_registerMenuCommand
  26. // ==/UserScript==
  27. "use strict";
  28. class TorrenterConfigurator {
  29. get localization() {
  30. var _a;
  31. return (_a = TorrenterConfigurator._localization[this._language]) !== null && _a !== void 0 ? _a : TorrenterConfigurator._localization["en"];
  32. }
  33. getConfigurationProperty(name, defaultValue) {
  34. return GM_config.get(name, defaultValue);
  35. }
  36. getConfiguration() {
  37. return {
  38. engines: [
  39. "https://thepiratebay10.org/search/{title}[ {year}]/0/7/0",
  40. "https://torrentgalaxy.to/torrents.php?search={title}[ {year}]&sort=seeders&order=desc",
  41. "https://1337x.to/sort-search/{title}[ {year}]/seeders/desc/1/",
  42. "https://torrentz2eu.org/index.html?q={title}[ {year}]",
  43. "https://yts.mx/browse-movies/{title}[/all/all/0/seeds/{year}/all]",
  44. "https://eztv.re/search/{title}[ {year}]",
  45. "https://www.torlock.com/?q={title}[ {year}]&sort=seeds&order=desc",
  46. "https://www.torrentdownloads.me/search/?new=1&s_cat=0&search={title}[ {year}]",
  47. "https://www.limetorrents.lol/search/all/{title}[ {year}]/seeds/1/"
  48. ],
  49. showEngines: this.getConfigurationProperty("showEngines", true),
  50. showUserEngines: this.getConfigurationProperty("showUserEngines", false),
  51. showUserEnginesFirst: this.getConfigurationProperty("showUserEnginesFirst", false),
  52. userEngines: this.getConfigurationProperty("userEngines", "").split(/\r?\n/).filter((item) => { return !!item; }),
  53. };
  54. }
  55. constructor(changeCallback, onInitCallback) {
  56. let languages = window.navigator.languages.map(item => item.replace('-', '_'));
  57. this._language = languages.find(lang => TorrenterConfigurator._localization[lang])
  58. || languages.find(lang => TorrenterConfigurator._localization[lang.split(/-|_/)[0]])
  59. || 'en';
  60. if (!GM_config || !GM_registerMenuCommand) {
  61. return;
  62. }
  63. let gmConfiguration = {
  64. "id": "mabakay_Torrenter",
  65. "title": this.localization.settingsTitle,
  66. "fields": {
  67. "showEngines": {
  68. "label": this.localization.showBuildInEngines,
  69. "type": "checkbox",
  70. "default": true
  71. },
  72. "showUserEngines": {
  73. "label": this.localization.showUserEngines,
  74. "type": "checkbox",
  75. "default": false
  76. },
  77. "showUserEnginesFirst": {
  78. "label": this.localization.showUserEnginesFirst,
  79. "type": "checkbox",
  80. "default": false
  81. },
  82. "userEngines": {
  83. "label": this.localization.userEngines,
  84. "type": "textarea",
  85. "default": "",
  86. }
  87. },
  88. "events": {
  89. "init": onInitCallback,
  90. "open": (document, window, frame) => {
  91. let userEnginesFiled = document.getElementById("mabakay_Torrenter_field_userEngines");
  92. userEnginesFiled.setAttribute("cols", "80");
  93. userEnginesFiled.setAttribute("rows", "10");
  94. userEnginesFiled.setAttribute("placeholder", this.localization.eg + " https://search-site.com/?title={title}&year={year}&orderby=seeds[&imdbTag={imdb}]");
  95. let enginesFieldDescription = document.createElement("div");
  96. enginesFieldDescription.setAttribute("style", "font-size: 12px;margin: 5px 6px;color: gray;");
  97. enginesFieldDescription.innerHTML = this.localization.addEngineDescription;
  98. let enginesLabelField = document.getElementById("mabakay_Torrenter_userEngines_field_label");
  99. enginesLabelField.after(enginesFieldDescription);
  100. let saveButton = document.getElementById("mabakay_Torrenter_saveBtn");
  101. saveButton.textContent = this.localization.saveButtonCaption;
  102. let closeButton = document.getElementById("mabakay_Torrenter_closeBtn");
  103. closeButton.textContent = this.localization.closeCaptionButton;
  104. let restToDefaultsLink = document.getElementById("mabakay_Torrenter_resetLink");
  105. restToDefaultsLink.textContent = this.localization.resetLinkCaption;
  106. GM_config.frame.setAttribute("style", "inset: 166px auto auto 326px;border: 1px solid rgb(0, 0, 0);height: 440px;margin: 0px;opacity: 1;overflow: auto;padding: 0px;position: fixed;width: 650px;z-index: 9999;display: block;");
  107. },
  108. "save": () => {
  109. GM_config.close();
  110. if (changeCallback) {
  111. changeCallback();
  112. }
  113. }
  114. }
  115. };
  116. GM_config.init(gmConfiguration);
  117. GM_registerMenuCommand(this.localization.configureMenuItem, () => { GM_config.open(); });
  118. }
  119. }
  120. TorrenterConfigurator._localization = {
  121. en: {
  122. settingsTitle: "Torrenter Script Settings",
  123. showBuildInEngines: "Show Build-in Engines",
  124. showUserEngines: "Show User Definied Engines",
  125. showUserEnginesFirst: "Show User Definied Engines First",
  126. userEngines: "User Engines",
  127. eg: "e.g.",
  128. addEngineDescription: "Type by separating with an enter. Available variables are:</br>&emsp;{title} - movie title</br>&emsp;{year} - movie release year</br>&emsp;{imdb} - position ID in www.imdb.com</br>&emsp;[] - optional fragment, removed if the internal tag is not found by the site processor",
  129. saveButtonCaption: "Save",
  130. closeCaptionButton: "Close",
  131. resetLinkCaption: "Reset to defaults",
  132. configureMenuItem: "Configure"
  133. },
  134. pl: {
  135. settingsTitle: "Ustawienia skryptu Torrenter",
  136. showBuildInEngines: "Pokaż wyszukiwarki wbudowane",
  137. showUserEngines: "Pokaż wyszukiwarki użytkownika",
  138. showUserEnginesFirst: "Pokaż wyszukiwarki użytkownika jako pierwsze",
  139. userEngines: "Wyszukiwarki użytkownika",
  140. eg: "np.",
  141. addEngineDescription: "Podaj rozdzielając enterem. Dostępne zmienne to:</br>&emsp;{title} - tytuł filmu</br>&emsp;{year} - rok wydania filmu</br>&emsp;{imdb} - ID pozycji w serwisie www.imdb.com</br>&emsp;[] - fragment opcjonalny usuwany jeżeli wewnętrzny tag nie zostanie odnaleziony przez parser strony",
  142. saveButtonCaption: "Zapisz",
  143. closeCaptionButton: "Anuluj",
  144. resetLinkCaption: "Przywróć ustawienia domyślne",
  145. configureMenuItem: "Skonfiguruj"
  146. },
  147. pt_PT: {
  148. settingsTitle: "Definições do script Torrenter",
  149. showBuildInEngines: "Mostrar motores de pesquisa incorporados",
  150. showUserEngines: "Mostrar motores de pesquisa definidos pelo utilizador",
  151. showUserEnginesFirst: "Mostrar primeiro os motores de pesquisa definidos pelo utilizador",
  152. userEngines: "Motores de pesquisa do utilizador",
  153. eg: "p. ex.",
  154. addEngineDescription: "Digite separando com um enter. As variáveis disponíveis são:</br>&emsp;{title} - título do filme</br>&emsp;{year} - ano de lançamento do filme</br>&emsp;{imdb} - ID da posição em www.imdb.com</br>&emsp;[] - fragmento opcional, removido se a etiqueta interna não for encontrada pelo processador do site",
  155. saveButtonCaption: "Guardar",
  156. closeCaptionButton: "Fechar",
  157. resetLinkCaption: "Repor as predefinições",
  158. configureMenuItem: "Definições"
  159. },
  160. pt_BR: {
  161. settingsTitle: "Configurações do script Torrenter",
  162. showBuildInEngines: "Mostrar mecanismos de busca incorporados",
  163. showUserEngines: "Mostrar mecanismos de busca definidos pelo usuário",
  164. showUserEnginesFirst: "Mostrar primeiro os mecanismos de busca definidos pelo usuário",
  165. userEngines: "Mecanismos de busca do usuário",
  166. eg: "p. ex.",
  167. addEngineDescription: "Digite separando com um enter. As variáveis disponíveis são:</br>&emsp;{title} - título do filme</br>&emsp;{year} - ano de lançamento do filme</br>&emsp;{imdb} - ID da posição em www.imdb.com</br>&emsp;[] - fragmento opcional, removido se a tag interna não for encontrada pelo processador do site",
  168. saveButtonCaption: "Salvar",
  169. closeCaptionButton: "Fechar",
  170. resetLinkCaption: "Redefinir para os padrões",
  171. configureMenuItem: "Configurações"
  172. }
  173. };
  174. class Torrenter {
  175. apply(config, siteProcessor) {
  176. let torrenterElements = document.getElementsByClassName("torrenter");
  177. if (torrenterElements && torrenterElements.length > 0) {
  178. for (let i = torrenterElements.length - 1; i >= 0; i--) {
  179. torrenterElements[i].remove();
  180. }
  181. }
  182. setTimeout(() => { siteProcessor((tag, style, itemStyle, args) => { return this.createLinkSpan(config, tag, style, itemStyle, args); }); }, 250);
  183. }
  184. static getSiteProcessor(hostName) {
  185. switch (hostName) {
  186. case "release24.pl":
  187. return Torrenter.processRelease24;
  188. case "www.filmweb.pl":
  189. return Torrenter.processFilmweb;
  190. case "www.imdb.com":
  191. return Torrenter.processImdb;
  192. case "www.rottentomatoes.com":
  193. return Torrenter.processRottenTomatoes;
  194. }
  195. }
  196. createLinkSpan(config, tag, style, itemStyle, args) {
  197. let span = document.createElement(tag);
  198. span.setAttribute("style", style);
  199. span.classList.add("torrenter");
  200. let engines = [];
  201. if (config.showEngines && config.showUserEngines) {
  202. if (config.showUserEnginesFirst) {
  203. engines = config.userEngines.concat(config.engines);
  204. }
  205. else {
  206. engines = config.engines.concat(config.userEngines);
  207. }
  208. }
  209. else if (config.showEngines) {
  210. engines = config.engines;
  211. }
  212. else if (config.showUserEngines) {
  213. engines = config.userEngines;
  214. }
  215. for (let i = 0; i < engines.length; i++) {
  216. let link = document.createElement("a");
  217. link.setAttribute("href", Torrenter.format(engines[i], args));
  218. if (itemStyle) {
  219. link.setAttribute("style", itemStyle);
  220. }
  221. let urlRegex = /(https?:\/\/)(.+?)\//;
  222. let regexResult = engines[i].match(urlRegex);
  223. link.innerHTML = Torrenter.getFavIconImg(regexResult[2]);
  224. link.setAttribute("title", regexResult[2]);
  225. if (i > 0) {
  226. let separator = document.createElement("span");
  227. separator.innerHTML = "&nbsp;|&nbsp;";
  228. span.appendChild(separator);
  229. }
  230. span.appendChild(link);
  231. }
  232. return span;
  233. }
  234. static getFavIconImg(url) {
  235. return '<img src="' + window.location.protocol + '//www.google.com/s2/favicons?domain=' + url + '" width="16px" height="16px">';
  236. }
  237. static format(str, args) {
  238. return str.replace(/(?:\[[^{}]*?)?{(\w+)}(?:[^{}]*?\])?/g, (text, placeholder) => {
  239. if (text[0] == "[" && text[text.length - 1] == "]") {
  240. return args.hasOwnProperty(placeholder) && args[placeholder] != null ? text.substring(1, text.length - 1).replace("{" + placeholder + "}", encodeURIComponent(args[placeholder])) : "";
  241. }
  242. else {
  243. return args.hasOwnProperty(placeholder) ? encodeURIComponent(args[placeholder]) : text;
  244. }
  245. });
  246. }
  247. static processRelease24(createLinkSpan) {
  248. let titleElement = document.getElementById("mainwindow");
  249. let loopCount = titleElement.childElementCount;
  250. for (let i = 1; i < loopCount; i++) {
  251. let elem = titleElement.children[i];
  252. if (elem.className === "wpis") {
  253. let title_regex = /\"(.*)\"\s*(\(([0-9]{4})\))?/;
  254. let match = elem.children[0].children[0].innerHTML.match(title_regex);
  255. if (match != null) {
  256. let title = match[1];
  257. let year = match.length === 4 && match[3] ? match[3] : null;
  258. let span = createLinkSpan("span", "margin-left: 1em; font-weight: normal;", "position: relative; top: 5px;", { title, year });
  259. elem.children[2].children[0].children[0].children[0].children[0].children[1].children[0].children[0].children[0].appendChild(span);
  260. }
  261. }
  262. }
  263. }
  264. static processFilmweb(createLinkSpan) {
  265. let titleElement = document.querySelector(".filmCoverSection__title");
  266. let title;
  267. let year;
  268. if (titleElement) {
  269. let smallTitleElement = document.querySelector(".filmCoverSection__originalTitle");
  270. if (smallTitleElement) {
  271. title = smallTitleElement.textContent;
  272. }
  273. else {
  274. title = titleElement.textContent;
  275. }
  276. let yearRegexp = /([0-9]{4})/;
  277. let match = document.querySelector(".filmCoverSection__year").textContent.match(yearRegexp);
  278. if (match != null) {
  279. year = match[1];
  280. }
  281. }
  282. let headerElement = document.querySelector(".filmCoverSection__titleDetails");
  283. if (headerElement && title) {
  284. headerElement.insertBefore(createLinkSpan("span", "display: inline-flex;", "position: relative; top: 2px; z-index: 1;", { title, year }), document.querySelector('.preview__content'));
  285. }
  286. }
  287. static processImdb(createLinkSpan) {
  288. let titleElement = document.querySelector('[data-testid*="hero__pageTitle"]');
  289. let title;
  290. let year;
  291. if (titleElement) {
  292. let smallTitleElement = titleElement.nextElementSibling;
  293. if (smallTitleElement && smallTitleElement.textContent && smallTitleElement.textContent.indexOf("Original title:") > -1) {
  294. title = smallTitleElement.textContent;
  295. // Remove "Original title" prefix
  296. let titleRegexp = /Original title: (.*)|.*/;
  297. let titleMatch = title.match(titleRegexp);
  298. if (titleMatch != null) {
  299. title = titleMatch[1];
  300. }
  301. }
  302. else {
  303. title = titleElement.textContent;
  304. }
  305. let yearElement = document.querySelector('[data-testid*="hero__pageTitle"] ~ ul > li');
  306. if (yearElement) {
  307. let yearRegexp = /([0-9]{4})/;
  308. let match = yearElement.textContent.match(yearRegexp);
  309. if (match != null) {
  310. year = match[1];
  311. }
  312. }
  313. }
  314. let headerElement = document.querySelector('[data-testid*="hero__pageTitle"] ~ ul');
  315. if (headerElement && title) {
  316. let match = window.location.pathname.match(/\/(tt.*?)(?:\/|\?|$)/i);
  317. let imdb = match != null ? match[1] : null;
  318. headerElement.appendChild(createLinkSpan("span", "margin-left: 1em; display: inline-block;", null, { title, year, imdb }));
  319. }
  320. }
  321. static processRottenTomatoes(createLinkSpan) {
  322. let titleElement = document.querySelector("media-hero rt-text[slot=title]");
  323. let title;
  324. let year;
  325. if (titleElement) {
  326. title = titleElement.textContent;
  327. let yearRegexp = /([0-9]{4})/;
  328. let match = document.querySelectorAll("media-hero rt-text[slot=metadataProp]")[1].textContent.match(yearRegexp);
  329. if (match != null) {
  330. year = match[1];
  331. }
  332. }
  333. let headerElement = document.querySelector("media-hero rt-text[slot=title]");
  334. if (headerElement && title) {
  335. headerElement.appendChild(createLinkSpan("span", "margin-left: 1em;font-size: 0.5em;position: relative;top: -7px;", "position: relative; top: 2px;", { title, year }));
  336. }
  337. }
  338. }
  339. let hostName = window.location.hostname;
  340. let siteProcessor = Torrenter.getSiteProcessor(hostName);
  341. let applyFunction = (config) => {
  342. if (siteProcessor) {
  343. let torrenter = new Torrenter();
  344. torrenter.apply(config, siteProcessor);
  345. }
  346. };
  347. let configurator = new TorrenterConfigurator(() => applyFunction(configurator.getConfiguration()), () => applyFunction(configurator.getConfiguration()));