Empornium Deluxe Mode

Enhances the empornium.me porn torrent website

Fra og med 22.01.2023. Se den nyeste version.

  1. // ==UserScript==
  2. // @name Empornium Deluxe Mode
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.16
  5. // @description Enhances the empornium.me porn torrent website
  6. // @author codingjoe
  7. // @match https://*.empornium.me/*
  8. // @match https://*.empornium.sx/*
  9. // @match https://*.empornium.is/*
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_addStyle
  13. // @grant GM_registerMenuCommand
  14. // @run-at document-idle
  15. // ==/UserScript==
  16.  
  17. let layout = {
  18. "General": {
  19. "AlwaysHeader": {
  20. type: "checkbox",
  21. text: "Always keep header in view",
  22. defaultValue: false
  23. },
  24. "AutoDismiss": {
  25. type: "checkbox",
  26. text: "Auto-dismiss login timeout notification",
  27. defaultValue: false
  28. },
  29. "JumpToTop": {
  30. type: "checkbox",
  31. text: "Insert 'Jump to Top' link onto bottom-right corner of all pages",
  32. defaultValue: false
  33. },
  34. "PreventNewWindow": {
  35. type: "checkbox",
  36. text: "Prevent single-click links from opening a new window",
  37. defaultValue: false
  38. },
  39. "StripAnonym": {
  40. type: "checkbox",
  41. text: "Strip url anonymizers from links",
  42. defaultValue: false
  43. },
  44. },
  45. "Notifications": {
  46. "CopyClearBottomNotifs": {
  47. type: "checkbox",
  48. text: 'Move "clear" and "clear selected" links onto bottom of matched groups',
  49. defaultValue: false
  50. },
  51. "HideClearAll": {
  52. type: "checkbox",
  53. text: 'Hide "Clear" and "clear all" links to prevent accidental clearing',
  54. defaultValue: false
  55. },
  56. "AutoCheckNotif": {
  57. type: "checkbox",
  58. text: "When the link is clicked, check the torrent's checkbox for manual clearing",
  59. defaultValue: false
  60. },
  61. },
  62. "Torrent Page": {
  63. "AutoOpenFileList": {
  64. type: "checkbox",
  65. text: 'Auto-open filelist',
  66. defaultValue: false
  67. },
  68. "AutoOpenSpoilers": {
  69. type: "checkbox",
  70. text: 'Auto-open hidden text / spoilers',
  71. defaultValue: false
  72. },
  73. "AutoThankUploader": {
  74. type: "checkbox",
  75. text: 'Auto-thank the uploader upon clicking download / freeleech / doubleseed',
  76. defaultValue: false
  77. },
  78. "AutoLoadScaledImages": {
  79. type: "checkbox",
  80. text: 'Automatically load the full res of scaled-down images when possible',
  81. defaultValue: false
  82. }
  83. },
  84. "Torrent Listings": {
  85. "ShowImages": {
  86. type: "checkbox",
  87. text: 'Display hover images inline',
  88. defaultValue: false
  89. },
  90. "HideSeeded": {
  91. type: "checkbox",
  92. text: 'Hide currently seeding torrents',
  93. defaultValue: false
  94. },
  95. "HideLeeching": {
  96. type: "checkbox",
  97. text: 'Hide currently leeching torrents',
  98. defaultValue: false
  99. },
  100. "HideGrabbed": {
  101. type: "checkbox",
  102. text: 'Hide previously grabbed torrents [incomplete download]',
  103. defaultValue: false
  104. },
  105. "HideSnatched": {
  106. type: "checkbox",
  107. text: 'Hide previously snatched torrents [finished]',
  108. defaultValue: false
  109. },
  110. },
  111. "Filters": {
  112. "FilterFilesize": {
  113. type: "checkbox",
  114. text: 'Only display torrents in the filesize range:',
  115. defaultValue: false
  116. },
  117. "FilesizeFilterRange": {
  118. type: "range",
  119. defaultValue: { lowerNumber: 0, lowerUnits: "KiB", higherNumber: 999, higherUnits: "TiB" },
  120. enabler: "FilterFilesize", // enabler indicates which checkbox must be checked to enable the field
  121. units: ["KiB", "MiB", "GiB", "TiB"]
  122. },
  123. "FilterTags": {
  124. type: "checkbox",
  125. text: 'Torrents displayed to me must <u>not</u> have any of the following tags (black list):',
  126. defaultValue: false
  127. },
  128. "TagListing": {
  129. type: "textarea",
  130. text: '(comma-separated list)',
  131. defaultValue: "bbc, big.black.cock, bbw, fat, obese, hairy, gay, trans, tranny, transsexual, scat, feces, poop, puke, vomit, censored",
  132. title: "black list",
  133. enabler: "FilterTags" // enabler indicates which checkbox must be checked to enable the field
  134. },
  135. "OnlyShowTheseTags": {
  136. type: "checkbox",
  137. text: 'Torrents displayed to me <b>must have</b> at least one of the following tags:',
  138. defaultValue: false
  139. },
  140. "OnlyShowTagListing": {
  141. type: "textarea",
  142. text: '(comma-separated list)',
  143. defaultValue: "swallowed.com, evilangel.com, amourangels.com, met-art.com, legalporno.com, mplstudios.com, femjoy.com, sexart.com, ftvgirls.com, teenpornstorage.com, domai.com, analteenangels.com, showybeauty.com, justteensite.com, facefucking.com, teenfidelity.com, chaturbate.com, myfreecams.com, twistys.com, atkgalleria.com, iameighteen.com",
  144. title: "must-have list",
  145. enabler: "OnlyShowTheseTags" // enabler indicates which checkbox must be checked to enable the field
  146. },
  147. },
  148. "Bonus": {
  149. "ShowRatioGoals": {
  150. type: "checkbox",
  151. text: 'Display ratio goals table on the Bonus Shop page (experimental)',
  152. defaultValue: false
  153. },
  154. }
  155. };
  156.  
  157. let customContentStyle = `
  158. .emp_modal_content {
  159. display: inline !important;
  160. width: 900px !important;
  161. height: auto !important;
  162. background-color: #000525 !important;
  163. margin-top: 0% !important;
  164. margin-left: -450px !important;
  165. position: absolute !important;
  166. top: 0% !important;
  167. left: 50% !important;
  168. text-align: center !important;
  169. overflow-y: auto !important;
  170. color: #2C8466 !important;
  171. }
  172. `;
  173.  
  174. let accordionStyle = {
  175. backgroundColor: "#0E2D4A",
  176. color: "white",
  177. cursor: "pointer",
  178. padding: "18px",
  179. width: "100%",
  180. border: "none",
  181. textAlign: "center",
  182. outline: "none",
  183. fontSize: "15px",
  184. transition: "0.4s",
  185. borderRadius: "5px"
  186. };
  187.  
  188. let panelStyle = {
  189. padding: "0 18px",
  190. display: "none",
  191. overflow: "hidden",
  192. textAlign: "left",
  193. backgroundColor: "#000015",
  194. fontSize: "10pt"
  195. };
  196.  
  197. let textareaStyle = {
  198. width: "870px",
  199. height: "65px",
  200. backgroundColor: "#0E2D4A",
  201. color: "silver",
  202. marginTop: "10px"
  203. };
  204.  
  205. // returns a list of elements contained in both lists (i.e. set intersection)
  206. Array.prototype.intersect = function (lst) {
  207. return this.filter(r => lst.indexOf(r) >= 0);
  208. };
  209.  
  210. // determines whether or not there exists an intersection between the two lists
  211. // tests for one or more elements in common
  212. Array.prototype.intersects = function (lst) {
  213. return this.intersect(lst).length > 0;
  214. };
  215.  
  216. // returns a list of elements unique to both lists
  217. Array.prototype.union = function(lst) {
  218. // if len(A)+len(B) == len(A U B) then each element from either list is unique
  219. return this.concat(lst).filter((item, idx, arr) => arr.indexOf(item) === idx);
  220. };
  221.  
  222. Element.prototype.props = function(json) {
  223. return Object.assign(this, json);
  224. };
  225.  
  226. HTMLDocument.prototype.new = function(tagName) {
  227. return document.createElement(tagName);
  228. };
  229.  
  230. Element.prototype.appendTo = function(element) {
  231. element.appendChild(this);
  232. return this;
  233. };
  234.  
  235. Element.prototype.setStyle = function(styler) {
  236. Object.assign(this.style, styler);
  237. return this;
  238. }
  239.  
  240. let GM_config = {};
  241.  
  242. class ConfigDialog {
  243. constructor(configId, headerText) {
  244. this.configId = configId;
  245. this.headerText = headerText;
  246.  
  247. // create modal dialog background
  248. this.modal = document.body.appendChild(document.createElement("div")).setStyle({
  249. display: "none",
  250. position: "fixed",
  251. left: 0,
  252. top: 0,
  253. width: "100%",
  254. height: "100%",
  255. overflow: "auto",
  256. backgroundColor: "rgba(0,0,0,0.7)",
  257. zIndex: 9999
  258. });
  259.  
  260. // create a space for content inside the modal
  261. this.content = this.modal.appendChild(Object.assign(document.createElement("div"), { className: "emp_modal_content" }));
  262. GM_addStyle(`
  263. .emp_modal_content {
  264. display: inline;
  265. width: 250;
  266. height: 250;
  267. background-color: white;
  268. margin-top: -125px;
  269. margin-left: -125px;
  270. position: absolute;
  271. top: 50%;
  272. left: 50%;
  273. text-align: center;
  274. }
  275. `);
  276.  
  277.  
  278. let self = this;
  279. // handle modal close on click
  280. window.addEventListener("click", function(e) {
  281. if (e.target === self.modal) {
  282. self.Close();
  283. }
  284. });
  285.  
  286. // retrieve settings
  287. this.savedValues = GM_getValue("EmporniumEnhancementsConfig")? JSON.parse(GM_getValue("EmporniumEnhancementsConfig")): null;
  288.  
  289. if (!this.savedValues) {
  290. // load default settings
  291. this.savedValues = this.GatherDefaultValues();
  292. }
  293.  
  294. Object.keys(layout).forEach(header => {
  295. Object.keys(layout[header]).forEach(id => {
  296. if (id === "FilesizeFilterRange") {
  297. if (!this.savedValues[id]) {
  298. if (GM_getValue("fileSizeRange")) {
  299. this.savedValues[id] = JSON.parse(GM_getValue("fileSizeRange"));
  300. } else {
  301. this.savedValues[id] = layout.Filters[id].defaultValue;
  302. }
  303. }
  304. } else {
  305. // auto-detect new settings
  306. if (!Object.keys(this.savedValues).includes(id)) {
  307. let defaultValue = null;
  308. Object.keys(layout).forEach(header => {
  309. if (Object.keys(layout[header]).includes(id)) {
  310. defaultValue = layout[header][id].defaultValue;
  311. }
  312. });
  313.  
  314. this.savedValues[id] = defaultValue;
  315. }
  316. }
  317. });
  318. });
  319. GM_setValue("EmporniumEnhancementsConfig", JSON.stringify(this.savedValues));
  320. GM_config = this.savedValues;
  321.  
  322. this.Customize_UI(self);
  323. }
  324.  
  325. Open() {
  326. this.modal.style.display = "block";
  327. document.body.style.overflow = "hidden";
  328. }
  329.  
  330. Close() {
  331. this.modal.style.display = "none";
  332. document.body.style.overflow = "";
  333. this.ResetUI();
  334. }
  335.  
  336. ResetUI() {
  337. // collapse all accordion headers
  338. document.querySelectorAll(".configHeader").forEach(r => {
  339. if (r.classList.contains("active_header")) {
  340. r.classList.remove("active_header");
  341. r.style.backgroundColor = "#0E2D4A";
  342. }
  343. });
  344.  
  345. // hide all panels with a list of settings
  346. document.querySelectorAll(".configPanel").forEach(r => {
  347. r.style.display = "none";
  348. });
  349.  
  350. // reset fields to the stored values
  351. document.querySelectorAll(`[id^='${this.configId}_var_']`).forEach(r => {
  352. switch (r.type || r.getAttribute("type")) {
  353. case "checkbox":
  354. r.checked = this.savedValues[r.id.replace(`${this.configId}_var_`,"")];
  355.  
  356. // determine disabled state of fields the checkbox governs
  357. document.querySelectorAll(`[data-enabler='${r.id.replace(`${this.configId}_var_`, "")}']`).forEach(d => {
  358. //d.disabled = !this.checked;
  359. if (this.checked) {
  360. d.disabled = true;
  361. } else {
  362. if (d.hasAttribute("disabled")) {
  363. d.removeAttribute("disabled");
  364. }
  365. }
  366. });
  367. break;
  368. case "textarea":
  369. case "text":
  370. case "number":
  371. case "date":
  372. case "select":
  373. r.value = this.savedValues[r.id.replace(`${this.configId}_var_`,"")];
  374. break;
  375. case "range":
  376. r.querySelectorAll("input,select").forEach(element => element.value = this.savedValues[r.id.replace(`${this.configId}_var_`,"")][element.id]);
  377. break;
  378. default:
  379. console.log(`ResetUI implement '${r.type || r.getAttribute("type")}'`);
  380. }
  381. });
  382. }
  383.  
  384. GatherFieldValues() {
  385. let self = this;
  386. this.savedValues = {};
  387.  
  388. // gather setting values from input fields
  389. document.querySelectorAll(`[id^='${this.configId}_var_']`).forEach(r => {
  390. switch (r.type || r.getAttribute("type")) {
  391. case "checkbox":
  392. this.savedValues[r.id.replace(`${this.configId}_var_`,"")] = r.checked;
  393. break;
  394. case "textarea":
  395. case "text":
  396. case "number":
  397. case "date":
  398. case "select":
  399. this.savedValues[r.id.replace(`${this.configId}_var_`,"")] = r.value;
  400. break;
  401. case "range":
  402. let rangeId = r.id.replace(`${this.configId}_var_`,"");
  403. this.savedValues[rangeId] = {};
  404. r.querySelectorAll("input,select").forEach(element => this.savedValues[rangeId][element.id] = element.value);
  405. break;
  406. default:
  407. console.log(`GatherFieldValues implement '${r.type || r.getAttribute("type")}'`);
  408. }
  409. });
  410. }
  411.  
  412. GatherDefaultValues() {
  413. let defaultValues = {};
  414.  
  415. // gather a list of defaults from the layout
  416. Object.keys(layout).forEach(header => {
  417. Object.keys(layout[header]).forEach(id => {
  418. let setting = layout[header][id];
  419. defaultValues[id] = setting.defaultValue;
  420. });
  421. });
  422.  
  423. return defaultValues;
  424. }
  425.  
  426. ResetFieldsToDefaults() {
  427. let defaultValues = this.GatherDefaultValues();
  428.  
  429. // reset fields to their default values
  430. document.querySelectorAll(`[id^='${this.configId}_var_']`).forEach(r => {
  431. switch (r.type || r.getAttribute("type")) {
  432. case "checkbox":
  433. r.checked = defaultValues[r.id.replace(`${this.configId}_var_`,"")];
  434. break;
  435. case "textarea":
  436. case "text":
  437. case "number":
  438. case "date":
  439. case "select":
  440. r.value = defaultValues[r.id.replace(`${this.configId}_var_`,"")];
  441. break;
  442. case "range":
  443. let rangeId = r.id.replace(`${this.configId}_var_`,"");
  444. let rangeValues = {};
  445. Object.keys(layout).forEach(k => {
  446. if (Object.keys(layout[k]).includes(rangeId)) {
  447. rangeValues = layout[k][rangeId].defaultValue;
  448. }
  449. });
  450.  
  451. r.querySelectorAll("input,select").forEach(element => element.value = rangeValues[element.id]);
  452. break;
  453. default:
  454. console.log(`ResetFieldsToDefaults implement '${r.type || r.getAttribute("type")}'`);
  455. }
  456. });
  457.  
  458. document.querySelectorAll("[data-enabler]").forEach(r => r.disabled = true);
  459. }
  460.  
  461. Save() {
  462. this.GatherFieldValues();
  463. // store to userscript
  464. GM_setValue("EmporniumEnhancementsConfig", JSON.stringify(this.savedValues));
  465. GM_config = this.savedValues;
  466. this.Close();
  467. location.reload();
  468. }
  469.  
  470. RenderFields(self, header) {
  471. // generate settings panel
  472. let panel = this.content.appendChild(document.createElement("div"));
  473. panel.classList.add("configPanel");
  474. panel.setStyle(panelStyle);
  475.  
  476. Object.keys(layout[header]).forEach(id => {
  477. let setting = layout[header][id];
  478. let fieldContainer = document.createElement("div");
  479. let label = null;
  480. let hr = null;
  481. let enabler = null;
  482.  
  483. if (setting.type !== "range") {
  484. label = fieldContainer.appendChild(Object.assign(document.createElement("label"), { innerHTML: setting.text }));
  485. label.setAttribute("for", `${this.configId}_var_${id}`);
  486. label.style.marginRight = "5px";
  487. }
  488.  
  489. switch (setting.type) {
  490. case "checkbox":
  491. let chk = fieldContainer.appendChild(Object.assign(document.createElement("input"), { id: `${this.configId}_var_${id}`, type: setting.type }));
  492. chk.checked = this.savedValues[id];
  493. break;
  494.  
  495. case "text":
  496. let textbox = fieldContainer.appendChild(Object.assign(document.createElement("input"), { id: `${this.configId}_var_${id}`, type: setting.type, value: this.savedValues[id] }));
  497. break;
  498.  
  499. case "textarea":
  500. let textarea = fieldContainer.appendChild(Object.assign(document.createElement("textarea"), { id: `${this.configId}_var_${id}`, value: this.savedValues[id] })).setStyle(textareaStyle);
  501.  
  502. hr = fieldContainer.appendChild(document.createElement("hr"));
  503. hr.style = "margin-top: 5px; margin-bottom: 10px";
  504.  
  505. if (setting.enabler !== undefined) {
  506. textarea.setAttribute("data-enabler", setting.enabler);
  507. enabler = panel.querySelector(`#${this.configId}_var_${setting.enabler}`);
  508. textarea.disabled = !enabler.checked;
  509. }
  510. break;
  511.  
  512. case "range":
  513. let rangeFields = [];
  514. let lowerUnits = null;
  515. let higherUnits = null;
  516.  
  517. let lowerNumber = fieldContainer.appendChild(document.createElement("input")).setStyle({ width: "50px" });
  518. Object.assign(lowerNumber, {
  519. id: "lowerNumber",
  520. type: "number",
  521. min: 0,
  522. max: 999,
  523. value: this.savedValues[id].lowerNumber
  524. });
  525. rangeFields.push(lowerNumber);
  526.  
  527. if (setting.units) {
  528. lowerUnits = fieldContainer.appendChild(Object.assign(document.createElement("select"), { id: "lowerUnits" }));
  529. rangeFields.push(lowerUnits);
  530. }
  531.  
  532. fieldContainer.appendChild(Object.assign(document.createElement("label"), { innerText: " to " }));
  533.  
  534. let higherNumber = fieldContainer.appendChild(document.createElement("input")).setStyle({ width: "50px" });
  535. Object.assign(higherNumber, {
  536. id: "higherNumber",
  537. type: "number",
  538. min: 0,
  539. max: 999,
  540. value: this.savedValues[id].higherNumber
  541. });
  542. rangeFields.push(higherNumber);
  543.  
  544. if (setting.units) {
  545. higherUnits = fieldContainer.appendChild(Object.assign(document.createElement("select"), { id: "higherUnits" }));
  546. rangeFields.push(higherUnits);
  547.  
  548. setting.units.forEach(u => {
  549. lowerUnits.appendChild(Object.assign(document.createElement("option"), { text: u, value: u }));
  550. higherUnits.appendChild(Object.assign(document.createElement("option"), { text: u, value: u }));
  551. });
  552.  
  553. lowerUnits.value = this.savedValues[id].lowerUnits;
  554. higherUnits.value = this.savedValues[id].higherUnits;
  555. }
  556.  
  557. rangeFields.forEach(r => r.setStyle({ backgroundColor: "#0E2D4A", color: "silver" }));
  558.  
  559. if (setting.enabler !== undefined) {
  560. enabler = panel.querySelector(`#${this.configId}_var_${setting.enabler}`);
  561.  
  562. rangeFields.forEach(r => {
  563. r.setAttribute("data-enabler", setting.enabler);
  564. r.disabled = !enabler.checked;
  565. });
  566. }
  567.  
  568. hr = fieldContainer.appendChild(document.createElement("hr"));
  569. hr.style = "margin-top: 5px; margin-bottom: 10px";
  570.  
  571. fieldContainer.id = `${this.configId}_var_${id}`;
  572. fieldContainer.setAttribute("type", setting.type);
  573. break;
  574.  
  575. default:
  576. fieldContainer = Object.assign(document.createElement("div"), { innerText: setting.text });
  577. break;
  578. }
  579.  
  580. // tie checkox to trigger the disabled state of the form elements it governs
  581. if (enabler) {
  582. enabler.addEventListener("change", function(e) {
  583. document.querySelectorAll(`[data-enabler='${this.id.replace(self.configId + "_var_", "")}']`).forEach(r => r.disabled = !this.checked);
  584. });
  585. }
  586.  
  587. panel.appendChild(fieldContainer);
  588. });
  589. }
  590.  
  591. RenderAccordion(self) {
  592. Object.keys(layout).forEach(header => {
  593. // generate accordion
  594. let category = this.content.appendChild(Object.assign(document.createElement("button"), { innerText: header }));
  595. category.classList.add("configHeader");
  596. category.setStyle(accordionStyle);
  597. // activate accordion header on mouse enter
  598. category.addEventListener("mouseenter", function(e) {
  599. this.style.backgroundColor = "#133C5F";
  600. });
  601. category.addEventListener("mouseleave", function(e) {
  602. // deactivate accordion header on mouse leave
  603. if (!this.classList.contains("active_header")) {
  604. this.style.backgroundColor = "#0E2D4A";
  605. }
  606. });
  607. category.addEventListener("click", function(e) {
  608. // toggle accordion active state
  609. if (this.classList.contains("active_header")) {
  610. this.classList.remove("active_header");
  611. } else {
  612. this.classList.add("active_header");
  613. }
  614.  
  615. let settingsPanel = this.nextElementSibling;
  616. if (settingsPanel.style.display === "block") {
  617. settingsPanel.style.display = "none";
  618. } else {
  619. settingsPanel.style.display = "block";
  620. }
  621. });
  622.  
  623. // render fields
  624. this.RenderFields(self, header);
  625. });
  626. }
  627.  
  628. Customize_UI(self) {
  629. // customize content box
  630. GM_addStyle(customContentStyle)
  631.  
  632. // create UI elements
  633.  
  634. // header text
  635. let headerDiv = this.content.appendChild(document.createElement("div")).setStyle({ fontSize: "20pt" });
  636. let lblTitle = headerDiv.appendChild(Object.assign(document.createElement("label"), { innerHTML: this.headerText }));
  637.  
  638. this.RenderAccordion(self);
  639.  
  640. // footer to contain buttons on the right
  641. let footerDiv = this.content.appendChild(document.createElement("div")).setStyle({ width: "100%", textAlign: "right", marginTop: "10px" });
  642.  
  643. // create save button
  644. let btnSave = footerDiv.appendChild(Object.assign(document.createElement("input"), { type: "button", value: "Save" })).setStyle({ width: "50px" });
  645. btnSave.addEventListener("click", function(e) {
  646. self.Save();
  647. });
  648.  
  649. // create reset to defaults button
  650. let btnReset = footerDiv.appendChild(Object.assign(document.createElement("input"), { type: "button", value: "Reset to defaults" })).setStyle({ width: "115px" });
  651. btnReset.addEventListener("click", function(e) {
  652. self.ResetFieldsToDefaults();
  653. });
  654. }
  655. }
  656.  
  657.  
  658. function myRound(x, places) {
  659. let trunc = Math.pow(10, places);
  660. return Math.round(x * trunc) / trunc;
  661. }
  662.  
  663. function Insert_JumpToTop() {
  664. if (GM_config.JumpToTop) {
  665. // create a div fixed in the bottom-right corner
  666. let jumpDiv = document.createElement("div");
  667. jumpDiv.setStyle({
  668. position: "fixed",
  669. bottom: "5px",
  670. right: "5px",
  671. textAlign: "right"
  672. });
  673.  
  674. // create the 'Jump to Top' link
  675. let anchor = document.createElement("a");
  676. anchor.innerText = "Jump to Top";
  677. anchor.href = "javascript:window.scrollTo(0, 0)";
  678.  
  679. // append the link to the corner div
  680. jumpDiv.appendChild(anchor);
  681.  
  682. // append the entire element to the body of the page
  683. document.body.appendChild(jumpDiv);
  684. }
  685. }
  686.  
  687. function Affix_Header() {
  688. if (GM_config.AlwaysHeader) {
  689. if (document.querySelectorAll("#header").length > 0) {
  690. // affix the header into position
  691. document.querySelector("#header").style.cssText = "position: fixed !important; top: 0px; left: 0px;";
  692. // shift the contents down to account for missing space
  693. document.querySelector("#content").style.cssText = "margin-top:120px;";
  694. }
  695. }
  696. }
  697.  
  698. function Strip_Anon(links) {
  699. if (GM_config.StripAnonym) {
  700. // list of url anonymizers to remove
  701. let anonymizers = [ "http://anonym.to/?", "http://anon.now.im/?", "https://anonym.es/?", "http://anonym.es/?" ];
  702.  
  703. // loop over each link
  704. links.forEach(link => {
  705. // loop thru the list of anonymizers
  706. anonymizers.forEach(anonymizer => {
  707. // if link contains current anonymizer
  708. if (link.href.indexOf(anonymizer) >= 0) {
  709. console.log(`Removing url anonymizer '${anonymizer}' from link '${link.href}'`);
  710. // replace it with empty string
  711. link.href = link.href.replace(anonymizer, "");
  712. }
  713. });
  714. });
  715. }
  716. }
  717.  
  718. function Prevent_NewWindow(links) {
  719. if (GM_config.PreventNewWindow) {
  720. // loop over each link
  721. links.forEach(link => {
  722. // if a new window target has been set for current link
  723. if (link.target.length > 0) {
  724. // remove its target
  725. link.target = '';
  726. }
  727. });
  728. }
  729. }
  730.  
  731. function AutoOpen_Spoilers(links) {
  732. if (GM_config.AutoOpenSpoilers) {
  733. console.log("AutoOpenSpoilers");
  734. // find all spoiler links
  735. links.filter(r => r.innerHTML === "Show").forEach(link => {
  736. // click to unhide
  737. link.click();
  738. });
  739. }
  740. }
  741.  
  742. function AutoDismiss_LoginTimeout() {
  743. if (GM_config.AutoDismiss) {
  744. if (document.querySelectorAll("#flashClose").length > 0) {
  745. document.querySelector("#flashClose").click()
  746. }
  747. }
  748. }
  749.  
  750. function RedirectTo_LoginScreen(links) {
  751. if (links.filter(r => r.innerHTML === "Login").length > 0) {
  752. window.location.href += "login";
  753. }
  754. }
  755.  
  756. function SetFocus_LoginForm() {
  757. document.querySelector("input[name=username]").focus();
  758. }
  759.  
  760. function Show_RatioGoals() {
  761. if (GM_config.ShowRatioGoals) {
  762. var stats = document.querySelectorAll(".stat");
  763. var units = [ "KiB", "MiB", "GiB", "TiB" ];
  764.  
  765. var ratio = parseFloat(stats[5].innerText);
  766. var down = stats[3].innerText;
  767. var up = stats[1].innerText;
  768.  
  769. var upUnitsIdx = units.indexOf(up.substring(1 + up.indexOf(" ")));
  770. var downUnitsIdx = units.indexOf(down.substring(1 + down.indexOf(" ")));
  771.  
  772. var unitsDiff = parseInt(Math.round(upUnitsIdx - downUnitsIdx));
  773. var displayUnits = units[downUnitsIdx];
  774.  
  775. up = up.replace(/,/g, "");
  776. up = parseFloat(up.substring(0, up.indexOf(" ")));
  777. down = down.replace(/,/g, "");
  778. down = parseFloat(down.substring(0, down.indexOf(" ")));
  779.  
  780. // convert totals to same units
  781. if (unitsDiff < 0) {
  782. down *= Math.pow(1024, -unitsDiff);
  783. displayUnits = units[upUnitsIdx];
  784. } else if (unitsDiff > 0) {
  785. up *= Math.pow(1024, unitsDiff);
  786. }
  787.  
  788. var diff = down / 100.0;
  789. var currAmount = Math.abs((ratio + 0.01) * down - up);
  790.  
  791. let strHtml = "<table style=\"text-align:center !important;margin: 0px auto; width:50%;\">\
  792. <tbody>\
  793. <tr>\
  794. <td style=\"width:90px;text-align:center;\">U/L differential</td>\
  795. <td style=\"width:90px;text-align:center;\"> for ratio </td>\
  796. <td style=\"width:90px;text-align:center;\"> Amount to U/L </td>\
  797. </tr>";
  798.  
  799. for (let i = 1; i <= 10; i++) {
  800. var currRatio = i/100.0 + ratio;
  801.  
  802. // alternate background color
  803. var style = "";
  804. if (i % 2 != 0) {
  805. style = " style=\"background-color:#222222 !important;\"";
  806. }
  807.  
  808. strHtml += "<tr" + style + "><td style=\"text-align:center;\"> +" + (i == 1 ? myRound(currAmount, 3) : myRound(diff, 3)) + " " + displayUnits + " </td><td style=\"text-align:center;\"> " + myRound(currRatio, 3) + " </td><td style=\"text-align:center;\"> " + myRound(currAmount, 3) + " " + displayUnits + " </td></tr>";
  809.  
  810. currAmount += diff;
  811. }
  812.  
  813. strHtml += "</tbody></table>";
  814.  
  815. var content = document.querySelector("#content");
  816. content.innerHTML = strHtml + content.innerHTML;
  817. }
  818. }
  819.  
  820. function Hide_Seeded(torrents) {
  821. if (GM_config.HideSeeded) {
  822. torrents.filter(r => r.querySelectorAll(".icon_disk_seed").length > 0).forEach(itemRow => { itemRow.style = "display:none"; });
  823. }
  824. }
  825.  
  826. function Hide_Grabbed(torrents) {
  827. if (GM_config.HideGrabbed) {
  828. torrents.filter(r => r.querySelectorAll(".icon_disk_grabbed").length > 0).forEach(itemRow => { itemRow.style = "display:none"; });
  829. }
  830. }
  831.  
  832. function Hide_Snatched(torrents) {
  833. if (GM_config.HideSnatched) {
  834. torrents.filter(r => r.querySelectorAll(".icon_disk_snatched").length > 0).forEach(itemRow => { itemRow.style = "display:none"; });
  835. }
  836. }
  837.  
  838. function Hide_Leeching(torrents) {
  839. if (GM_config.HideLeeching) {
  840. torrents.filter(r => r.querySelectorAll(".icon_disk_leech").length > 0).forEach(itemRow => { itemRow.style = "display:none"; });
  841. }
  842. }
  843.  
  844. function BlackList_TheseTags(torrents) {
  845. if (GM_config.FilterTags) {
  846. // grab the filters from the settings
  847. let filters = GM_config.TagListing.replace(/[\n\r\s]/g, "").split(',');
  848.  
  849. // loop over each torrent
  850. torrents.filter(r => r.style.display !== "none").forEach(itemRow => {
  851. // grab the current torrent's list of tags
  852. let tags = itemRow.querySelector(".tags").innerText.split(" ");
  853.  
  854. // if the filter keywords and tags have items in common
  855. if (filters.intersects(tags)) {
  856. // hide this torrent row
  857. itemRow.style.display = "none";
  858. let torrentInfo = Array.from(itemRow.querySelectorAll("a")).filter(r => /\/torrents\.php\?id\=\d+$/.test(r.href))[0]
  859. console.log(`Blacklisted found: "${filters.intersect(tags).join(",")}", therefore the torrent "${torrentInfo.href} - ${torrentInfo.innerText}" was hidden`);
  860. }
  861. });
  862. }
  863. }
  864.  
  865. function Display_ImagesInline(torrents) {
  866. if (GM_config.ShowImages) {
  867. // loop over each torrent
  868. torrents.forEach(itemRow => {
  869. // if current torrent row contains a hover script and a cats_col class of element exists within it
  870. if (itemRow.querySelectorAll("script").length > 0 && itemRow.querySelectorAll("[class*=cats_col]").length > 0) {
  871. // extract the image-generating html from the hover script
  872. let strHtml = itemRow.querySelector("script").innerHTML.replace(/[\[\]\{\}\(\)\\\|]/g, "");
  873. let start = strHtml.indexOf("\"")+1;
  874. let end = strHtml.lastIndexOf("\"");
  875. strHtml = strHtml.substring(start, end);
  876.  
  877. // create a div within which to place the image
  878. let div = document.createElement("div");
  879.  
  880. // hide the cats_col's child elements which contain a definite title to make room for the image
  881. Array.from(itemRow.querySelector("[class*=cats_col]").childNodes).filter(r => r.title !== undefined).forEach(cat => { cat.style.display = "none"; });
  882.  
  883. // add the html to generate the image to the div
  884. div.innerHTML = strHtml;
  885.  
  886. // display the image in the torrent row
  887. itemRow.querySelector("[class*=cats_col]").appendChild(div);
  888. }
  889. });
  890. }
  891. }
  892.  
  893. function Hide_ClearAll(links) {
  894. if (GM_config.HideClearAll) {
  895. // hide "clear all" and "Clear"
  896. links.filter(r => r.innerHTML === "(clear all)" || r.innerHTML === "Clear").forEach(link => { link.style.display = "none"; });
  897. }
  898. }
  899.  
  900. function AutoCheck_ClickedTorrent(torrents) {
  901. if (GM_config.AutoCheckNotif) {
  902. // loop over entire torrent list
  903. torrents.forEach(itemRow => {
  904. // filter-out links on current torrent that do not contain "/torrents.php?id"
  905. Array.from(itemRow.querySelectorAll("a")).filter(r => r.href.indexOf("/torrents.php?id") > -1).forEach(link => {
  906. link.addEventListener("mousedown", function (e) {
  907. // activate checkbox of current torrent if notification link was clicked
  908. itemRow.querySelector("input[type=checkbox]").checked = true;
  909. });
  910. });
  911. });
  912. }
  913. }
  914.  
  915. function Move_ClearToGroupBottom() {
  916. if (GM_config.CopyClearBottomNotifs) {
  917. // look for the torrent_table class of elements
  918. document.querySelectorAll(".torrent_table").forEach(t => {
  919. // find the current table's previous sibling's previous sibling
  920. let ps = t.previousSibling.previousSibling;
  921. // clone the element
  922. let c = ps.cloneNode(true);
  923. // insert at the bottom
  924. t.parentNode.insertBefore(c, t.nextSibling);
  925. ps.innerHTML = ps.innerHTML.toString().match(/(\w+\s){4}/gm);
  926. });
  927. }
  928. }
  929.  
  930. function Filter_Filesizes(torrents) {
  931. if (GM_config.FilterFilesize) {
  932. let units = [ "KiB", "MiB", "GiB", "TiB" ];
  933. let filesizeRange = GM_config.FilesizeFilterRange;
  934. let torrentInfo = null;
  935. let colhead = document.querySelector(".colhead");
  936. let torrentHeader = null;
  937. if (colhead) {
  938. torrentHeader = Array.from(colhead.querySelectorAll("td")).map(r => r.innerHTML.startsWith("<img")? r.innerHTML.match(/title\=\"([^\"]+)\"/)[1]: r.innerText);
  939.  
  940. // loop over each torrent
  941. torrents.filter(r => r.style.display !== "none").forEach(itemRow => {
  942. let sizeCell = itemRow.querySelectorAll("td")[torrentHeader.indexOf("Size")];
  943.  
  944. // retrieve filesize of current torrent
  945. let currSize = sizeCell.innerText.split(" ");
  946. currSize[0] = currSize[0].replace(",","");
  947.  
  948. // hide row if units of current row are less than units of min range
  949. if (units.indexOf(currSize[1]) < units.indexOf(filesizeRange.lowerUnits)) {
  950. itemRow.style.display = "none";
  951. torrentInfo = Array.from(itemRow.querySelectorAll("a")).filter(r => /\/torrents\.php\?id\=\d+$/.test(r.href))[0]
  952. console.log(`${currSize.join("")} < ${filesizeRange.lowerNumber}${filesizeRange.lowerUnits}; hide the torrent '${torrentInfo.href} - ${torrentInfo.innerText}'`);
  953. } else if (currSize[1] === filesizeRange.lowerUnits) {
  954. // hide row if units match and filesize is less than min range
  955. if (parseFloat(currSize[0]) < parseInt(filesizeRange.lowerNumber)) {
  956. itemRow.style.display = "none";
  957. torrentInfo = Array.from(itemRow.querySelectorAll("a")).filter(r => /\/torrents\.php\?id\=\d+$/.test(r.href))[0]
  958. console.log(`${currSize.join("")} < ${filesizeRange.lowerNumber}${filesizeRange.lowerUnits}; hide the torrent '${torrentInfo.href} - ${torrentInfo.innerText}'`);
  959. }
  960. }
  961.  
  962. // hide row if units of current row are greater than units of max range
  963. if (units.indexOf(currSize[1]) > units.indexOf(filesizeRange.higherUnits)) {
  964. itemRow.style.display = "none";
  965. torrentInfo = Array.from(itemRow.querySelectorAll("a")).filter(r => /\/torrents\.php\?id\=\d+$/.test(r.href))[0]
  966. console.log(`${currSize.join("")} > ${filesizeRange.higherNumber}${filesizeRange.higherUnits}; hide the torrent '${torrentInfo.href} - ${torrentInfo.innerText}'`);
  967. } else if (currSize[1] === filesizeRange.higherUnits) {
  968. // hide row if units match and filesize is greater than max range
  969. if (parseFloat(currSize[0]) > parseInt(filesizeRange.higherNumber)) {
  970. itemRow.style.display = "none";
  971. torrentInfo = Array.from(itemRow.querySelectorAll("a")).filter(r => /\/torrents\.php\?id\=\d+$/.test(r.href))[0]
  972. console.log(`${currSize.join("")} > ${filesizeRange.higherNumber}${filesizeRange.higherUnits}; hide the torrent '${torrentInfo.href} - ${torrentInfo.innerText}'`);
  973. }
  974. }
  975. });
  976. } else {
  977. console.log("Unable to filter filesizes; could not find torrent listing column header.");
  978. }
  979. }
  980. }
  981.  
  982. function Draw_MenuItem(callback) {
  983. if (document.querySelector(".username") != null) {
  984. // config icon - wrench & screwdriver depicted
  985. let icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA\
  986. u0lEQVQ4jZ2SMQ4CMQwERyiICqEr+EGgRBelpkvLI3kDD+IBdDQUFKbJIcdyTqezlGazu7\
  987. ZXBlM55+1ajFLyYS2mQanv4hFDCNP/rKNUoid+NAZql5MxFODriMtkEGPcNV2BsxIL8O6J\
  988. vfVE7fd/w7C/LhFjOosxTEvETTnTjK7YO4hKDM40o+bZEO0qnwrZbNKSHF4OZoNtK6V4VC\
  989. QtnkoAUYZuZwE2vRHVdbqXeO90ttgTuEEnRA+cw36EkV9UhABsAgAAAABJRU5ErkJggg==";
  990. // find the user nav element
  991. let navbar = document.querySelector(".username").parentNode.querySelector("ul");
  992. // clone a list item layout
  993. let configNode = navbar.querySelector("li").cloneNode(true);
  994.  
  995. // wire-up the config element
  996. configNode.id = "deluxe_config";
  997. configNode.innerHTML = "";
  998. let anchor = document.new("a").appendTo(configNode);
  999. anchor.title = "Empornium Deluxe Mode Configuration";
  1000. anchor.innerHTML = '<img src="' + icon + '" style="filter:invert(100%);"/> Deluxe Mode Config';
  1001. anchor.href = "javascript:void(0)";
  1002. anchor.addEventListener("click", function (e) {
  1003. callback();
  1004. });
  1005.  
  1006. // display config link as first item in the user nav
  1007. navbar.insertBefore(configNode, navbar.firstChild);
  1008. }
  1009. }
  1010.  
  1011. function AutoThank_Uploader() {
  1012. if (GM_config.AutoThankUploader) {
  1013. // .blueButton => Download
  1014. // .greenButton => Freeleech
  1015. // .orangeButton => Doubleseed
  1016. // look for the download / freeleech / doubleseed buttons
  1017. Array.from(document.querySelectorAll(".blueButton,.greenButton,.orangeButton")).forEach(btn => {
  1018. // wire-up event to auto-click thanks upon downloading
  1019. btn.addEventListener("click", function (e) {
  1020. window.setTimeout(function () {
  1021. // if thanks button not disabled
  1022. if (!document.querySelector("#thanksbutton").disabled) {
  1023. // invoke it
  1024. document.querySelector("#thanksbutton").click();
  1025. }
  1026. // wait 500ms so as not to interrupt download request
  1027. }, 500);
  1028. });
  1029. });
  1030. }
  1031. }
  1032.  
  1033. function AutoOpen_FileList(links) {
  1034. if (GM_config.AutoOpenFileList) {
  1035. links.filter(r => r.innerHTML === "(View Filelist)")[0].click();
  1036. }
  1037. }
  1038.  
  1039. function MustHave_TheseTags(torrents) {
  1040. if (GM_config.OnlyShowTheseTags) {
  1041. // grab the filters from the settings
  1042. let filters = GM_config.OnlyShowTagListing.replace(/[\n\r\s]/g, "").split(',');
  1043.  
  1044. // loop over each torrent
  1045. torrents.filter(r => r.style.display !== "none").forEach(itemRow => {
  1046. // grab the current torrent's list of tags
  1047. let tags = itemRow.querySelector(".tags").innerText.split(" ");
  1048.  
  1049. // if the filter keywords and tags do not have items in common
  1050. if (!filters.intersects(tags)) {
  1051. // hide this torrent row
  1052. itemRow.style = "display:none";
  1053. let torrentInfo = Array.from(itemRow.querySelectorAll("a")).filter(r => /\/torrents\.php\?id\=\d+$/.test(r.href))[0]
  1054. console.log(`Not found on must-have list: "${tags.filter(r => r !== "").join(",")}", therefore the torrent "${torrentInfo.href} - ${torrentInfo.innerText}" was hidden`);
  1055. }
  1056. });
  1057. }
  1058. }
  1059.  
  1060. function AutoLoad_ScaledImages() {
  1061. if (GM_config.AutoLoadScaledImages) {
  1062. Array.from(document.querySelectorAll(".scale_image")).forEach((r, idx) => {
  1063. setTimeout(function() {
  1064. // detect thumbnails and medium scaled images
  1065. let newsrc = r.src.replace(".th","").replace(".md","");
  1066. r.src = newsrc;
  1067. r.parentNode.href = newsrc;
  1068. }, idx * 3000); // put a delay of 3sec between each full-res image request
  1069. });
  1070. }
  1071. }
  1072.  
  1073.  
  1074.  
  1075. // main
  1076. (function() {
  1077. 'use strict';
  1078.  
  1079. let headerText = "Empornium Deluxe Mode Config";
  1080.  
  1081. let config = new ConfigDialog("EmporniumConfig", headerText);
  1082.  
  1083. GM_registerMenuCommand(headerText, function() {
  1084. config.Open();
  1085. });
  1086.  
  1087. AutoDismiss_LoginTimeout();
  1088. Draw_MenuItem(function() {
  1089. config.Open();
  1090. });
  1091.  
  1092. let links = Array.from(document.querySelectorAll("a"));
  1093. let torrents = Array.from(document.querySelectorAll(".torrent"));
  1094.  
  1095. // page match rules
  1096. if (/empornium\.(me|is|sx)\/?$/.test(window.location.href)) {
  1097. RedirectTo_LoginScreen(links);
  1098. } else if (/empornium\.(me|is|sx)\/login$/.test(window.location.href)) {
  1099. SetFocus_LoginForm();
  1100. } else {
  1101. // occurs on all authenticated pages
  1102. Affix_Header();
  1103. Strip_Anon(links);
  1104. Prevent_NewWindow(links);
  1105. Insert_JumpToTop();
  1106.  
  1107.  
  1108. if (/torrents\.php.+action=notify/.test(window.location.href)) {
  1109. // notifications page
  1110. Hide_ClearAll(links);
  1111. Move_ClearToGroupBottom();
  1112. AutoCheck_ClickedTorrent(torrents);
  1113. }
  1114.  
  1115. if (/(top10|user)\.php/.test(window.location.href) || (/torrents\.php/.test(window.location.href) && !/(\?|&)id=/.test(window.location.href))) {
  1116. // torrents / top10 / user - lists of torrents
  1117. BlackList_TheseTags(torrents);
  1118. Filter_Filesizes(torrents);
  1119. MustHave_TheseTags(torrents);
  1120.  
  1121. Hide_Seeded(torrents);
  1122. Hide_Grabbed(torrents);
  1123. Hide_Snatched(torrents);
  1124. Hide_Leeching(torrents);
  1125.  
  1126. Display_ImagesInline(torrents);
  1127. } else if (/torrents\.php\?id=\d+/.test(window.location.href)) {
  1128. console.log("single torrent page");
  1129. // single torrent landing page
  1130. AutoOpen_Spoilers(links);
  1131. AutoOpen_FileList(links);
  1132. AutoThank_Uploader();
  1133. AutoLoad_ScaledImages();
  1134. } else if (/bonus\.php/.test(window.location.href)) {
  1135. // bonus page
  1136. Show_RatioGoals();
  1137. }
  1138. }
  1139. })();