Empornium Deluxe Mode

Enhances the empornium.me porn torrent website

Mint 2023.11.13.. Lásd a legutóbbi verzió

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