Seriesfeed++

A fork of Bierdopje AddOn Plus for Seriesfeed

当前为 2017-11-17 提交的版本,查看 最新版本

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