Seriesfeed++

A fork of Bierdopje AddOn Plus for Seriesfeed

目前为 2017-10-01 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Seriesfeed++
  3. // @namespace https://greasyfork.org/en/users/22592
  4. // @description A fork of Bierdopje AddOn Plus for Seriesfeed
  5. // @include http://seriesfeed.com/*
  6. // @include http://*.seriesfeed.com/*
  7. // @include https://seriesfeed.com/*
  8. // @include https://*.seriesfeed.com/*
  9. // @version 1.15
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant GM_addStyle
  14. // @require http://code.jquery.com/jquery-1.10.2.js
  15. // @require http://code.jquery.com/ui/1.11.4/jquery-ui.js
  16. // @author Mr. Invisible (mrinvisible@cryptolab.net)
  17. // @run-at document-end
  18. // ==/UserScript==
  19.  
  20. /*global GM_getValue,GM_registerMenuCommand,GM_addStyle,GM_info,GM_setValue,$ */
  21.  
  22. /**
  23. Changelog:
  24. 1.14: Fix bug with provider introduced in 1.13
  25. 1.13: Added another search engine
  26. 1.12:
  27. - Fix problem with watchlist that have no items left
  28. - Replaced two dead search engines
  29. 1.11: Quick-fix for dialog
  30. 1.10:
  31. - Replaced all providers, now you can define your own
  32. - Replaced quality, now you can define your own
  33. 1.09: Added an exception
  34. 1.08: Multi-domain
  35. 1.07: Forgot to turn of debug once again
  36. 1.06:
  37. - Updated for Seriesfeed 2.0
  38. - Added new provider
  39. - Dialog also closes when middle-clicking
  40. - Re-added functionality to the episode and season pages.
  41. - Added exception for legends of tomorrow
  42. - Added email address for easier communication
  43. 1.05: Updated for Seriesfeed 1.3
  44. 1.04: Fixed problem with the visual watchlist & dialog for download now closes after clicking a link.
  45. 1.03: Updated for Seriesfeed 1.2
  46. 1.02: Fixed small bug with Chrome-derived browsers
  47. 1.01: Rewrote script in order to accommodate the Seriesfeed pages
  48. 1.00: Cloned from the Bierdopje AddOn Plus version 1.101
  49. **/
  50.  
  51. // Create one accessible object. The remainder is hidden for external use.
  52. var seriesFeedPlusPlus = (function () {
  53. 'use strict';
  54.  
  55. var seriesFeedPlusPlus, configDialog, // Objects
  56. debug, pageRegexes, currentPage, flags, subProviders, languageMap, // Variables
  57. main, checkPage, injectMenuItem, modifyPage, handleStartPage, injectDefaultTable, createFunctionality,
  58. createLanguageFlag, parseEpisode, showSubSelectionDialog, handleBroadcastPage, handleWatchlistPage,
  59. injectTableHeader, showDlSelectionDialog, createMediaLink, formatToConvention, handleSeasonPage,
  60. handleEpisodePage; // Methods
  61.  
  62. // Initialize objects
  63. seriesFeedPlusPlus = {};
  64. configDialog = (function () {
  65. var instance, configElementName, preferences, mapping, show, close, closeOtherSubConfigs, closeSubConfig,
  66. openSubConfig, changeConfiguration, saveConfiguration, loadPreferences, getEnabledSubtitleLanguages,
  67. getConfigValue, getEnabledSubtitleSources, getEnabledDownloadProviders, getEnabledMediaQualities,
  68. checkConfiguration, isValidQualityConfig, isValidProviderConfig, isValidProvidersConfig;
  69.  
  70. // Init vars
  71. configElementName = "configFrame";
  72. // Preferences with their default values
  73. preferences = {
  74. sub_lang_nl: true,
  75. sub_lang_en: true,
  76. sub_source_addic7ed: true,
  77. sub_source_podnapisi: true,
  78. sub_source_opensubtitles: false,
  79. sub_source_subtitleseeker: false,
  80. dl_quality: [
  81. 'WEB-DL', 'HDTV 1080', 'HDTV 720', 'x265'
  82. ],
  83. dl_providers: [
  84. {
  85. "name": "1337x",
  86. "url": "https://1337x.to/search/{show}+{season_episode}+{quality}/1/",
  87. "quality": {
  88. "WEB-DL": "WEB-DL",
  89. "HDTV 1080": "1080p x264",
  90. "HDTV 720": "720p x264",
  91. "x265": "x265"
  92. },
  93. "invalid_characters": {
  94. "old": ["(", ")", " "],
  95. "new": ["", "", "+"]
  96. },
  97. },
  98. {
  99. "name": "RARBG",
  100. "url": "https://rarbg.to/torrents.php?search={show} {season_episode} {quality}",
  101. "quality": {
  102. "WEB-DL": "WEB-DL",
  103. "HDTV 1080": "1080p x264",
  104. "HDTV 720": "720p x264",
  105. "x265": "x265"
  106. }
  107. },
  108. {
  109. "name": "TPB",
  110. "url": "https://thepiratebay.org/search/{show} {season_episode} {quality}",
  111. "quality": {
  112. "WEB-DL": "WEB-DL",
  113. "HDTV 1080": "1080p x264",
  114. "HDTV 720": "720p x264",
  115. "x265": "x265"
  116. }
  117. },
  118. {
  119. "name": "NZBIndex",
  120. "url": "https://www.nzbindex.com/search/?q={show} {season_episode} {quality}&max=25&sort=agedesc&hidespam=1&more=0",
  121. "quality": {
  122. "WEB-DL": "720p|1080p WEB-DL",
  123. "HDTV 1080": "1080p x264",
  124. "HDTV 720": "720p x264",
  125. "x265": "x265"
  126. }
  127. },
  128. {
  129. "name": "NZBClub",
  130. "url": "https://www.nzbclub.com/search.aspx?q={show} {season_episode} {quality}&szs=20&sze=24&st=1&sp=1&sn=1",
  131. "quality": {
  132. "WEB-DL": "WEB-DL",
  133. "HDTV 1080": "1080p x264",
  134. "HDTV 720": "720p x264",
  135. "x265": "x265"
  136. }
  137. },
  138. {
  139. "name": "BinSearch",
  140. "url": "https://binsearch.info/index.php?q={show} {season_episode} {quality}&max=25&adv_age=999&adv_sort=date&adv_col=on&font=small",
  141. "quality": {
  142. "WEB-DL": "720p|1080p WEB-DL",
  143. "HDTV 1080": "1080p x264",
  144. "HDTV 720": "720p x264",
  145. "x265": "x265"
  146. }
  147. }
  148. ]
  149. };
  150. mapping = {
  151. sub_lang_nl: "Ext.SF.SubLanguage_NL",
  152. sub_lang_en: "Ext.SF.SubLanguage_US",
  153. sub_source_addic7ed: "Ext.SF.SubProvider_Addic7ed",
  154. sub_source_podnapisi: "Ext.SF.SubProvider_PodNapisi",
  155. sub_source_opensubtitles: "Ext.SF.SubProvider_OpenSubTitles",
  156. sub_source_subtitleseeker: "Ext.SF.SubProvider_SubtitleSeeker",
  157. dl_providers: "Ext.SF.MediaProviders",
  158. dl_quality: "Ext.SF.MediaQuality"
  159. };
  160. // Initialize functions
  161. show = function () {
  162. var css, html, div, subFrames, idx, inputs;
  163. if (document.getElementById(configElementName)) {
  164. close();
  165. return;
  166. }
  167. css = ' ' +
  168. '.h3subframe { margin: 1px 0 0px; padding: 1px 10px; border-bottom: 1px solid #bbb; font-size: 1.5em; font-weight: normal; cursor:pointer; background:#DDDDDD none repeat scroll 0 0; } ' +
  169. '.h3subframe:hover { background:#C0BEBE none repeat scroll 0 0; } ' +
  170. '#h3subframetitle { margin: 2px 0 0px; padding: 7px 10px; border-bottom: 1px solid #bbb; font-size: 2.0em; font-weight: normal; } ' +
  171. '.popup a { color: darkblue; text-decoration: none; } ' +
  172. '.popup p { padding: 1px 10px; margin: 0px 0; font-family:arial,helvetica,sans-serif; font-size:10pt; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal; line-height:normal; } ' +
  173. '.sidebyside { padding: 1px 10px; margin: 0px 0;display:inline-block;width:17em; } ' +
  174. '.h3subframecontent { overflow:auto; display: none; padding: 10px 10px; } ' +
  175. '.showinfo { font-size:14px; } ' +
  176. 'textarea.valid, textarea.valid:focus { border: 2px solid green; } ' +
  177. 'textarea.invalid, textarea.invalid:focus { border: 2px solid red; }';
  178. GM_addStyle(css);
  179.  
  180. html =
  181. '<div id="fade" style="background: #000;height: 100%;opacity: .80;"></div>' +
  182. '<div style="font-family: verdana; color: black; background: #ddd; padding: 10px 20px; border: 10px solid #fff; float: left; width: 731px; position: absolute; top: 2%; left: 40%; margin: 0 0 0 -292px; border-radius: 10px; z-index: 100;">' +
  183. ' <div class="popup" style="float: left; width: 100%; background: #fff; margin: 10px 0; padding: 0px 0 0px; border-left: 1px solid #bbb; border-top: 1px solid #bbb; border-right: 1px solid #bbb;">' +
  184. ' <a href="#" onclick="javascript:return false;">' +
  185. ' <img id="' + configElementName + '_close" style="border:none; position: absolute; right: -20px; top: -20px;" title="Close" src="data:image/png;base64,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%3D%3D"/>' +
  186. ' </a>' +
  187. ' <div id="h3subframetitle"><b>Seriesfeed++ - Preferences</b></div>' +
  188. ' <div id="h3subframe1" class="h3subframe">Languages</div>' +
  189. ' <div class="h3subframecontent">' +
  190. ' <p class="showinfo">Choose the <b>subtitle languages</b> you want to find</p><br>' +
  191. ' <p><input type="checkbox" id="sub_lang_nl" /> Nederlands <img src="' + flags.nl + '"/></p>' +
  192. ' <p><input type="checkbox" id="sub_lang_en" /> English <img src="' + flags.en + '"/></p>' +
  193. ' </div>' +
  194. ' <div id="h3subframe2" class="h3subframe">Subtitles</div>' +
  195. ' <div class="h3subframecontent">' +
  196. ' <p class="showinfo">Choose the <b>subtitle sites</b> you want as option</p><br>' +
  197. ' <p><input type="checkbox" id="sub_source_addic7ed" /> Addic7eD <font color="gray">(preferred)</font></p>' +
  198. ' <p><input type="checkbox" id="sub_source_podnapisi" /> PodNapisi</p>' +
  199. ' <p><input type="checkbox" id="sub_source_opensubtitles" /> OpenSubtitles</p>' +
  200. ' <p><input type="checkbox" id="sub_source_subtitleseeker" /> SubTitleSeeker <font color="gray">(can be unsafe)</font></p>' +
  201. ' </div>' +
  202. ' <div id="h3subframe3" class="h3subframe">Media</div>' +
  203. ' <div class="h3subframecontent">' +
  204. ' <p class="showinfo">For examples of the configuration, see the <a target="_blank" href="https://greasyfork.org/en/scripts/14722-seriesfeed">Greasyfork</a> website</p>' +
  205. ' <p class="showinfo">Enter the <b>media formats</b> you want to have links for</p><br>' +
  206. ' <textarea cols="100" rows="2" class="valid" id="config_dl_quality">' + JSON.stringify(preferences.dl_quality, null, 1) + '</textarea><br>Config will not be stored unless the border is green.<br><br>' +
  207. ' <p class="showinfo">Enter the configuration for the <b>media providers</b> you want to use</p><br>' +
  208. ' <textarea cols="100" rows="20" class="valid" id="config_dl_providers">' + JSON.stringify(preferences.dl_providers, null, '\t') + '</textarea><br>Config will not be stored unless the border is green.<br><br>' +
  209. ' </div>' +
  210. ' <div id="h3subframe4" class="h3subframe">About &amp; Help</div>' +
  211. ' <div class="h3subframecontent">' +
  212. ' <p><b>' + GM_info.script.name + '</b> - version: ' + GM_info.script.version + '</p>' +
  213. ' <br />' +
  214. ' <p>' + GM_info.script.description + '</p><br>' +
  215. ' <p>Author: Mr. Invisible (mrinvisible@cryptolab.net) - original plugin by: XppX</p>' +
  216. ' <p>License: GPL</p><br><br>' +
  217. ' <p><b>In need of help</b>? Visit the script page on <a target="_blank" href="https://greasyfork.org/en/scripts/14722-seriesfeed">Greasyfork</a>.</p>' +
  218. ' </div>' +
  219. ' </div>' +
  220. '</div>';
  221.  
  222. div = document.createElement("div");
  223. div.id = configElementName;
  224. div.setAttribute('style',
  225. 'visibility: visible;position: fixed;width: 100%;height: 100%;top: 0;left: 0;font-size:12px;' +
  226. 'z-index:1001;text-align:left;');
  227. div.innerHTML = html;
  228. document.body.appendChild(div);
  229. document.getElementById(configElementName + "_close").addEventListener("click", close, false);
  230.  
  231. // Loop through checkboxes to populate them
  232. inputs = div.getElementsByTagName("input");
  233. for (idx = 0; idx < inputs.length; idx++) {
  234. if (inputs[idx].type === "checkbox") {
  235. if (preferences.hasOwnProperty(inputs[idx].id) && preferences[inputs[idx].id]) {
  236. inputs[idx].setAttribute("checked", "checked");
  237. }
  238. // Add a listener to each checkbox
  239. inputs[idx].addEventListener("click", changeConfiguration, false);
  240. }
  241. }
  242. inputs = div.getElementsByTagName('textarea');
  243. for (idx = 0; idx < inputs.length; idx++) {
  244. // Add a listener to each text area
  245. inputs[idx].addEventListener("change", checkConfiguration, false);
  246. inputs[idx].addEventListener("keyup", checkConfiguration, false);
  247. }
  248.  
  249. // Add event listeners for opening when a click on the head is performed
  250. subFrames = document.getElementsByClassName("h3subframe");
  251. for (idx = 0; idx < subFrames.length; idx++) {
  252. subFrames[idx].addEventListener("click", openSubConfig, false);
  253. }
  254. // Unfold the first one
  255. openSubConfig({
  256. target: document.getElementById('h3subframe1')
  257. });
  258. };
  259. close = function () {
  260. var box = document.getElementById(configElementName);
  261. box.parentNode.removeChild(box);
  262. window.location.reload(false);
  263. };
  264. closeOtherSubConfigs = function (evt) {
  265. var ignore, subFrames, idx;
  266. ignore = evt.target || evt.srcElement;
  267. subFrames = document.getElementsByClassName("h3subframe");
  268. for (idx = 0; idx < subFrames.length; idx++) {
  269. if (ignore !== subFrames[idx]) {
  270. subFrames[idx].nextElementSibling.style.display = "none";
  271. subFrames[idx].addEventListener("click", openSubConfig, false);
  272. }
  273. }
  274. };
  275. closeSubConfig = function (e) {
  276. var evt, target;
  277.  
  278. evt = e || window.event;
  279. target = evt.target || evt.srcElement;
  280. target.removeEventListener("click", closeSubConfig, false);
  281. target.nextElementSibling.style.display = "none";
  282. target.addEventListener("click", openSubConfig, false);
  283. };
  284. openSubConfig = function (e) {
  285. var evt, target;
  286.  
  287. evt = e || window.event;
  288. target = evt.target || evt.srcElement;
  289. target.removeEventListener("click", openSubConfig, false);
  290. target.nextElementSibling.style.display = "block";
  291. closeOtherSubConfigs(evt);
  292. target.addEventListener("click", closeSubConfig, false);
  293. };
  294. changeConfiguration = function (e) {
  295. if (e.target.tagName.toLowerCase() === 'input') {
  296. saveConfiguration(e.target.id, e.target.checked);
  297. }
  298. };
  299. checkConfiguration = function (e) {
  300. var json, idx;
  301.  
  302. if (debug) {
  303. console.log('Entering checkConfiguration');
  304. }
  305.  
  306. if (e.target.tagName.toLowerCase() === 'textarea' && e.target.id.indexOf("config_dl_") === 0) {
  307. e.target.classList.remove('valid');
  308. e.target.classList.add('invalid');
  309. try {
  310. json = JSON.parse(e.target.value);
  311. if ((e.target.id === "config_dl_quality" && !isValidQualityConfig(json)) ||
  312. (e.target.id === "config_dl_providers" && !isValidProvidersConfig(json))) {
  313. return;
  314. }
  315. e.target.classList.add('valid');
  316. e.target.classList.remove('invalid');
  317. saveConfiguration(e.target.id.replace("config_", ""), json);
  318. } catch (e) {
  319. if (debug) {
  320. console.log('Invalid JSON: '+ e);
  321. }
  322. }
  323. }
  324. };
  325. isValidQualityConfig = function (json) {
  326. if (! Array.isArray(json)) {
  327. if (debug) {
  328. console.log('Quality config is no array');
  329. }
  330. return false;
  331. }
  332. return true;
  333. };
  334. isValidProvidersConfig = function (json) {
  335. var idx;
  336. if (! Array.isArray(json)) {
  337. if (debug) {
  338. console.log('Quality providers is no array');
  339. }
  340. return false;
  341. }
  342. for (idx = 0; idx < json.length; idx++) {
  343. if (!isValidProviderConfig(json[idx])) {
  344. return false;
  345. }
  346. }
  347. return true;
  348. };
  349. isValidProviderConfig = function (json) {
  350. var idx;
  351.  
  352. if (!json.hasOwnProperty('name') || !json.hasOwnProperty('url') || !json.hasOwnProperty('quality')) {
  353. if (debug) {
  354. console.log('Provider config missing name, url or quality property.');
  355. }
  356. return false;
  357. }
  358.  
  359. for (idx = 0; idx < preferences.dl_quality.length; idx++) {
  360. if(!json.quality.hasOwnProperty(preferences.dl_quality[idx])) {
  361. if (debug) {
  362. console.log('Provider config missing quality entry.');
  363. }
  364. return false;
  365. }
  366. }
  367.  
  368. if (json.url.indexOf('{show}') === -1 || json.url.indexOf('{season_episode}') === -1 || json.url.indexOf('{quality}') === -1) {
  369. if (debug) {
  370. console.log('Provider config url missing {show}, {season_episode} or {quality} section.');
  371. }
  372. return false;
  373. }
  374.  
  375. if (json.hasOwnProperty('invalid_characters')) {
  376. if (! json.invalid_characters.hasOwnProperty('old') || ! json.invalid_characters.hasOwnProperty('new') ||
  377. ! Array.isArray(json.invalid_characters.new) || ! Array.isArray(json.invalid_characters.old) ||
  378. json.invalid_characters.new.length !== json.invalid_characters.old.length
  379. ) {
  380. if (debug) {
  381. console.log('Provider config invalid_characters not provided, no array or length not equal between old & new.');
  382. }
  383. return false;
  384. }
  385. }
  386.  
  387. return true;
  388. };
  389. saveConfiguration = function (id, value) {
  390. if (preferences.hasOwnProperty(id)) {
  391. preferences[id] = value;
  392. GM_setValue(mapping[id], value);
  393. }
  394. };
  395. loadPreferences = function () {
  396. var key;
  397. if (debug) {
  398. window.console.log("Entering load preferences function");
  399. }
  400. if (debug) {
  401. window.console.log("Preferences (default):");
  402. window.console.log(preferences);
  403. }
  404.  
  405. for (key in mapping) {
  406. if (mapping.hasOwnProperty(key) && preferences.hasOwnProperty(key)) {
  407. preferences[key] = GM_getValue(mapping[key], preferences[key]);
  408. }
  409. }
  410.  
  411. if (debug) {
  412. window.console.log("Preferences (loaded):");
  413. window.console.log(preferences);
  414. }
  415. };
  416. getConfigValue = function (name) {
  417. if (preferences.hasOwnProperty(name)) {
  418. return preferences[name];
  419. }
  420. return null;
  421. };
  422. getEnabledSubtitleLanguages = function () {
  423. var result = [];
  424.  
  425. if (preferences.sub_lang_en) {
  426. result.push("en");
  427. }
  428. if (preferences.sub_lang_nl) {
  429. result.push("nl");
  430. }
  431.  
  432. return result;
  433. };
  434. getEnabledSubtitleSources = function () {
  435. var result = [];
  436.  
  437. if (preferences.sub_source_addic7ed) {
  438. result.push(subProviders.sub_source_addic7ed);
  439. }
  440. if (preferences.sub_source_podnapisi) {
  441. result.push(subProviders.sub_source_podnapisi);
  442. }
  443. if (preferences.sub_source_opensubtitles) {
  444. result.push(subProviders.sub_source_opensubtitles);
  445. }
  446. if (preferences.sub_source_subtitleseeker) {
  447. result.push(subProviders.sub_source_subtitleseeker);
  448. }
  449.  
  450. return result;
  451. };
  452. getEnabledDownloadProviders = function () {
  453. return preferences.dl_providers;
  454. };
  455. getEnabledMediaQualities = function () {
  456. return preferences.dl_quality;
  457. };
  458. // Initialize object to return and expose appropriate methods
  459. instance = {};
  460. instance.show = show;
  461. instance.loadPreferences = loadPreferences;
  462. instance.getConfigValue = getConfigValue;
  463. instance.getEnabledSubtitleLanguages = getEnabledSubtitleLanguages;
  464. instance.getEnabledSubtitleSources = getEnabledSubtitleSources;
  465. instance.getEnabledDownloadProviders = getEnabledDownloadProviders;
  466. instance.getEnabledMediaQualities = getEnabledMediaQualities;
  467.  
  468. return instance;
  469. }());
  470. // Initialize variables
  471. debug = false;
  472. // Maps short language keywords to the full English language
  473. languageMap = {
  474. "en": "English",
  475. "nl": "Dutch"
  476. };
  477. // Providers, keys of this MUST be equal to the ones in the configDialog.preferences variable
  478. subProviders = {
  479. sub_source_addic7ed: {
  480. title: "Addic7eD",
  481. createLink: function (showName, showEpisode, language) {
  482. var showNameConverted, showEpisodeConverted, languageConverted;
  483. // Convert show name & show episode to appropriate formats
  484. showNameConverted = this.showConversion(showName);
  485. showEpisodeConverted = this.episodeConversion(showEpisode);
  486. languageConverted = this.languageConversion(language);
  487.  
  488. return "http://www.addic7ed.com/serie/" + showNameConverted + "/" + showEpisodeConverted.season + "/" +
  489. showEpisodeConverted.episode + "/" + languageConverted;
  490. },
  491. showConversion: function (show) {
  492. var exceptions;
  493.  
  494. show = show.replace(/ /g, "_");
  495. // Exception map for shows
  496. exceptions = {
  497. "The_Flash": "The_Flash_(2014)",
  498. "Legends_of_Tomorrow": "DC's_Legends_of_Tomorrow",
  499. "Marvel's_Daredevil": "Daredevil"
  500. };
  501. if (exceptions.hasOwnProperty(show)) {
  502. show = exceptions[show];
  503. }
  504. return show;
  505. },
  506. episodeConversion: function (episode) { return parseEpisode(episode); },
  507. languageConversion: function (language) {
  508. switch (language) {
  509. case "nl":
  510. return "17";
  511. case "en":
  512. return "1";
  513. }
  514. return language;
  515. }
  516. },
  517. sub_source_podnapisi: {
  518. title: "PodNapisi",
  519. createLink: function (showName, showEpisode, language) {
  520. var showNameConverted, showEpisodeConverted, languageConverted;
  521. // Convert show name & show episode to appropriate formats
  522. showNameConverted = this.showConversion(showName);
  523. showEpisodeConverted = this.episodeConversion(showEpisode);
  524. languageConverted = this.languageConversion(language);
  525.  
  526. return "http://www.podnapisi.net/subtitles/search/advanced?keywords=" + showNameConverted + "&seasons="
  527. + showEpisodeConverted.season + "&episodes=" + showEpisodeConverted.episode + "&language=" +
  528. languageConverted;
  529. },
  530. showConversion: function (show) {
  531. var exceptions;
  532.  
  533. show = show.replace(/ /g, "+");
  534. // Exception map for shows
  535. exceptions = {};
  536. if (exceptions.hasOwnProperty(show)) {
  537. show = exceptions[show];
  538. }
  539. return show;
  540. },
  541. episodeConversion: function (episode) { return parseEpisode(episode); },
  542. languageConversion: function (language) { return language; }
  543. },
  544. sub_source_opensubtitles: {
  545. title: "OpenSubtitles",
  546. createLink: function (showName, showEpisode, language) {
  547. var showNameConverted, showEpisodeConverted, languageConverted;
  548. // Convert show name & show episode to appropriate formats
  549. showNameConverted = this.showConversion(showName);
  550. showEpisodeConverted = this.episodeConversion(showEpisode);
  551. languageConverted = this.languageConversion(language);
  552.  
  553. return "http://www.openSubtitles.org/nl/search/searchonlytvseries-on/subformat-srt/sublanguageid-" +
  554. languageConverted + "/season-" + showEpisodeConverted.season + "/episode-" +
  555. showEpisodeConverted.episode + "/moviename-" + showNameConverted;
  556. },
  557. showConversion: function (show) {
  558. var exceptions;
  559.  
  560. show = show.replace(/ /g, "+");
  561. // Exception map for shows
  562. exceptions = {};
  563. if (exceptions.hasOwnProperty(show)) {
  564. show = exceptions[show];
  565. }
  566. return show;
  567. },
  568. episodeConversion: function (episode) { return parseEpisode(episode); },
  569. languageConversion: function (language) {
  570. switch (language) {
  571. case "nl":
  572. return "dut";
  573. case "en":
  574. return "eng";
  575. }
  576. }
  577. },
  578. sub_source_subtitleseeker: {
  579. title: "SubTitleSeeker",
  580. createLink: function (showName, showEpisode, language) {
  581. var showNameConverted, showEpisodeConverted, convertedLanguage;
  582. // Convert show name & show episode to appropriate formats
  583. showNameConverted = this.showConversion(showName);
  584. showEpisodeConverted = this.episodeConversion(showEpisode);
  585. convertedLanguage = this.languageConversion(language);
  586. if (debug) {
  587. window.console.log("Language is not used for subTitleSeeker: " + convertedLanguage);
  588. }
  589.  
  590. return "http://www.subtitleseeker.com/search/TV_EPISODES/" + showNameConverted + "+S" +
  591. showEpisodeConverted.season + "E" + showEpisodeConverted.episode;
  592. },
  593. showConversion: function (show) {
  594. var exceptions;
  595.  
  596. show = show.replace(/ /g, "+");
  597. // Exception map for shows
  598. exceptions = {};
  599. if (exceptions.hasOwnProperty(show)) {
  600. show = exceptions[show];
  601. }
  602. return show;
  603. },
  604. episodeConversion: function (episode) { return parseEpisode(episode); },
  605. languageConversion: function (language) { return language; }
  606. }
  607. };
  608. // Flags
  609. flags = {
  610. "nl": "data:image/gif;base64,R0lGODlhEAALANUAAAABdP7+/jRusgAhj/picxZYp/UAAPLy8vr6+vj4+Pb29v56iVqKw/1da0V7ujpytSpmrusAAGSSx/pCUftUZP11gwAAR/96hgAAWe7v7yFgq0J3tw1Rokt/vAAAPFCCv1SFwS9qrtHR0fk8Tf6CkdXV1ftqevcxQvg3Rz52uPxMWvVWavZabv8VMflXaP5ZZ3eezv6XpC5qsvYsPPv7+/pQX/5/j/htfvlIV/T19fxwgPX19fz8/P0AAPT09P8AACH5BAAAAAAALAAAAAAQAAsAAAaFwJ/w1ysWDUhkJPJbLC6Vis5kIhAaLkqNeWk0XloVbjJCnWa4SCtGsjmltyqBtbpFDoE8j0dDIBIJCjs+OyUZenx+gII+Bzkih3t9f4GDBwc+IgMwDCAfHQ4bDwIhEBAaGxYAEg4pozKnGgUFHBwhHqsMnZ+hrgKkELgAwxjFxRbIHsoeQQA7",
  611. "en": "data:image/gif;base64,R0lGODlhEAALANUAAGNjtvr6+vb29fHx8fxGRvkQEP15eatjSuNjSjx6+TV0+f2Kd+np6dvb2PxUVPx4YttjSvsrK5q7/PxlZrpjSvw6Oubm5e7u7sljSgR09Z1jSvT080iB+gBMtkF9+vofHwBpu/f39uLi3/syMtVjSqG+/C9y+Ozs7KLA/GNlwPyBbO3t6gNx5QArs/2XhpGRgKysn+z+/rS0qPR0XuXl/P7KwcnJwf39Wzh3+f7+/fv/+vHx7wNv4df+/f3h3GNnxCH5BAAAAAAALAAAAAAQAAsAAAaMwJ/wl0oBjqoHYgmB/HIoXSkgoQYCoawA9kNxOJ5EAmcyTByEiouUmnoEiYspMxhcTreNrR1OKOYgaBUjEQsYAAEJAwonLCIddwwMFj0yABI4fzwgHS2DER8fCwciAQIbG3aRkxYNPi8qNWZoaRGgBbgqGg0BMVqoqTsrNBYiM0sITU0kGBQUBwca0kEAOw=="
  612. };
  613. // Page regexes
  614. pageRegexes = {
  615. // Seriesfeed homepage
  616. start: new RegExp("^/$"),
  617. // Broadcast schedule (format: series/uitzendlijst/[{month}/]* )
  618. broadcast: new RegExp("^.*/series/uitzendlijst(/[a-z]+)*/$"),
  619. // Watchlist (format: series/kijklijst/[topshows/|favorieten/]*
  620. watch: new RegExp("^.*/series/kijklijst/([topshows|favorieten]+/)*$"),
  621. // Episodes/Seasons (format: series/{name}/afleveringen/[seizoen/{nr}/]* )
  622. season: new RegExp("^.*/series/(.+)/afleveringen/(seizoen/[0-9]+/)*$"),
  623. // Episode (format: series/aflevering/{nr}/ )
  624. episode: new RegExp("^.*/series/aflevering/[0-9]+/?$")
  625. };
  626. // Current page
  627. currentPage = null;
  628.  
  629. // Initialize functions
  630. main = function () {
  631. if (debug) {
  632. window.console.log("Entering main function");
  633. }
  634. // Load preferences
  635. configDialog.loadPreferences();
  636. // Register GreaseMonkey menu entry
  637. try {
  638. GM_registerMenuCommand("[Seriesfeed++] Configuratie", configDialog.show, "C");
  639. } catch (e) {
  640. window.console.warn(
  641. "Could not register GM menu handler. This is normal if run outside of GreaseMonkey.", e);
  642. }
  643. // Inject config menu item
  644. injectMenuItem();
  645. // Check page we're on
  646. checkPage();
  647. // If the page is still null at this point, we didn't identify the page.
  648. if (currentPage === null) {
  649. window.console.warn("Did not identify a page to run on. Not executing any more page alterations.");
  650. return;
  651. }
  652. // Inject some css
  653. GM_addStyle('.ui-front { z-index: 1000 !important; }');
  654. // Modify the page
  655. modifyPage();
  656. };
  657. checkPage = function () {
  658. var key, found;
  659. if (debug) {
  660. window.console.log("Entering checkPage function");
  661. }
  662.  
  663. found = null;
  664. for (key in pageRegexes) {
  665. if (pageRegexes.hasOwnProperty(key)) {
  666. if (debug) {
  667. window.console.log("Trying to match " + pageRegexes[key] + " to " + window.location.pathname);
  668. }
  669. if (pageRegexes[key].exec(window.location.pathname)) {
  670. if (debug) {
  671. window.console.log("Match found for " + key);
  672. }
  673. found = key;
  674. break;
  675. }
  676. }
  677. }
  678. currentPage = found;
  679. };
  680. injectMenuItem = function () {
  681. var idx, links, li, menu, inject, injectLink;
  682. if (debug) {
  683. window.console.log("Entering injectMenuItem function");
  684. }
  685. // There are no id's used, so we'll hook on to some text contents in the page
  686. links = document.getElementsByTagName("a");
  687. for (idx = 0; idx < links.length; idx++) {
  688. if (links[idx].innerHTML === "Profiel wijzigen") {
  689. // Might have a match, verify "menu" element above
  690. li = links[idx].parentNode;
  691. menu = li.parentNode;
  692. if (menu.classList.contains("dropdown-menu")) {
  693. // We can assume safely that we're in a menu. Inject menu item
  694. inject = document.createElement("li");
  695. injectLink = document.createElement("a");
  696. injectLink.innerHTML = "Seriesfeed++ configureren";
  697. injectLink.addEventListener("click", configDialog.show, false);
  698. inject.appendChild(injectLink);
  699. menu.appendChild(inject);
  700. }
  701. }
  702. }
  703. };
  704. modifyPage = function () {
  705. if (debug) {
  706. window.console.log("Entering modifyPage function");
  707. }
  708. // Depending on the type of the page, we need to render differently
  709. switch (currentPage) {
  710. case "start":
  711. handleStartPage();
  712. break;
  713. case "broadcast":
  714. handleBroadcastPage();
  715. break;
  716. case "watch":
  717. handleWatchlistPage();
  718. break;
  719. case "season":
  720. handleSeasonPage();
  721. break;
  722. case "episode":
  723. handleEpisodePage();
  724. break;
  725. default:
  726. window.console.warn("Did not identify a page to run on. Not executing any more page alterations.");
  727. }
  728. // Append css for jquery UI
  729. $("head").append('<link href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css"' +
  730. ' rel="stylesheet" type="text/css">');
  731. };
  732. // Page specific modifications
  733. handleStartPage = function () {
  734. if (debug) {
  735. window.console.log("Entering handleStartPage function");
  736. }
  737. // There is one table of interest: latest favourites. As of 1.3 it can be missing if there's no episodes
  738. injectDefaultTable("favourite_episodes");
  739. };
  740. handleBroadcastPage = function () {
  741. if (debug) {
  742. window.console.log("Entering handleBroadcastPage function");
  743. }
  744. // Single table: broadcasted episodes
  745. injectDefaultTable("afleveringen");
  746. };
  747. handleWatchlistPage = function () {
  748. if (debug) {
  749. window.console.log("Entering handleWatchlistPage function");
  750. }
  751. // Single table: favourites/popular episodes
  752. injectDefaultTable("afleveringen");
  753. };
  754. handleSeasonPage = function () {
  755. var table, showName;
  756. if (debug) {
  757. window.console.log("Entering handleSeasonPage function");
  758. }
  759. // Get show name
  760. showName = document.getElementById('seriesName').value;
  761.  
  762. // Single table: show episodes
  763. table = $("#afleveringen");
  764. // Inject element for header
  765. injectTableHeader("afleveringen");
  766. // Inject icons in rows
  767. table.find("tbody tr.light").each(function (idx, elm) {
  768. var td, cells, showEpisode;
  769. if (debug) {
  770. window.console.log("Processing row " + idx);
  771. }
  772. td = document.createElement("td");
  773. cells = elm.getElementsByTagName("td");
  774. showEpisode = cells[0].firstElementChild.innerHTML;
  775. td.appendChild(createFunctionality(showName, showEpisode));
  776. elm.appendChild(td);
  777. });
  778. };
  779. handleEpisodePage = function () {
  780. var table, row, cell, data, showName, showEpisode;
  781.  
  782. if (debug) {
  783. window.console.log("Entering handleEpisodePage function");
  784. }
  785. // Need to inject new row instead of cell
  786. table = $("#episodeInfo");
  787. data = table.find("thead th").first().html().trim();
  788. data = data.match(/([A-Za-z ]*) \([0-9]{4}\) : (.*)/);
  789. showName = data[1];
  790. showEpisode = data[2];
  791. // Inject
  792. row = document.createElement('tr');
  793. cell = document.createElement('td');
  794. cell.innerHTML = 'Seriesfeed++';
  795. row.appendChild(cell);
  796. cell = document.createElement('td');
  797. cell.appendChild(createFunctionality(showName, showEpisode));
  798. row.appendChild(cell);
  799. table.find("tbody").append(row);
  800. };
  801. // General modification methods
  802. injectTableHeader = function (tableId) {
  803. var table, th;
  804. if (debug) {
  805. window.console.log("Entering injectTableHeader function");
  806. }
  807.  
  808. table = $("#" + tableId);
  809. // Inject element for header
  810. th = document.createElement("th");
  811. th.innerHTML = "Seriesfeed++";
  812. table.find("thead tr")[0].appendChild(th);
  813. };
  814. injectDefaultTable = function (tableId) {
  815. var table, colspan, readMore;
  816. if (debug) {
  817. window.console.log("Entering injectDefaultTable function");
  818. }
  819.  
  820. table = $("#" + tableId);
  821. // Check if element actually exists
  822. if (table.length === 0) {
  823. if (debug) {
  824. window.console.log("Did not find a table with the id: " + tableId);
  825. }
  826. return;
  827. }
  828. // Inject element for header
  829. injectTableHeader(tableId);
  830. // Inject icons in rows
  831. table.find("tbody tr").not('.readMore').each(function (idx, elm) {
  832. var td, cells, showName, showEpisode;
  833. if (debug) {
  834. window.console.log("Processing row" + idx);
  835. }
  836. if ($(elm).attr('data-aired') === undefined) {
  837. if (debug) {
  838. window.console.log("Skipping row because it has no data-aired attribute");
  839. }
  840. return;
  841. }
  842. td = document.createElement("td");
  843. cells = elm.getElementsByTagName("td");
  844. showName = cells[0].firstElementChild.innerHTML;
  845. showEpisode = cells[1].firstElementChild.innerHTML;
  846. td.appendChild(createFunctionality(showName, showEpisode));
  847. elm.appendChild(td);
  848. });
  849. readMore = table.find("tbody tr.readMore td");
  850. colspan = parseInt(readMore.attr("colspan"), 10);
  851. readMore.attr('colspan', colspan + 1);
  852. };
  853. createFunctionality = function (showName, showEpisode) {
  854. var span, languages, idx, downloadProviders, downloadTypes, downloadIcon;
  855. if(debug){
  856. console.log(
  857. "Entering createFunctionality with parameters: showName: "+showName+", showEpisode: "+showEpisode);
  858. }
  859.  
  860. span = document.createElement("span");
  861. // Add language flags
  862. languages = configDialog.getEnabledSubtitleLanguages();
  863. for (idx = 0; idx < languages.length; idx++) {
  864. span.appendChild(createLanguageFlag(languages[idx], showName, showEpisode));
  865. span.appendChild(document.createTextNode(" "));
  866. }
  867. downloadProviders = configDialog.getEnabledDownloadProviders();
  868. downloadTypes = configDialog.getEnabledMediaQualities();
  869. if (downloadProviders.length > 0 && downloadTypes.length > 0) {
  870. downloadIcon = document.createElement("i");
  871. downloadIcon.setAttribute('class','fa fa-download');
  872. downloadIcon.setAttribute('style', 'display:inline-block; font-size: 19px; cursor: pointer;');
  873. downloadIcon.title = "download episode";
  874. downloadIcon.addEventListener("click", showDlSelectionDialog);
  875.  
  876. span.appendChild(downloadIcon);
  877. }
  878. return span;
  879. };
  880. createLanguageFlag = function (lang, showName, showEpisode) {
  881. var result, img, subSources;
  882. if(debug){
  883. console.log(
  884. "Entering createLanguageFlag with parameters: lang: " + lang + ", showName: " + showName +
  885. ", showEpisode: " + showEpisode);
  886. }
  887.  
  888. if (!flags.hasOwnProperty(lang)) {
  889. throw new Error(lang + "is not a recognized language flag!");
  890. }
  891.  
  892. img = document.createElement("img");
  893. img.src = flags[lang];
  894. img.alt = lang + " flag";
  895. img.title = languageMap[lang] + " subtitles";
  896. img.setAttribute("data-language", lang);
  897. img.setAttribute('style', 'height: 16px; vertical-align:top;');
  898. // If there's just one subtitle source, make it a link, otherwise make it a pop-up menu
  899. subSources = configDialog.getEnabledSubtitleSources();
  900. if (subSources.length > 1) {
  901. img.addEventListener("click", showSubSelectionDialog, false);
  902. result = img;
  903. } else {
  904. result = document.createElement("a");
  905. result.href = subSources[0].createLink(showName, showEpisode, lang);
  906. result.target = "_blank";
  907. result.appendChild(img);
  908. }
  909.  
  910. return result;
  911. };
  912. showSubSelectionDialog = function (e) {
  913. var evt, target, dialog, subSources, row, showName, showEpisode, lang, cells, idx, link, p, thead, data;
  914.  
  915. evt = e || window.event;
  916. target = evt.target || evt.srcElement;
  917.  
  918. // Get language
  919. lang = target.getAttribute("data-language");
  920. // Get row, so we can extract show name & episode
  921. row = target.parentNode.parentNode.parentNode;
  922. cells = row.getElementsByTagName("td");
  923. if(currentPage === "season"){
  924. showName = document.getElementById('seriesName').value;
  925. showEpisode = cells[0].firstElementChild.innerHTML;
  926. } else if(currentPage === "episode") {
  927. thead = row.parentNode.previousElementSibling;
  928. data = thead.firstElementChild.firstElementChild.innerHTML.trim();
  929. data = data.match(/([A-Za-z ]*) \([0-9]{4}\) : (.*)/);
  930. showName = data[1];
  931. showEpisode = data[2];
  932. } else {
  933. showName = cells[0].firstElementChild.innerHTML;
  934. showEpisode = cells[1].firstElementChild.innerHTML;
  935. }
  936.  
  937. // Build dialog
  938. dialog = document.createElement("div");
  939. p = document.createElement("p");
  940. p.innerHTML = "Show: " + showName + "<br/>Episode: " + showEpisode;
  941. dialog.appendChild(p);
  942. p = document.createElement("p");
  943. // Get sub source sites
  944. subSources = configDialog.getEnabledSubtitleSources();
  945. for (idx = 0; idx < subSources.length; idx++) {
  946. link = document.createElement("a");
  947. link.target = "_blank";
  948. link.href = subSources[idx].createLink(showName, showEpisode, lang);
  949. link.innerHTML = subSources[idx].title;
  950. link.setAttribute("style","text-decoration: underline;");
  951. link.addEventListener("click", function () {
  952. $(dialog).dialog("close");
  953. }, false);
  954. p.appendChild(link);
  955. p.appendChild(document.createTextNode(" "));
  956. }
  957. dialog.appendChild(p);
  958. $(dialog).dialog({
  959. title: "Download " + languageMap[lang] + " subtitles",
  960. position: { my: "right bottom", at: "top left", of: target }
  961. });
  962. };
  963. showDlSelectionDialog = function (e) {
  964. var evt, target, dialog, row, cells, showName, showEpisode, p, mediaQuality, mediaProviders, idx, jdx,
  965. table_head, data, providers;
  966.  
  967. evt = e || window.event;
  968. target = evt.target || evt.srcElement;
  969.  
  970. // Get row, so we can extract show name & episode
  971. row = target.parentNode.parentNode.parentNode;
  972. cells = row.getElementsByTagName("td");
  973. if(currentPage === "season") {
  974. showName = document.getElementById('seriesName').value;
  975. showEpisode = cells[0].firstElementChild.innerHTML;
  976. } else if(currentPage === "episode") {
  977. table_head = row.parentNode.previousElementSibling;
  978. data = table_head.firstElementChild.firstElementChild.innerHTML.trim();
  979. data = data.match(/([A-Za-z ]*) \([0-9]{4}\) : (.*)/);
  980. showName = data[1];
  981. showEpisode = data[2];
  982. } else {
  983. showName = cells[0].firstElementChild.innerHTML;
  984. showEpisode = cells[1].firstElementChild.innerHTML;
  985. }
  986.  
  987. dialog = document.createElement("div");
  988. p = document.createElement("p");
  989. p.innerHTML = "Show: " + showName + "<br/>Episode: " + showEpisode;
  990. dialog.appendChild(p);
  991. p = document.createElement("p");
  992. // Get types & sites
  993. mediaProviders = configDialog.getEnabledDownloadProviders();
  994. mediaQuality = configDialog.getEnabledMediaQualities();
  995. providers = mediaProviders.length;
  996. for (idx = 0; idx < mediaQuality.length; idx++) {
  997. p = document.createElement("p");
  998. p.appendChild(document.createTextNode(mediaQuality[idx]));
  999. dialog.appendChild(p);
  1000. p = document.createElement("p");
  1001. for (jdx = 0; jdx < providers; jdx++) {
  1002. if(mediaProviders[jdx].quality.hasOwnProperty(mediaQuality[idx])) {
  1003. p.appendChild(createMediaLink(mediaProviders[jdx], showName, showEpisode, mediaQuality[idx], dialog));
  1004. if(jdx < providers - 1) {
  1005. p.appendChild(document.createTextNode(", "));
  1006. }
  1007. }
  1008. }
  1009. dialog.appendChild(p);
  1010. }
  1011.  
  1012. $(dialog).dialog({
  1013. title: "Download episode",
  1014. position: { my: "right bottom", at: "top left", of: target }
  1015. });
  1016. };
  1017. // Helper functions
  1018. parseEpisode = function (showEpisode) {
  1019. var result, regex, match;
  1020. if (debug) {
  1021. window.console.log("Entering parseEpisode function");
  1022. }
  1023.  
  1024. result = {
  1025. season: 0,
  1026. episode: 0,
  1027. title: ""
  1028. };
  1029. regex = new RegExp("S([0-9]+)E([0-9]+) - (.+)");
  1030. // Epected format: SxEy - episode title
  1031. match = regex.exec(showEpisode);
  1032. if (match !== null) {
  1033. result.season = parseInt(match[1], 10);
  1034. result.episode = parseInt(match[2], 10);
  1035. result.title = match[3];
  1036. } else {
  1037. window.console.warn("Could not parse " + showEpisode + " correctly!");
  1038. }
  1039. return result;
  1040. };
  1041. createMediaLink = function (mediaProviderConfig, showName, showEpisode, mediaType, dialog) {
  1042. var a, idx, episodeData, closeDialog, quality;
  1043.  
  1044. quality = mediaProviderConfig.quality[mediaType];
  1045. if (mediaProviderConfig.hasOwnProperty('invalid_characters')) {
  1046. for (idx = 0; idx < mediaProviderConfig.invalid_characters.old.length; idx++) {
  1047. showName = showName.replace(
  1048. mediaProviderConfig.invalid_characters.old[idx],
  1049. mediaProviderConfig.invalid_characters.new[idx]
  1050. );
  1051. quality = quality.replace(
  1052. mediaProviderConfig.invalid_characters.old[idx],
  1053. mediaProviderConfig.invalid_characters.new[idx]
  1054. );
  1055. }
  1056. }
  1057. episodeData = parseEpisode(showEpisode);
  1058. if (mediaProviderConfig.hasOwnProperty('episodeCharacter')){
  1059. showEpisode = formatToConvention(episodeData, mediaProviderConfig.episodeCharacter);
  1060. } else {
  1061. showEpisode = formatToConvention(episodeData);
  1062. }
  1063.  
  1064.  
  1065. closeDialog = function () {
  1066. $(dialog).dialog("close");
  1067. };
  1068. a = document.createElement("a");
  1069. a.href = mediaProviderConfig.url.replace('{show}', encodeURIComponent(showName)).replace('{season_episode}', encodeURIComponent(showEpisode)).replace('{quality}', encodeURIComponent(quality));
  1070. a.target = "_blank";
  1071. a.innerHTML = mediaProviderConfig.name;
  1072. a.setAttribute("style","text-decoration: underline;");
  1073. a.addEventListener("mouseup", closeDialog, false);
  1074.  
  1075. return a;
  1076. };
  1077. formatToConvention = function (episodeData, episodeCharacter) {
  1078. episodeCharacter = episodeCharacter || "E";
  1079. return "S" + ((episodeData.season < 10) ? "0" : "") + episodeData.season + episodeCharacter +
  1080. ((episodeData.episode < 10) ? "0" : "") + episodeData.episode;
  1081. };
  1082.  
  1083. // Expose methods to the outside world
  1084. seriesFeedPlusPlus.main = main;
  1085.  
  1086. return seriesFeedPlusPlus;
  1087. }());
  1088.  
  1089. // Execute main
  1090. try {
  1091. seriesFeedPlusPlus.main();
  1092. } catch (e) {
  1093. console.log(e);
  1094. console.log(e.stack);
  1095. // Display error
  1096. var txt = "An error occurred while executing this script.\n\n";
  1097. txt += "Issue: <<<" + e.message + ">>>\n\n";
  1098. txt += "\nPlease report this back to the author (on the greasyfork website, or by sending me an email at mrinvisible@cryptolab.net) so it can be corrected.\n\n";
  1099. txt += "Click 'OK' to continue.\n\n";
  1100. window.alert(txt);
  1101. }