MSPFA extras

Adds custom features to MSPFA.

当前为 2020-08-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MSPFA extras
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.5
  5. // @description Adds custom features to MSPFA.
  6. // @author seymour schlong
  7. // @match https://mspfa.com/
  8. // @match https://mspfa.com/*/
  9. // @match https://mspfa.com/*/?*
  10. // @match https://mspfa.com/?s=*
  11. // @match https://mspfa.com/my/*
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. const currentVersion = "1.5";
  20. console.log(`MSPFA extras script v${currentVersion} by seymour schlong`);
  21.  
  22. /**
  23. * https://github.com/GrantGryczan/MSPFA/projects/1?fullscreen=true
  24. * Github to-do completion list
  25. *
  26. * https://github.com/GrantGryczan/MSPFA/issues/26 - Dropdown menu - February 23rd, 2020
  27. * https://github.com/GrantGryczan/MSPFA/issues/18 - MSPFA themes - February 23rd, 2020
  28. * https://github.com/GrantGryczan/MSPFA/issues/32 - Adventure creation dates - February 23rd, 2020
  29. * https://github.com/GrantGryczan/MSPFA/issues/32 - User creation dates - February 23rd, 2020
  30. * https://github.com/GrantGryczan/MSPFA/issues/40 - Turn certain buttons into links - July 21st, 2020
  31. * https://github.com/GrantGryczan/MSPFA/issues/41 - Word and character count - July 21st, 2020
  32. * https://github.com/GrantGryczan/MSPFA/issues/57 - Default spoiler values - August 7th, 2020
  33. * https://github.com/GrantGryczan/MSPFA/issues/62 - Buttonless spoilers - August 7th, 2020
  34. *
  35. * Note: At this point, I think I've done pretty much all that I /can/ do from the Github list. However, that doesn't mean I'll stop working on this.
  36. * So long as people have ideas for features to add, I can continue adding things.
  37. *
  38. * Extension to-do... maybe...
  39. *
  40. * If trying to save a page and any other save button is not disabled, ask the user if they would rather Save All instead, or prompt to disable update notifications.
  41. */
  42.  
  43. // A general function that allows for waiting until a certain element appears on the page.
  44. const pageLoad = (fn) => {
  45. let interval = setInterval(() => {
  46. if (fn()) clearInterval(interval);
  47. }, 500);
  48. };
  49.  
  50. // Saves the options data for the script.
  51. const saveData = (data) => {
  52. localStorage.mspfaextra = JSON.stringify(data);
  53. };
  54.  
  55. // Encases an element within a link
  56. const addLink = (elm, url) => {
  57. let link = document.createElement('a');
  58. link.href = url;
  59. elm.parentNode.insertBefore(link, elm);
  60. link.appendChild(elm);
  61. };
  62.  
  63. // Returns true if version 2 is newer
  64. const compareVer = (ver1, ver2) => {
  65. ver1 = ver1.split(/\./); // current version
  66. ver2 = ver2.split(/\./); // new version
  67. ver1.push(0);
  68. ver2.push(0);
  69. if (parseInt(ver2[0]) > parseInt(ver1[0])) { // 1.x.x
  70. return true;
  71. } else if (parseInt(ver2[1]) > parseInt(ver1[1])) { // x.1.x
  72. return true;
  73. } else if (parseInt(ver2[2]) > parseInt(ver1[2])) { // x.x.1
  74. return true;
  75. }
  76. return false;
  77. }
  78.  
  79. let settings = {};
  80. let defaultSettings = {
  81. autospoiler: false,
  82. style: 0,
  83. styleURL: "",
  84. night: false,
  85. auto502: true,
  86. textFix: false,
  87. pixelFix: false,
  88. intro: false,
  89. autoUpdate: true,
  90. version: currentVersion,
  91. spoilerValues: {}
  92. }
  93.  
  94. // Load any previous settings from localStorage
  95. if (localStorage.mspfaextra) {
  96. settings = JSON.parse(localStorage.mspfaextra);
  97. }
  98.  
  99. // If any settings are undefined, re-set to their default state. (For older users when new things get stored)
  100. Object.assign(defaultSettings, settings);
  101. saveData(settings);
  102.  
  103. // Update saved version to the version used in the script to prevent unnecessary notifications
  104. if (compareVer(settings.version, currentVersion)) {
  105. settings.version = currentVersion;
  106. saveData(settings);
  107. }
  108.  
  109. window.MSPFAe = {}
  110. window.MSPFAe.getSettings = () => {
  111. return settings;
  112. }
  113. window.MSPFAe.getSettingsString = () => {
  114. console.log(JSON.stringify(settings));
  115. }
  116. window.MSPFAe.changeSettings = (newSettings) => {
  117. console.log('Settings updated');
  118. console.log(settings);
  119. Object.assign(settings, newSettings);
  120. saveData(settings);
  121. }
  122.  
  123. //console.log(settings);
  124.  
  125. let styleOptions = ["Standard", "Low Contrast", "Light", "Dark", "Felt", "Trickster", "Custom"];
  126. let styleUrls = ['', '/css/theme1.css', '/css/theme2.css', '/css/?s=36237', '/css/theme4.css', '/css/theme5.css'];
  127.  
  128. // Dropdown menu
  129. let myLink = document.querySelector('nav a[href="/my/"]');
  130. if (myLink) {
  131. let dropDiv = document.createElement('div');
  132. dropDiv.className = 'dropdown';
  133. Object.assign(dropDiv.style, {
  134. position: 'relative',
  135. display: 'inline-block',
  136. backgroundColor: 'inherit'
  137. });
  138.  
  139. let dropContent = document.createElement('div');
  140. dropContent.className = 'dropdown-content';
  141. Object.assign(dropContent.style, {
  142. display: 'none',
  143. backgroundColor: 'inherit',
  144. position: 'absolute',
  145. textAlign: 'left',
  146. minWidth: '100px',
  147. marginLeft: '-5px',
  148. padding: '2px',
  149. zIndex: '1',
  150. borderRadius: '0 0 5px 5px'
  151. });
  152.  
  153. dropDiv.addEventListener('mouseenter', evt => {
  154. dropContent.style.display = 'block';
  155. dropContent.style.color = getComputedStyle(myLink).color;
  156. });
  157. dropDiv.addEventListener('mouseleave', evt => {
  158. dropContent.style.display = 'none';
  159. });
  160.  
  161. myLink.parentNode.insertBefore(dropDiv, myLink);
  162. dropDiv.appendChild(myLink);
  163. dropDiv.appendChild(dropContent);
  164.  
  165. let dLinks = [];
  166. dLinks[0] = [ 'Messages', 'My Adventures', 'Settings' ];
  167. dLinks[1] = [ '/my/messages/', '/my/stories/', '/my/settings/' ];
  168.  
  169. for (let i = 0; i < dLinks[0].length; i++) {
  170. let newLink = document.createElement('a');
  171. newLink.textContent = dLinks[0][i];
  172. newLink.href = dLinks[1][i];
  173. dropContent.appendChild(newLink);
  174. }
  175.  
  176. // Append "My Profile" to the dropdown list if you're signed in
  177. pageLoad(() => {
  178. if (window.MSPFA) {
  179. if (window.MSPFA.me.n) {
  180. let newLink = document.createElement('a');
  181. newLink.textContent = "My Profile";
  182. newLink.href = `/user/?u=${window.MSPFA.me.i}`;
  183. dropContent.appendChild(newLink);
  184. return true;
  185. }
  186. }
  187. });
  188. }
  189.  
  190. // Error reloading
  191. window.addEventListener("load", () => {
  192. // Reload the page if 502 CloudFlare error page appears
  193. if (settings.auto502 && document.querySelector('.cf-error-overview')) {
  194. window.location.reload();
  195. }
  196.  
  197. // Wait five seconds, then refresh the page
  198. if (document.body.textContent === "Your client is sending data to MSPFA too quickly. Wait a moment before continuing.") {
  199. setTimeout(() => {
  200. window.location.reload();
  201. }, 5000);
  202. }
  203. });
  204.  
  205. // Message that shows when you first get the script
  206. const showIntroDialog = () => {
  207. let msg = window.MSPFA.parseBBCode('Hi! Thanks for installing this script!\n\nBe sure to check the [url=https://greasyfork.org/en/scripts/396798-mspfa-extras#additional-info]GreasyFork[/url] page to see a full list of features, and don\'t forget to check out your [url=https://mspfa.com/my/settings/#extraSettings]settings[/url] page to tweak things to how you want.\n\nIf you have any suggestions, or you find a bug, please be sure to let me know on Discord at [url=discord://discordapp.com/users/277928549866799125]@seymour schlong#3669[/url].\n\n[size=12]This dialog will only appear once. To view it again, click "View Script Message" at the bottom of the site.[/size]');
  208. window.MSPFA.dialog("MSPFA extras message", msg, ["Okay"]);
  209. }
  210.  
  211. // Check for updates by comparing currentVersion to text data from an adventure that has update text and info
  212. const checkForUpdates = (evt) => {
  213. window.MSPFA.request(0, {
  214. do: "story",
  215. s: "36596"
  216. }, story => {
  217. if (typeof story !== "undefined") {
  218. let ver = settings.version.split(/\./);
  219. let newVer = story.p[1].c.split(/\./);
  220. // compare versions
  221. if (compareVer(settings.version, story.p[1].c) || (evt && evt.type === 'click')) {
  222. let msg = window.MSPFA.parseBBCode(story.p[1].b);
  223. settings.version = story.p[1].c;
  224. saveData(settings);
  225. window.MSPFA.dialog(`MSPFA extras update! (${story.p[1].c})`, msg, ["Opt-out", "Dismiss", "Update"], (output, form) => {
  226. if (output === "Update") {
  227. window.open('https://greasyfork.org/en/scripts/396798-mspfa-extras', '_blank').focus();
  228. } else if (output === "Opt-out") {
  229. settings.autoUpdate = false;
  230. saveData(settings);
  231. }
  232. });
  233. }
  234. }
  235. });
  236. };
  237.  
  238.  
  239. // Check for updates and show intro dialog if needed
  240. pageLoad(() => {
  241. if (window.MSPFA) {
  242. if (settings.autoUpdate) {
  243. checkForUpdates();
  244. }
  245.  
  246. if (!settings.intro) {
  247. showIntroDialog();
  248. settings.intro = true;
  249. saveData(settings);
  250. }
  251. return true;
  252. }
  253. });
  254.  
  255. let details = document.querySelector('#details');
  256. // Add 'link' at the bottom to show the intro dialog again
  257. let introLink = document.createElement('a');
  258. introLink.textContent = 'View Script Message';
  259. introLink.style = 'cursor: pointer; color: #00E; text-decoration: underline;';
  260. introLink.className = 'intro-link';
  261. introLink.addEventListener('click', showIntroDialog);
  262. details.appendChild(introLink);
  263.  
  264. // vbar!!!!
  265. let vbar = document.createElement('span');
  266. vbar.className = 'vbar';
  267. vbar.style = 'padding: 0 5px';
  268. vbar.textContent = '|';
  269. details.appendChild(vbar);
  270.  
  271. // Add 'link' at the bottom to show the update dialog again
  272. let updateLink = document.createElement('a');
  273. updateLink.textContent = 'View Update';
  274. updateLink.style = 'cursor: pointer; color: #00E; text-decoration: underline;';
  275. updateLink.className = 'intro-link';
  276. updateLink.addEventListener('click', checkForUpdates);
  277. details.appendChild(updateLink);
  278.  
  279. // Theme stuff
  280. let theme = document.createElement('link');
  281. Object.assign(theme, { id: 'theme', type: 'text/css', rel: 'stylesheet' });
  282. const updateTheme = (src) => {
  283. theme.href = src;
  284. }
  285. if (!document.querySelector('#theme') && !/^\/css\/|^\/js\//.test(location.pathname)) {
  286. document.querySelector('head').appendChild(theme);
  287. if (settings.night) {
  288. updateTheme('/css/?s=36237');
  289. } else {
  290. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  291. }
  292. }
  293.  
  294. // Dropdown menu and pixelated scaling
  295. let dropStyle = document.createElement('style');
  296. let pixelFixText = 'img, .mspfalogo, .major, .arrow, #flashytitle, .heart, .fav, .notify, .edit, .rss, input, #loading { image-rendering: pixelated !important; }';
  297. let dropStyleText = `#notification { z-index: 2; } .dropdown-content a { color: inherit; padding: 2px; text-decoration: underline; display: block;}`;
  298. if (!document.querySelector('#dropdown-style')) {
  299. dropStyle.id = 'dropdown-style';
  300. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  301. //dropdownStyle.textContent = '#notification { z-index: 2;}.dropdown:hover .dropdown-content { display: block;}.dropdown { position: relative; display: inline-block; background-color: inherit;}.dropdown-content { display: none; position: absolute; text-align: left; background-color: inherit; min-width: 100px; margin-left: -5px; padding: 2px; z-index: 1; border-radius: 0 0 5px 5px;}.dropdown-content a { color: #fffa36; padding: 2px 2px; text-decoration: underline; display: block;}';
  302.  
  303. document.querySelector('head').appendChild(dropStyle);
  304. }
  305.  
  306. // Remove the current theme if the adventure has CSS (to prevent conflicts);
  307. pageLoad(() => {
  308. if (window.MSPFA) {
  309. if (window.MSPFA.story && window.MSPFA.story.y && window.MSPFA.story.y.length > 0) {
  310. updateTheme('');
  311. }
  312. return true;
  313. }
  314. });
  315.  
  316. // Enabling night mode.
  317. pageLoad(() => {
  318. if (document.querySelector('footer .mspfalogo')) {
  319. document.querySelector('footer .mspfalogo').addEventListener('dblclick', evt => {
  320. if (evt.button === 0) {
  321. settings.night = !settings.night;
  322. saveData(settings);
  323.  
  324. if (settings.night) {
  325. updateTheme('/css/?s=36237');
  326. } else {
  327. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  328. }
  329.  
  330. dropStyle.textContent = dropStyleText + '';
  331. dropStyle.textContent = dropStyleText + '*{transition:1s}';
  332. setTimeout(() => {
  333. dropStyle.textContent = dropStyleText;
  334. }, 1000);
  335.  
  336. console.log(`Night mode turned ${settings.night ? 'on' : 'off'}.`);
  337. }
  338. });
  339. return true;
  340. }
  341. });
  342.  
  343. if (location.pathname === "/" || location.pathname === "/preview/") {
  344. // Automatic spoiler opening
  345. if (settings.autospoiler) {
  346. window.MSPFA.slide.push((p) => {
  347. document.querySelectorAll('#slide .spoiler:not(.open) > div:first-child > input').forEach(sb => sb.click());
  348. });
  349. }
  350.  
  351. if (location.search) {
  352. // Show creation date
  353. pageLoad(() => {
  354. if (document.querySelector('#infobox tr td:nth-child(2)')) {
  355. document.querySelector('#infobox tr td:nth-child(2)').appendChild(document.createTextNode('Creation date: ' + new Date(window.MSPFA.story.d).toString().split(' ').splice(1, 3).join(' ')));
  356. return true;
  357. }
  358. });
  359.  
  360. // Attempt to fix text errors
  361. if (settings.textFix) {
  362. pageLoad(() => {
  363. if (window.MSPFA.story && window.MSPFA.story.p) {
  364. // russian/bulgarian is not possible =(
  365. let currentPage = parseInt(/^\?s(?:.*?)&p=([\d]*)$/.exec(location.search)[1]);
  366. let library = [
  367. ["&acirc;��", "'"],
  368. ["&Atilde;�", "Ñ"],
  369. ["&Atilde;&plusmn;", "ñ"],
  370. ["&Atilde;&sup3;", "ó"],
  371. ["&Atilde;&iexcl;", "á"],
  372. ["&Atilde;&shy;", "í"],
  373. ["&Atilde;&ordm;", "ú"],
  374. ["&Atilde;&copy;", "é"],
  375. ["&Acirc;&iexcl;", "¡"],
  376. ["&Acirc;&iquest;", "¿"],
  377. ["N&Acirc;&ordm;", "#"]
  378. ];
  379. // https://mspfa.com/?s=5280&p=51 -- unknown error
  380.  
  381. const replaceTerms = (p) => {
  382. library.forEach(term => {
  383. if (window.MSPFA.story.p[p]) {
  384. window.MSPFA.story.p[p].c = window.MSPFA.story.p[p].c.replace(new RegExp(term[0], 'g'), term[1]);
  385. window.MSPFA.story.p[p].b = window.MSPFA.story.p[p].b.replace(new RegExp(term[0], 'g'), term[1]);
  386. }
  387. });
  388. };
  389.  
  390. replaceTerms(currentPage-1);
  391.  
  392. window.MSPFA.slide.push(p => {
  393. replaceTerms(p);
  394. replaceTerms(p-2);
  395. });
  396. window.MSPFA.page(currentPage);
  397. return true;
  398. }
  399. });
  400. }
  401.  
  402. // Turn buttons into links
  403. pageLoad(() => {
  404. let infoButton = document.querySelector('.edit.major');
  405. if (infoButton) {
  406. pageLoad(() => {
  407. if (window.MSPFA.me.i) {
  408. addLink(infoButton, `/my/stories/info/${location.search.split('&p=')[0]}`);
  409. return;
  410. }
  411. });
  412. addLink(document.querySelector('.rss.major'), `/rss/${location.search.split('&p=')[0]}`);
  413. return true;
  414. }
  415. });
  416.  
  417. // Add "Reply" button to comment gear
  418. document.body.addEventListener('click', evt => {
  419. if (evt.toElement.classList.contains('gear')) {
  420. let userID = evt.path[2].classList[2].replace('u', '');
  421. let reportButton = document.querySelector('#dialog button[data-value="Report"]');
  422. let replyButton = document.createElement('button');
  423. replyButton.classList.add('major');
  424. replyButton.type = 'submit';
  425. replyButton.setAttribute('data-value', 'Reply');
  426. replyButton.textContent = 'Reply';
  427. replyButton.style = 'margin-right: 9px';
  428. reportButton.parentNode.insertBefore(replyButton, reportButton);
  429.  
  430. replyButton.addEventListener('click', evt => {
  431. document.querySelector('#dialog button[data-value="Cancel"]').click();
  432. let commentBox = document.querySelector('#commentbox textarea');
  433. commentBox.value = `[user]${userID}[/user], ${commentBox.value}`;
  434. commentBox.focus();
  435. });
  436. } else {
  437. return;
  438. }
  439. });/**/
  440. }
  441. }
  442. else if (location.pathname === "/my/settings/") { // Custom settings
  443. let saveBtn = document.querySelector('#savesettings');
  444.  
  445. let table = document.querySelector("#editsettings tbody");
  446. let saveTr = table.querySelectorAll("tr");
  447. saveTr = saveTr[saveTr.length - 1];
  448.  
  449. let headerTr = document.createElement('tr');
  450. let header = document.createElement('th');
  451. headerTr.id = "extraSettings";
  452. header.textContent = "Extra Settings";
  453. headerTr.appendChild(header);
  454.  
  455. let moreTr = document.createElement('tr');
  456. let more = document.createElement('td');
  457. more.textContent = "* This only applies to a select few older adventures that have had their text corrupted. Some punctuation is fixed, as well as regular characters with accents. Currently only some spanish/french is fixable. Russian/Bulgarian is not possible.";
  458. moreTr.appendChild(more);
  459.  
  460. let settingsTr = document.createElement('tr');
  461. let localMsg = document.createElement('span');
  462. let settingsTd = document.createElement('td');
  463. localMsg.innerHTML = "Because this is an extension, any data saved is only <b>locally</b> on this device.<br>Don't forget to <b>save</b> when you've finished making changes!";
  464. let plusTable = document.createElement('table');
  465. let plusTbody = document.createElement('tbody');
  466. plusTable.appendChild(plusTbody);
  467. settingsTd.appendChild(localMsg);
  468. settingsTd.appendChild(document.createElement('br'));
  469. settingsTd.appendChild(document.createElement('br'));
  470. settingsTd.appendChild(plusTable);
  471. settingsTr.appendChild(settingsTd);
  472.  
  473. plusTable.style = "text-align: center;";
  474.  
  475. let spoilerTr = plusTbody.insertRow(plusTbody.childNodes.length);
  476. let spoilerTextTd = spoilerTr.insertCell(0);
  477. let spoilerInputTd = spoilerTr.insertCell(1);
  478. let spoilerInput = document.createElement('input');
  479. spoilerInputTd.appendChild(spoilerInput);
  480.  
  481. spoilerTextTd.textContent = "Automatically open spoilers:";
  482. spoilerInput.type = "checkbox";
  483. spoilerInput.checked = settings.autospoiler;
  484.  
  485. let errorTr = plusTbody.insertRow(plusTbody.childNodes.length);
  486. let errorTextTd = errorTr.insertCell(0);
  487. let errorInputTd = errorTr.insertCell(1);
  488. let errorInput = document.createElement('input');
  489. errorInputTd.appendChild(errorInput);
  490.  
  491. errorTextTd.textContent = "Automatically reload Cloudflare 502 error pages:";
  492. errorInput.type = "checkbox";
  493. errorInput.checked = settings.auto502;
  494.  
  495. let updateTr = plusTbody.insertRow(plusTbody.childNodes.length);
  496. let updateTextTd = updateTr.insertCell(0);
  497. let updateInputTd = updateTr.insertCell(1);
  498. let updateInput = document.createElement('input');
  499. updateInputTd.appendChild(updateInput);
  500.  
  501. updateTextTd.textContent = "Automatically check for updates:";
  502. updateInput.type = "checkbox";
  503. updateInput.checked = settings.autoUpdate;
  504.  
  505. let pixelFixTr = plusTbody.insertRow(plusTbody.childNodes.length);
  506. let pixelFixTextTd = pixelFixTr.insertCell(0);
  507. let pixelFixInputTd = pixelFixTr.insertCell(1);
  508. let pixelFixInput = document.createElement('input');
  509. pixelFixInputTd.appendChild(pixelFixInput);
  510.  
  511. pixelFixTextTd.textContent = "Change pixel scaling to nearest neighbour:";
  512. pixelFixInput.type = "checkbox";
  513. pixelFixInput.checked = settings.pixelFix;
  514.  
  515. let textFixTr = plusTbody.insertRow(plusTbody.childNodes.length);
  516. let textFixTextTd = textFixTr.insertCell(0);
  517. let textFixInputTd = textFixTr.insertCell(1);
  518. let textFixInput = document.createElement('input');
  519. textFixInputTd.appendChild(textFixInput);
  520.  
  521. textFixTextTd.textContent = "Attempt to fix text errors (experimental)*:";
  522. textFixInput.type = "checkbox";
  523. textFixInput.checked = settings.textFix;
  524.  
  525. let cssTr = plusTbody.insertRow(plusTbody.childNodes.length);
  526. let cssTextTd = cssTr.insertCell(0);
  527. let cssSelectTd = cssTr.insertCell(1);
  528. let cssSelect = document.createElement('select');
  529. cssSelectTd.appendChild(cssSelect);
  530.  
  531. cssTextTd.textContent = "Change style:";
  532.  
  533. let customTr = plusTbody.insertRow(plusTbody.childNodes.length);
  534. let customTextTd = customTr.insertCell(0);
  535. let customCssTd = customTr.insertCell(1);
  536. let customCssInput = document.createElement('input');
  537. customCssTd.appendChild(customCssInput);
  538.  
  539. customTextTd.textContent = "Custom CSS URL:";
  540. customCssInput.style.width = "99px";
  541. customCssInput.value = settings.styleURL;
  542.  
  543. styleOptions.forEach(o => cssSelect.appendChild(new Option(o, o)));
  544.  
  545. // Enable the save button
  546.  
  547. saveTr.parentNode.insertBefore(headerTr, saveTr);
  548. saveTr.parentNode.insertBefore(settingsTr, saveTr);
  549. saveTr.parentNode.insertBefore(moreTr, saveTr);
  550. cssSelect.selectedIndex = settings.style;
  551.  
  552. // Add event listeners
  553. plusTbody.querySelectorAll('input, select').forEach(elm => {
  554. elm.addEventListener("change", () => {
  555. saveBtn.disabled = false;
  556. });
  557. });
  558.  
  559. saveBtn.addEventListener('mouseup', () => {
  560. settings.autospoiler = spoilerInput.checked;
  561. settings.style = cssSelect.selectedIndex;
  562. settings.styleURL = customCssInput.value;
  563. settings.auto502 = errorInput.checked;
  564. settings.textFix = textFixInput.checked;
  565. settings.pixelFix = pixelFixInput.checked;
  566. settings.autoUpdate = updateInput.checked;
  567. settings.night = false;
  568. console.log(settings);
  569. saveData(settings);
  570.  
  571. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  572.  
  573. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  574.  
  575. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + ' *{transition:1s}';
  576. setTimeout(() => {
  577. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  578. }, 1000);
  579. });
  580. }
  581. else if (location.pathname === "/my/messages/") { // New buttons
  582. let btnStyle = "margin: 10px 5px;";
  583.  
  584. // Select all read messages button.
  585. const selRead = document.createElement('input');
  586. selRead.style = btnStyle;
  587. selRead.value = "Select Read";
  588. selRead.id = "selectread";
  589. selRead.classList.add("major");
  590. selRead.type = "button";
  591.  
  592. // On click, select all messages with the style attribute indicating it as read.
  593. selRead.addEventListener('mouseup', () => {
  594. document.querySelectorAll('td[style="border-left: 8px solid rgb(221, 221, 221);"] > input').forEach((m) => m.click());
  595. });
  596.  
  597. // Select duplicate message (multiple update notifications).
  598. const selDupe = document.createElement('input');
  599. selDupe.style = btnStyle;
  600. selDupe.value = "Select Same";
  601. selDupe.id = "selectdupe";
  602. selDupe.classList.add("major");
  603. selDupe.type = "button";
  604.  
  605. selDupe.addEventListener('mouseup', evt => {
  606. let temp = document.querySelectorAll('#messages > tr');
  607. let msgs = [];
  608. for (let i = temp.length - 1; i >= 0; i--) {
  609. msgs.push(temp[i]);
  610. }
  611. let titles = [];
  612. msgs.forEach((msg) => {
  613. let title = msg.querySelector('a.major').textContent;
  614. if (/^New update: /.test(title)) { // Select only adventure updates
  615. if (titles.indexOf(title) === -1) {
  616. if (msg.querySelector('td').style.cssText !== "border-left: 8px solid rgb(221, 221, 221);") {
  617. titles.push(title);
  618. }
  619. } else {
  620. msg.querySelector('input').click();
  621. }
  622. }
  623. });
  624. });
  625.  
  626. // Add buttons to the page.
  627. let del = document.querySelector('#deletemsgs');
  628. del.parentNode.appendChild(document.createElement('br'));
  629. del.parentNode.appendChild(selRead);
  630. del.parentNode.appendChild(selDupe);
  631. }
  632. else if (location.pathname === "/my/stories/") {
  633. // Add links to buttons
  634. pageLoad(() => {
  635. let adventures = document.querySelectorAll('#stories tr');
  636. if (adventures.length > 0) {
  637. adventures.forEach(story => {
  638. let buttons = story.querySelectorAll('input.major');
  639. let id = story.querySelector('a').href.replace('https://mspfa.com/', '').replace('&p=1', '');
  640. if (id) {
  641. addLink(buttons[0], `/my/stories/info/${id}`);
  642. addLink(buttons[1], `/my/stories/pages/${id}`);
  643. }
  644. });
  645. return true;
  646. }
  647. });
  648.  
  649. // Add user guides
  650. let guides = ["A Guide To Uploading Your Comic To MSPFA", "MSPFA Etiquette", "Fanventure Guide for Dummies", "CSS Guide", "HTML and CSS Things", ];
  651. let links = ["https://docs.google.com/document/d/17QI6Cv_BMbr8l06RrRzysoRjASJ-ruWioEtVZfzvBzU/edit?usp=sharing", "/?s=27631", "/?s=29299", "/?s=21099", "/?s=23711"];
  652. let authors = ["Farfrom Tile", "Radical Dude 42", "nzar", "MadCreativity", "seymour schlong"];
  653.  
  654. let parentTd = document.querySelector('.container > tbody > tr:last-child > td');
  655. let unofficial = parentTd.querySelector('span');
  656. unofficial.textContent = "Unofficial Guides";
  657. let guideTable = document.createElement('table');
  658. let guideTbody = document.createElement('tbody');
  659. guideTable.style.width = "100%";
  660. guideTable.style.textAlign = "center";
  661.  
  662. guideTable.appendChild(guideTbody);
  663. parentTd.appendChild(guideTable);
  664.  
  665. for (let i = 0; i < guides.length; i++) {
  666. let guideTr = guideTbody.insertRow(i);
  667. let guideTd = guideTr.insertCell(0);
  668. let guideLink = document.createElement('a');
  669. guideLink.href = links[i];
  670. guideLink.textContent = guides[i];
  671. guideLink.className = "major";
  672. guideTd.appendChild(guideLink);
  673. guideTd.appendChild(document.createElement('br'));
  674. guideTd.appendChild(document.createTextNode('by '+authors[i]));
  675. guideTd.appendChild(document.createElement('br'));
  676. guideTd.appendChild(document.createElement('br'));
  677. }
  678. }
  679. else if (location.pathname === "/my/stories/info/" && location.search) {
  680. // Button links
  681. addLink(document.querySelector('#userfavs'), `/readers/${location.search}`);
  682. addLink(document.querySelector('#editpages'), `/my/stories/pages/${location.search}`);
  683. }
  684. else if (location.pathname === "/my/stories/pages/" && location.search) {
  685. let adventureID = /\?s=(\d{5})/.exec(location.search)[1];
  686.  
  687. // Button links
  688. addLink(document.querySelector('#editinfo'), `/my/stories/info/${adventureID}`);
  689.  
  690. // Default spoiler values...
  691. let replaceButton = document.querySelector('#replaceall');
  692. let spoilerButton = document.createElement('input');
  693. spoilerButton.classList.add('major');
  694. spoilerButton.value = 'Default Spoiler Values';
  695. spoilerButton.type = 'button';
  696. replaceButton.parentNode.insertBefore(spoilerButton, replaceButton);
  697. replaceButton.parentNode.insertBefore(document.createElement('br'), replaceButton);
  698. replaceButton.parentNode.insertBefore(document.createElement('br'), replaceButton);
  699.  
  700. let spoilerSpan = document.createElement('span');
  701. let spoilerOpen = document.createElement('input');
  702. let spoilerClose = document.createElement('input');
  703. spoilerSpan.appendChild(document.createTextNode('Open button text:'));
  704. spoilerSpan.appendChild(document.createElement('br'));
  705. spoilerSpan.appendChild(spoilerOpen);
  706. spoilerSpan.appendChild(document.createElement('br'));
  707. spoilerSpan.appendChild(document.createElement('br'));
  708. spoilerSpan.appendChild(document.createTextNode('Close button text:'));
  709. spoilerSpan.appendChild(document.createElement('br'));
  710. spoilerSpan.appendChild(spoilerClose);
  711.  
  712. if (!settings.spoilerValues[adventureID]) {
  713. settings.spoilerValues[adventureID] = {
  714. open: 'Show',
  715. close: 'Hide'
  716. }
  717. }
  718.  
  719. spoilerOpen.value = settings.spoilerValues[adventureID].open;
  720. spoilerClose.value = settings.spoilerValues[adventureID].close;
  721.  
  722. spoilerButton.addEventListener('click', evt => {
  723. window.MSPFA.dialog('Default Spoiler Values', spoilerSpan, ['Save', 'Cancel'], (output, form) => {
  724. if (output === 'Save') {
  725. settings.spoilerValues[adventureID].open = spoilerOpen.value === '' ? 'Show' : spoilerOpen.value;
  726. settings.spoilerValues[adventureID].close = spoilerClose.value === '' ? 'Hide' : spoilerClose.value;
  727. if (settings.spoilerValues[adventureID].open === 'Show' && settings.spoilerValues[adventureID].close === 'Hide') {
  728. delete settings.spoilerValues[adventureID];
  729. }
  730. saveData(settings);
  731. }
  732. });
  733. });
  734.  
  735. document.querySelector('input[title="Spoiler"]').addEventListener('click', evt => {
  736. document.querySelector('#dialog input[name="open"]').value = settings.spoilerValues[adventureID].open;
  737. document.querySelector('#dialog input[name="close"]').value = settings.spoilerValues[adventureID].close;
  738. });
  739.  
  740. // Buttonless spoilers
  741. let flashButton = document.querySelector('input[title="Flash');
  742. let newSpoilerButton = document.createElement('input');
  743. newSpoilerButton.setAttribute('data-tag', 'Buttonless Spoiler');
  744. newSpoilerButton.title = 'Buttonless Spoiler';
  745. newSpoilerButton.type = 'button';
  746. newSpoilerButton.style = 'background-image: url("https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/HTML%20and%20CSS%20Things/icons.png"); background-position: -66px -88px;';
  747.  
  748. newSpoilerButton.addEventListener('click', evt => {
  749. let bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea');
  750. if (bbe) {
  751. bbe.focus();
  752. let start = bbe.selectionStart;
  753. let end = bbe.selectionEnd;
  754. bbe.value = bbe.value.slice(0, start) + '<div class="spoiler"><div>' + bbe.value.slice(start, end) + '</div></div>' + bbe.value.slice(end);
  755. bbe.selectionStart = start + 26;
  756. bbe.selectionEnd = end + 26;
  757. }
  758. });
  759.  
  760. flashButton.parentNode.insertBefore(newSpoilerButton, flashButton);
  761. }
  762. else if (location.pathname === "/user/") {
  763. let id = location.search.slice(3);
  764. const statAdd = [];
  765. // Button links
  766. pageLoad(() => {
  767. let msgButton = document.querySelector('#sendmsg');
  768. if (msgButton) {
  769. addLink(msgButton, '/my/messages/new/'); // note: doesn't input the desired user's id
  770. addLink(document.querySelector('#favstories'), `/favs/${location.search}`);
  771. return true;
  772. }
  773. });
  774.  
  775. // Add extra user stats
  776. pageLoad(() => {
  777. if (window.MSPFA) {
  778. let stats = document.querySelector('#userinfo table');
  779.  
  780. let joinTr = stats.insertRow(1);
  781. let joinTextTd = joinTr.insertCell(0);
  782. joinTextTd.appendChild(document.createTextNode("Account created:"));
  783. let joinDate = joinTr.insertCell(1);
  784. let joinTime = document.createElement('b');
  785. joinTime.textContent = "Loading...";
  786. joinDate.appendChild(joinTime);
  787.  
  788. let advCountTr = stats.insertRow(2);
  789. let advTextTd = advCountTr.insertCell(0);
  790. advTextTd.appendChild(document.createTextNode("Adventures created:"));
  791. let advCount = advCountTr.insertCell(1);
  792. let advCountText = document.createElement('b');
  793. advCountText.textContent = "Loading...";
  794. advCount.appendChild(advCountText);
  795.  
  796. if (statAdd.indexOf('date') === -1) {
  797. window.MSPFA.request(0, {
  798. do: "user",
  799. u: id
  800. }, user => {
  801. if (typeof user !== "undefined") {
  802. statAdd.push('date');
  803. let d = new Date(user.d).toString().split(' ').splice(1, 4).join(' ');
  804. joinTime.textContent = d;
  805. }
  806. });
  807. }
  808.  
  809. if (statAdd.indexOf('made') === -1) {
  810. window.MSPFA.request(0, {
  811. do: "editor",
  812. u: id
  813. }, s => {
  814. if (typeof s !== "undefined") {
  815. statAdd.push('made');
  816. advCountText.textContent = s.length;
  817. }
  818. });
  819. }
  820.  
  821. if (document.querySelector('#favstories').style.display !== 'none' && statAdd.indexOf('fav') === -1) {
  822. statAdd.push('fav');
  823. let favCountTr = stats.insertRow(3);
  824. let favTextTd = favCountTr.insertCell(0);
  825. favTextTd.appendChild(document.createTextNode("Adventures favorited:"));
  826. let favCount = favCountTr.insertCell(1);
  827. let favCountText = document.createElement('b');
  828. favCountText.textContent = "Loading...";
  829. window.MSPFA.request(0, {
  830. do: "favs",
  831. u: id
  832. }, s => {
  833. if (typeof s !== "undefined") {
  834. favCountText.textContent = s.length;
  835. }
  836. });
  837. favCount.appendChild(favCountText);
  838. }
  839.  
  840. return true;
  841. }
  842. });
  843. }
  844. else if (location.pathname === "/favs/" && location.search) {
  845. // Button links
  846. pageLoad(() => {
  847. let stories = document.querySelectorAll('#stories tr');
  848. let favCount = 0;
  849.  
  850. if (stories.length > 0) {
  851. stories.forEach(story => {
  852. favCount++;
  853. let id = story.querySelector('a').href.replace('https://mspfa.com/', '');
  854. pageLoad(() => {
  855. if (window.MSPFA.me.i) {
  856. addLink(story.querySelector('.edit.major'), `/my/stories/info/${id}`);
  857. return;
  858. }
  859. });
  860. addLink(story.querySelector('.rss.major'), `/rss/${id}`);
  861. });
  862.  
  863. // Fav count
  864. let username = document.querySelector('#username');
  865. username.parentNode.appendChild(document.createElement('br'));
  866. username.parentNode.appendChild(document.createElement('br'));
  867. username.parentNode.appendChild(document.createTextNode(`Favorited adventures: ${favCount}`));
  868.  
  869. return true;
  870. }
  871. });
  872. }
  873. else if (location.pathname === "/search/" && location.search) {
  874. // Character and word statistics
  875. let statTable = document.createElement('table');
  876. let statTbody = document.createElement('tbody');
  877. let statTr = statTbody.insertRow(0);
  878. let charCount = statTr.insertCell(0);
  879. let wordCount = statTr.insertCell(0);
  880. let statParentTr = document.querySelector('#pages').parentNode.parentNode.insertRow(2);
  881. let statParentTd = statParentTr.insertCell(0);
  882.  
  883. let statHeaderTr = statTbody.insertRow(0);
  884. let statHeader = document.createElement('th');
  885. statHeader.colSpan = '2';
  886.  
  887. statHeaderTr.appendChild(statHeader);
  888. statHeader.textContent = 'Statistics may not be entirely accurate.';
  889.  
  890. statTable.style.width = "100%";
  891.  
  892. charCount.textContent = "Character count: loading...";
  893. wordCount.textContent = "Word count: loading...";
  894.  
  895. statTable.appendChild(statTbody);
  896. statParentTd.appendChild(statTable);
  897.  
  898. pageLoad(() => {
  899. if (document.querySelector('#pages br')) {
  900. let bbc = window.MSPFA.BBC.slice();
  901. bbc.splice(0, 3);
  902.  
  903. window.MSPFA.request(0, {
  904. do: "story",
  905. s: location.search.replace('?s=', '')
  906. }, story => {
  907. if (typeof story !== "undefined") {
  908. let pageContent = [];
  909. story.p.forEach(p => {
  910. pageContent.push(p.c);
  911. pageContent.push(p.b);
  912. });
  913.  
  914. let storyText = pageContent.join(' ')
  915. .replace(/\n/g, ' ')
  916. .replace(bbc[0][0], '$1')
  917. .replace(bbc[1][0], '$1')
  918. .replace(bbc[2][0], '$1')
  919. .replace(bbc[3][0], '$1')
  920. .replace(bbc[4][0], '$2')
  921. .replace(bbc[5][0], '$3')
  922. .replace(bbc[6][0], '$3')
  923. .replace(bbc[7][0], '$3')
  924. .replace(bbc[8][0], '$3')
  925. .replace(bbc[9][0], '$3')
  926. .replace(bbc[10][0], '$2')
  927. .replace(bbc[11][0], '$1')
  928. .replace(bbc[12][0], '$3')
  929. .replace(bbc[13][0], '$3')
  930. .replace(bbc[14][0], '')
  931. .replace(bbc[16][0], '$1')
  932. .replace(bbc[17][0], '$2 $4 $5')
  933. .replace(bbc[18][0], '$2 $4 $5')
  934. .replace(bbc[19][0], '')
  935. .replace(bbc[20][0], '')
  936. .replace(/<(.*?)>/g, '');
  937.  
  938. wordCount.textContent = `Word count: ${storyText.split(/ +/g).length}`;
  939. charCount.textContent = `Character count: ${storyText.replace(/ +/g, '').length}`;
  940. }
  941. });
  942. return true;
  943. }
  944. });
  945. }
  946. })();