MSPFA extras

Adds custom quality of life features to MSPFA.

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

  1. // ==UserScript==
  2. // @name MSPFA extras
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.6.7
  5. // @description Adds custom quality of life features to MSPFA.
  6. // @author seymour schlong
  7. // @icon https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/ico.png
  8. // @icon64 https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/ico.png
  9. // @match https://mspfa.com/
  10. // @match https://mspfa.com/*/
  11. // @match https://mspfa.com/*/?*
  12. // @match https://mspfa.com/?s=*
  13. // @match https://mspfa.com/my/*
  14. // @match https://mspfa.com/random/
  15. // @exclude https://mspfa.com/js/*
  16. // @exclude https://mspfa.com/css/*
  17. // @grant none
  18. // ==/UserScript==
  19.  
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. const currentVersion = "1.6.7";
  25. console.log(`MSPFA extras script v${currentVersion} by seymour schlong`);
  26.  
  27. const debug = false;
  28.  
  29. /**
  30. * https://github.com/GrantGryczan/MSPFA/projects/1?fullscreen=true
  31. * Github to-do completion list (and other stuff too)
  32. *
  33. * https://github.com/GrantGryczan/MSPFA/issues/26 - Dropdown menu - February 23rd, 2020
  34. * https://github.com/GrantGryczan/MSPFA/issues/18 - MSPFA themes - February 23rd, 2020
  35. * https://github.com/GrantGryczan/MSPFA/issues/32 - Adventure creation dates - February 23rd, 2020
  36. * https://github.com/GrantGryczan/MSPFA/issues/32 - User creation dates - February 23rd, 2020
  37. * https://github.com/GrantGryczan/MSPFA/issues/40 - Turn certain buttons into links - July 21st, 2020
  38. * https://github.com/GrantGryczan/MSPFA/issues/41 - Word and character count - July 21st, 2020
  39. * https://github.com/GrantGryczan/MSPFA/issues/57 - Default spoiler values - August 7th, 2020
  40. * https://github.com/GrantGryczan/MSPFA/issues/62 - Buttonless spoilers - August 7th, 2020
  41. * https://github.com/GrantGryczan/MSPFA/issues/52 - Hash URLs - August 8th, 2020
  42. * - Page drafts - August 8th, 2020
  43. * - Edit pages button - August 8th, 2020
  44. * - Image preloading - August 20th, 2020
  45. * https://github.com/GrantGryczan/MSPFA/issues/19 - Manage game saves - August 22nd, 2020
  46. *
  47. * Extension to-do... maybe...
  48. *
  49. * 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.
  50. * When adding a new page, store it in an array and if that array length is > 1 when someone tries to save, prompt them to press Save All?
  51. */
  52.  
  53. // A general function that allows for waiting until a certain element appears on the page.
  54. const pageLoad = (fn, length) => {
  55. const interval = setInterval(() => {
  56. if (fn()) clearInterval(interval);
  57. }, length ? length*1000 : 500);
  58. };
  59.  
  60. // Saves the options data for the script.
  61. const saveData = (data) => {
  62. localStorage.mspfaextra = JSON.stringify(data);
  63. if (debug) {
  64. console.log('Settings:');
  65. console.log(data);
  66. }
  67. };
  68.  
  69. // Saves the data for drafts
  70. const saveDrafts = (data) => {
  71. localStorage.mspfadrafts = JSON.stringify(data);
  72. if (debug) {
  73. console.log('Drafts:');
  74. console.log(data);
  75. }
  76. };
  77.  
  78. // Encases an element within a link
  79. const addLink = (elm, url) => {
  80. const link = document.createElement('a');
  81. link.href = url;
  82. elm.parentNode.insertBefore(link, elm);
  83. link.appendChild(elm);
  84. return link;
  85. };
  86.  
  87. // Returns true if version 2 is newer
  88. const compareVer = (ver1, ver2) => {
  89. ver1 = ver1.split(/\./); // current version
  90. ver2 = ver2.split(/\./); // new version
  91. ver1.push(0);
  92. ver1.push(0);
  93. ver2.push(0);
  94. ver2.push(0);
  95. if (parseInt(ver2[0]) > parseInt(ver1[0])) { // 1.x.x.x
  96. return true;
  97. } else if (parseInt(ver2[1]) > parseInt(ver1[1])) { // x.1.x.x
  98. return true;
  99. } else if (parseInt(ver2[2]) > parseInt(ver1[2])) { // x.x.1.x
  100. return true;
  101. } else if (parseInt(ver2[3]) > parseInt(ver1[3]) && parseInt(ver2[2]) === parseInt(ver1[2])) { // x.x.x.1
  102. return true;
  103. }
  104. return false;
  105. }
  106.  
  107. // Easy br element
  108. const newBr = () => {
  109. return document.createElement('br');
  110. }
  111.  
  112. let settings = {};
  113. let drafts = {};
  114.  
  115. const defaultSettings = {
  116. autospoiler: false,
  117. style: 0,
  118. styleURL: "",
  119. night: false,
  120. auto502: true,
  121. textFix: false,
  122. pixelFix: false,
  123. intro: false,
  124. autoUpdate: true,
  125. commandScroll: false,
  126. preload: true,
  127. version: currentVersion,
  128. spoilerValues: {}
  129. }
  130.  
  131. let pageLoaded = false;
  132.  
  133. if (localStorage.mspfadrafts) {
  134. Object.assign(drafts, JSON.parse(localStorage.mspfadrafts));
  135. }
  136.  
  137. // Load any previous settings from localStorage
  138. if (localStorage.mspfaextra) {
  139. Object.assign(settings, JSON.parse(localStorage.mspfaextra));
  140.  
  141. // Get draft data from settings
  142. if (typeof settings.drafts === "object") {
  143. if (Object.keys(settings.drafts).length > 0 && Object.keys(drafts).length === 0) {
  144. drafts = settings.drafts;
  145. }
  146. }
  147. saveDrafts(drafts);
  148. }
  149.  
  150. // If any settings are undefined, re-set to their default state. (For older users when new things get stored)
  151. const checkSettings = () => {
  152. const defaultSettingsKeys = Object.keys(defaultSettings);
  153. for (let i = 0; i < defaultSettingsKeys.length; i++) {
  154. if (typeof settings[defaultSettingsKeys[i]] === "undefined") {
  155. settings[defaultSettingsKeys[i]] = defaultSettings[defaultSettingsKeys[i]];
  156. }
  157. }
  158. saveData(settings);
  159. }
  160.  
  161. checkSettings();
  162.  
  163. // Update saved version to the version used in the script to prevent unnecessary notifications
  164. if (compareVer(settings.version, currentVersion)) {
  165. settings.version = currentVersion;
  166. saveData(settings);
  167. }
  168.  
  169. // Scrolls you to where you need to be
  170. const hashSearch = location.href.replace(location.origin + location.pathname, '').replace(location.search, '');
  171. if (hashSearch !== '') {
  172. pageLoad(() => {
  173. const idElement = document.querySelector(hashSearch);
  174. if (idElement) {
  175. const selected = document.querySelector(hashSearch);
  176. selected.scrollIntoView();
  177. selected.style.boxShadow = '1px 1px 5px red, -1px -1px 5px red, -1px 1px 5px red, -1px 1px 5px red';
  178. selected.style.transition = '0.5s';
  179. pageLoad(() => {
  180. if (pageLoaded) {
  181. selected.style.boxShadow = '';
  182. }
  183. });
  184.  
  185. return true;
  186. }
  187. }, 1);
  188. }
  189.  
  190. // Ripped shamelessly right from mspfa lol (URL search parameters -- story ID, page num, etc.)
  191. let rawParams;
  192. if (location.href.indexOf("#") != -1) {
  193. rawParams = location.href.slice(0, location.href.indexOf("#"));
  194. } else {
  195. rawParams = location.href;
  196. }
  197. if (rawParams.indexOf("?") != -1) {
  198. rawParams = rawParams.slice(rawParams.indexOf("?") + 1).split("&");
  199. } else {
  200. rawParams = [];
  201. }
  202. const params = {};
  203. for (let i = 0; i < rawParams.length; i++) {
  204. try {
  205. const p = rawParams[i].split("=");
  206. params[p[0]] = decodeURIComponent(p[1]);
  207. } catch (err) {}
  208. }
  209.  
  210. if (debug) {
  211. console.log('URL parameters:');
  212. console.log(params);
  213. }
  214.  
  215. // Functions to get/change data from the console
  216. window.MSPFAe = {
  217. getSettings: () => {
  218. return settings;
  219. },
  220. getSettingsString: (formatted) => {
  221. if (formatted) {
  222. console.log(JSON.stringify(settings, null, 4));
  223. } else {
  224. console.log(JSON.stringify(settings));
  225. }
  226. },
  227. getDrafts: () => {
  228. return drafts;
  229. },
  230. getDraftsString: (formatted) => {
  231. if (formatted) {
  232. console.log(JSON.stringify(drafts, null, 4));
  233. } else {
  234. console.log(JSON.stringify(drafts));
  235. }
  236. },
  237. changeSettings: (newSettings) => {
  238. console.log('Settings updated');
  239. console.log(settings);
  240. Object.assign(settings, newSettings);
  241. saveData(settings);
  242. },
  243. changeSettingsString: (fullString) => {
  244. try {
  245. JSON.parse(fullString);
  246. } catch (err) {
  247. console.error(err);
  248. return;
  249. }
  250. settings = JSON.parse(fullString);
  251. checkSettings();
  252. console.log(settings);
  253. },
  254. getParams: params
  255. }
  256.  
  257. // Delete any unchanged spoiler values or empty drafts
  258. if (location.pathname !== "/my/stories/pages/") {
  259. // Go through spoiler values and remove any that aren't unique
  260. Object.keys(settings.spoilerValues).forEach(adventure => {
  261. if (settings.spoilerValues[adventure].open === "Show" && settings.spoilerValues[adventure].close === "Hide") {
  262. delete settings.spoilerValues[adventure];
  263. } else if (settings.spoilerValues[adventure].open === '' && settings.spoilerValues[adventure].close === '') {
  264. delete settings.spoilerValues[adventure];
  265. }
  266. });
  267. // Go through and remove adventures with empty drafts
  268. Object.keys(drafts).forEach(adventure => {
  269. if (Object.keys(drafts[adventure]).length === 0) {
  270. delete drafts[adventure];
  271. }
  272. });
  273. }
  274.  
  275. const styleOptions = ["Standard", "Low Contrast", "Light", "Dark", "Felt", "Trickster", "Custom"];
  276. const styleUrls = ['', '/css/theme1.css', '/css/theme2.css', '/css/?s=36237', '/css/theme4.css', '/css/theme5.css'];
  277.  
  278. // Dropdown menu
  279. const myLink = document.querySelector('nav a[href="/my/"]');
  280. if (myLink) {
  281. const dropDiv = document.createElement('div');
  282. dropDiv.className = 'dropdown';
  283. Object.assign(dropDiv.style, {
  284. position: 'relative',
  285. display: 'inline-block',
  286. backgroundColor: 'inherit'
  287. });
  288.  
  289. const dropContent = document.createElement('div');
  290. dropContent.className = 'dropdown-content';
  291. Object.assign(dropContent.style, {
  292. display: 'none',
  293. backgroundColor: 'inherit',
  294. position: 'absolute',
  295. textAlign: 'left',
  296. minWidth: '100px',
  297. marginLeft: '-5px',
  298. padding: '2px',
  299. zIndex: '1',
  300. borderRadius: '0 0 5px 5px'
  301. });
  302.  
  303. dropDiv.addEventListener('mouseenter', evt => {
  304. dropContent.style.display = 'block';
  305. dropContent.style.color = getComputedStyle(myLink).color;
  306. dropContent.querySelectorAll('a').forEach(link => {
  307. link.style.color = getComputedStyle(myLink).color;
  308. });
  309. });
  310. dropDiv.addEventListener('mouseleave', evt => {
  311. dropContent.style.display = 'none';
  312. });
  313.  
  314. myLink.parentNode.insertBefore(dropDiv, myLink);
  315. dropDiv.appendChild(myLink);
  316. dropDiv.appendChild(dropContent);
  317.  
  318. const dLinks = [];
  319. dLinks[0] = [ 'Messages', 'My Adventures', 'Settings' ];
  320. dLinks[1] = [ '/my/messages/', '/my/stories/', '/my/settings/' ];
  321.  
  322. for (let i = 0; i < dLinks[0].length; i++) {
  323. const newLink = document.createElement('a');
  324. newLink.textContent = dLinks[0][i];
  325. newLink.href = dLinks[1][i];
  326. newLink.style = 'visibility: visible; word-spacing: normal; letter-spacing: normal; font-size: 10px;';
  327. dropContent.appendChild(newLink);
  328. }
  329.  
  330. // Append "My Profile" to the dropdown list if you're signed in
  331. pageLoad(() => {
  332. if (window.MSPFA) {
  333. if (window.MSPFA.me.n) {
  334. const newLink = document.createElement('a');
  335. newLink.textContent = "My Profile";
  336. newLink.href = `/user/?u=${window.MSPFA.me.i}`;
  337. newLink.style = 'visibility: visible; word-spacing: normal; letter-spacing: normal; font-size: 10px;';
  338. dropContent.appendChild(newLink);
  339. return true;
  340. }
  341. }
  342. });
  343. }
  344.  
  345. // Dropdown search bar
  346. const exploreLink = document.querySelector('nav a[href="/stories/"');
  347. if (exploreLink) {
  348. const dropDiv = document.createElement('div');
  349. dropDiv.className = 'dropdown';
  350. Object.assign(dropDiv.style, {
  351. position: 'relative',
  352. display: 'inline-block',
  353. backgroundColor: 'inherit'
  354. });
  355.  
  356. const dropContent = document.createElement('div');
  357. dropContent.className = 'dropdown-content';
  358. Object.assign(dropContent.style, {
  359. display: 'none',
  360. backgroundColor: 'inherit',
  361. position: 'absolute',
  362. textAlign: 'left',
  363. minWidth: '100px',
  364. marginLeft: '-5px',
  365. padding: '2px',
  366. zIndex: '1',
  367. borderRadius: '0 0 5px 5px'
  368. });
  369.  
  370. exploreLink.parentNode.insertBefore(dropDiv, exploreLink);
  371. dropDiv.appendChild(exploreLink);
  372. dropDiv.appendChild(dropContent);
  373.  
  374. const exploreInput = document.createElement('input');
  375. Object.assign(exploreInput, { type: 'text', placeholder: 'Search...', style: 'font-size: 11px; width: 90%; border: none; border-radius: 3px; margin: auto; display: block;' });
  376. dropContent.appendChild(exploreInput);
  377. exploreInput.addEventListener('keydown', ke => {
  378. if (ke.code === 'Enter') {
  379. location.href = `/stories/?go=1&n=${encodeURIComponent(exploreInput.value)}&t=&h=14&o=favs&p=p&m=50&load=true`;
  380. return;
  381. }
  382. });
  383.  
  384. dropDiv.addEventListener('mouseenter', evt => {
  385. dropContent.style.display = 'block';
  386. });
  387. dropDiv.addEventListener('mouseleave', evt => {
  388. // If input is focused
  389. if (document.activeElement !== exploreInput) {
  390. dropContent.style.display = 'none';
  391. }
  392. });
  393. document.body.addEventListener('click', evt => {
  394. if (document.activeElement !== exploreInput) {
  395. dropContent.style.display = 'none';
  396. }
  397. });
  398. }
  399.  
  400. // Error reloading
  401. window.addEventListener("load", () => {
  402. pageLoaded = true;
  403.  
  404. // Reload the page if 502 CloudFlare error page appears
  405. if (settings.auto502 && document.querySelector('.cf-error-overview')) {
  406. window.location.reload();
  407. }
  408.  
  409. // Wait five seconds, then refresh the page
  410. if (document.body.textContent === "Your client is sending data to MSPFA too quickly. Wait a moment before continuing.") {
  411. setTimeout(() => {
  412. window.location.reload();
  413. }, 5000);
  414. }
  415. });
  416.  
  417. // Message that shows when you first get the script
  418. const showIntroDialog = () => {
  419. const 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]');
  420. window.MSPFA.dialog("MSPFA extras message", msg, ["Okay"]);
  421. }
  422.  
  423. // Check for updates by comparing currentVersion to text data from a text file update text and info
  424. const checkForUpdates = (evt) => {
  425. fetch(`https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/update.txt?rand=${Math.random()}`, { cache:'no-store' }).then((req) => {
  426. if (req.ok) {
  427. return req.text().then(r => {
  428. const version = /^{(.*?)}/.exec(r)[1];
  429. const content = r.replace(/^{(.*?)}\r\n/, '');
  430.  
  431. if (compareVer(settings.version, version) || (evt && evt.type === 'click')) {
  432. const msg = window.MSPFA.parseBBCode(content);
  433. settings.version = version;
  434. saveData(settings);
  435. window.MSPFA.dialog(`MSPFA extras update! (${version})`, msg, ["Opt-out", "Dismiss", "Update"], (output, form) => {
  436. if (output === "Update") {
  437. window.open('https://greasyfork.org/scripts/396798-mspfa-extras/code/MSPFA%20extras.user.js', '_blank').focus();
  438. } else if (output === "Opt-out") {
  439. settings.autoUpdate = false;
  440. saveData(settings);
  441. }
  442. });
  443. } else {
  444. console.log('No new update found.');
  445. }
  446. });
  447. }
  448. });
  449. };
  450.  
  451. // Check for updates and show intro dialog if needed
  452. pageLoad(() => {
  453. if (window.MSPFA) {
  454. if (settings.autoUpdate) {
  455. console.log('Checking for updates...');
  456. checkForUpdates();
  457. }
  458.  
  459. if (!settings.intro) {
  460. showIntroDialog();
  461. settings.intro = true;
  462. saveData(settings);
  463. }
  464. return true;
  465. }
  466. });
  467.  
  468. const details = document.querySelector('#details');
  469.  
  470. // Add 'link' at the bottom to show the intro dialog again
  471. const introLink = document.createElement('a');
  472. introLink.textContent = 'View Script Message';
  473. introLink.href = 'javascript:void(0);';
  474. introLink.addEventListener('click', showIntroDialog);
  475. details.appendChild(introLink);
  476.  
  477. // vbar!!!!
  478. const vbar = document.createElement('span');
  479. Object.assign(vbar, {className: 'vbar', style: 'padding: 0 5px', textContent: '|'});
  480. details.appendChild(vbar);
  481.  
  482. // Add 'link' at the bottom to show the update dialog again
  483. const updateLink = document.createElement('a');
  484. updateLink.textContent = 'View Update';
  485. updateLink.href = 'javascript:void(0);';
  486. updateLink.addEventListener('click', checkForUpdates);
  487. details.appendChild(updateLink);
  488.  
  489. // vbar 2!!!!
  490. const vbar2 = document.createElement('span');
  491. Object.assign(vbar2, {className: 'vbar', style: 'padding: 0 5px', textContent: '|'});
  492. details.appendChild(vbar2);
  493.  
  494. // if you really enjoy the script and has some extra moneys 🥺
  495. const donateLink = document.createElement('a');
  496. donateLink.textContent = 'Donate';
  497. donateLink.href = 'https://ko-fi.com/ironbean';
  498. donateLink.target = "blank";
  499. details.appendChild(donateLink);
  500.  
  501. // Theme stuff
  502. const theme = document.createElement('link');
  503. Object.assign(theme, { id: 'theme', type: 'text/css', rel: 'stylesheet' });
  504. const updateTheme = (src) => {
  505. theme.href = src;
  506. }
  507. if (!document.querySelector('#theme')) {
  508. document.querySelector('head').appendChild(theme);
  509. if (settings.night) {
  510. updateTheme('/css/?s=36237');
  511. } else {
  512. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  513. }
  514. }
  515.  
  516. // Dropdown menu and pixelated scaling
  517. const dropStyle = document.createElement('style');
  518. const pixelFixText = 'img, .mspfalogo, .major, .arrow, #flashytitle, .heart, .fav, .notify, .edit, .rss, input, #loading { image-rendering: pixelated !important; }';
  519. const dropStyleText = `#notification { z-index: 2; } .dropdown-content a { color: inherit; padding: 2px; text-decoration: underline; display: block;}`;
  520. if (!document.querySelector('#dropdown-style')) {
  521. dropStyle.id = 'dropdown-style';
  522. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  523. document.querySelector('head').appendChild(dropStyle);
  524. }
  525.  
  526. // Enabling night mode.
  527. document.querySelector('footer .mspfalogo').addEventListener('click', evt => {
  528. settings.night = !settings.night;
  529. saveData(settings);
  530.  
  531. // Transition to make it feel nicer on the eyes
  532. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + '';
  533. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + ' *{transition:1s}';
  534.  
  535. if (settings.night) {
  536. updateTheme('/css/?s=36237');
  537. } else {
  538. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  539. }
  540.  
  541. setTimeout(() => {
  542. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  543. }, 1000);
  544.  
  545. console.log(`Night mode turned ${settings.night ? 'on' : 'off'}.`);
  546. });
  547.  
  548. if (location.pathname.includes('//')) {
  549. location.href = location.pathname.replace(/\/\//g, '/') + location.search;
  550. }
  551.  
  552. if (location.pathname === "/" || location.pathname === "/preview/") {
  553. if (location.search) {
  554. // Remove the current theme if the adventure has CSS (to prevent conflicts);
  555. if (settings.style > 0) {
  556. pageLoad(() => {
  557. if (window.MSPFA) {
  558. if (window.MSPFA.story && window.MSPFA.story.y && (window.MSPFA.story.y.toLowerCase().includes('import') || window.MSPFA.story.y.includes('{'))) {
  559. if (!settings.night) updateTheme('');
  560. return true;
  561. }
  562. }
  563. if (pageLoaded) return true;
  564. });
  565. }
  566.  
  567. // Preload the next page
  568. if (settings.preload) {
  569. const preloadImages = document.createElement('div');
  570. preloadImages.id = 'preload';
  571. preloadImages.style.display = 'none';
  572. document.querySelector('#container').appendChild(preloadImages);
  573.  
  574. window.MSPFA.slide.push(p => {
  575. preloadImages.innerHTML = '';
  576. if (window.MSPFA.story.p[p-2]) {
  577. window.MSPFA.parseBBCode(window.MSPFA.story.p[p-2].b).querySelectorAll('img').forEach(image => {
  578. preloadImages.appendChild(image);
  579. });
  580. }
  581. if (window.MSPFA.story.p[p]) {
  582. window.MSPFA.parseBBCode(window.MSPFA.story.p[p].b).querySelectorAll('img').forEach(image => {
  583. preloadImages.appendChild(image);
  584. });
  585. }
  586. document.querySelectorAll('#slide img').forEach(image => {
  587. if (image.src.toLowerCase().includes('.gif')) {
  588. //image.src = `${image.src}?r=${Math.random()}`;
  589. }
  590. });
  591. });
  592. }
  593.  
  594. // Scroll up to the nav bar when changing page so you don't have to scroll down as much =)
  595. if (settings.commandScroll) {
  596. const heightTop = document.querySelector('nav').getBoundingClientRect().top - document.body.getBoundingClientRect().top;
  597. let temp = -2; // To prevent moving the page down when loading it for the first time
  598. window.MSPFA.slide.push((p) => {
  599. if (temp < 0) {
  600. temp++;
  601. } else {
  602. window.scroll(0, heightTop);
  603. }
  604. });
  605. }
  606.  
  607. // Automatic spoiler opening
  608. if (settings.autospoiler) {
  609. window.MSPFA.slide.push((p) => {
  610. document.querySelectorAll('#slide .spoiler:not(.open) > div:first-child > input').forEach(sb => sb.click());
  611. });
  612. }
  613.  
  614. // Show creation date
  615. pageLoad(() => {
  616. if (document.querySelector('#infobox tr td:nth-child(2)')) {
  617. 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(' ')));
  618. return true;
  619. }
  620. });
  621.  
  622. // Hash scrolling and opening infobox or commmentbox
  623. if (['#infobox', '#commentbox', '#newcomment', '#latestpages'].indexOf(hashSearch) !== -1) {
  624. pageLoad(() => {
  625. if (document.querySelector(hashSearch)) {
  626. if (hashSearch === '#infobox') {
  627. document.querySelector('input[data-open="Show Adventure Info"]').click();
  628. } else if (hashSearch === '#commentbox' || hashSearch === '#newcomment') {
  629. document.querySelector('input[data-open="Show Comments"]').click();
  630. } else if (hashSearch === '#latestpages') {
  631. document.querySelector('input[data-open="Show Adventure Info"]').click();
  632. document.querySelector('input[data-open="Show Latest Pages"]').click();
  633. }
  634. return true;
  635. }
  636. });
  637. }
  638.  
  639. // Attempt to fix text errors
  640. if (settings.textFix) {
  641. pageLoad(() => {
  642. if (window.MSPFA.story && window.MSPFA.story.p) {
  643. // russian/bulgarian is not possible =(
  644. const currentPage = parseInt(/^\?s(?:.*?)&p=([\d]*)$/.exec(location.search)[1]);
  645. const library = [
  646. ["&acirc;��", "'"],
  647. ["&Atilde;�", "Ñ"],
  648. ["&Atilde;&plusmn;", "ñ"],
  649. ["&Atilde;&sup3;", "ó"],
  650. ["&Atilde;&iexcl;", "á"],
  651. ["&Auml;�", "ą"],
  652. ["&Atilde;&shy;", "í"],
  653. ["&Atilde;&ordm;", "ú"],
  654. ["&Atilde;&copy;", "é"],
  655. ["&Aring;�", "ł"],
  656. ["&Aring;&frac14;", "ż"],
  657. ["&Acirc;&iexcl;", "¡"],
  658. ["&Acirc;&iquest;", "¿"],
  659. ["N&Acirc;&ordm;", "#"]
  660. ];
  661. // https://mspfa.com/?s=5280&p=51 -- unknown error
  662.  
  663. const replaceTerms = (p) => {
  664. library.forEach(term => {
  665. if (window.MSPFA.story.p[p]) {
  666. window.MSPFA.story.p[p].c = window.MSPFA.story.p[p].c.replace(new RegExp(term[0], 'g'), term[1]);
  667. window.MSPFA.story.p[p].b = window.MSPFA.story.p[p].b.replace(new RegExp(term[0], 'g'), term[1]);
  668. }
  669. });
  670. };
  671.  
  672. replaceTerms(currentPage-1);
  673.  
  674. window.MSPFA.slide.push(p => {
  675. replaceTerms(p);
  676. replaceTerms(p-2);
  677. });
  678. return true;
  679. }
  680. });
  681. }
  682.  
  683. // Turn buttons into links
  684. const pageButton = document.createElement('button');
  685. const pageLink = document.createElement('a');
  686. pageLink.href = `/my/stories/pages/?s=${params.s}#p${params.p}`;
  687. pageButton.className = 'pages edit major';
  688. pageButton.type = 'button';
  689. pageButton.title = 'Edit Pages';
  690. pageLink.style.marginRight = '9.5px';
  691. pageButton.style.backgroundImage = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAL1JREFUOI1jYKAQMCJzugoL/+NSePPhQ4a569YxoouzoAuU9vUx3AoLY1BbtYqBgYGB4d21a8jS/9ENwTBg/ezZDNpQGh2oy8szJAcFoRiCYUBgairDrd27Gdz9/Bh+vH2LInfn+nUMQ3G6YOemTRiKsQEMA9z9/Bie7N7NYG9tjaGYKBfs3LSJQZuBgeHg0aPkucDe2prhDSUuOHj06Ih3wdw5cxgYZGQYGObMId0FNx8+JKhBQlGRKIOJBgBpTFVYzRG//QAAAABJRU5ErkJggg==")';
  692. pageLink.appendChild(pageButton);
  693.  
  694. // Edit pages button & button link
  695. pageLoad(() => {
  696. const infoButton = document.querySelector('.edit.major');
  697. if (infoButton) {
  698. pageLoad(() => {
  699. if (window.MSPFA.me.i) {
  700. infoButton.title = "Edit Info";
  701. infoButton.parentNode.insertBefore(pageLink, infoButton);
  702. addLink(infoButton, `/my/stories/info/?s=${params.s}`);
  703. pageButton.style.display = document.querySelector('.edit.major:not(.pages)').style.display;
  704.  
  705. // Change change page link when switching pages
  706. window.MSPFA.slide.push(p => {
  707. const newSearch = location.search.split('&p=');
  708. pageLink.href = `/my/stories/pages/?s=${params.s}#p${newSearch[1].split('#')[0]}`;
  709. });
  710. return true;
  711. }
  712. });
  713. addLink(document.querySelector('.rss.major'), `/rss/?s=${params.s}`);
  714. return true;
  715. }
  716. });
  717.  
  718. // Add "Reply" button to comment gear
  719. document.body.addEventListener('click', evt => {
  720. if (evt.toElement.classList.contains('gear')) {
  721. const userID = evt.path[2].classList[2].replace('u', '');
  722. const reportButton = document.querySelector('#dialog button[data-value="Report"]');
  723. const replyButton = document.createElement('button');
  724. replyButton.classList.add('major');
  725. replyButton.type = 'submit';
  726. replyButton.setAttribute('data-value', 'Reply');
  727. replyButton.textContent = 'Reply';
  728. replyButton.style = 'margin-right: 9.5px';
  729. reportButton.parentNode.insertBefore(replyButton, reportButton);
  730.  
  731. replyButton.addEventListener('click', evt => {
  732. document.querySelector('#dialog button[data-value="Cancel"]').click();
  733. const commentBox = document.querySelector('#commentbox textarea');
  734. commentBox.value = `[user]${userID}[/user], ${commentBox.value}`;
  735. commentBox.focus();
  736. });
  737. } else return;
  738. });/**/
  739. }
  740. }
  741. else if (location.pathname === "/my/") {
  742. const parent = document.querySelector('#editstories').parentNode;
  743. const viewSaves = document.createElement('a');
  744. Object.assign(viewSaves, { id: 'viewsaves', className: 'major', textContent: 'View Adventure Saves' });
  745.  
  746. parent.appendChild(viewSaves);
  747. parent.appendChild(newBr());
  748. parent.appendChild(newBr());
  749.  
  750. pageLoad(() => {
  751. if (window.MSPFA && window.MSPFA.me && window.MSPFA.me.i) {
  752. viewSaves.href = `/?s=36596&p=6`;
  753. return true;
  754. }
  755. });
  756.  
  757. document.querySelector('#editstories').classList.remove('alt');
  758. }
  759. else if (location.pathname === "/my/settings/") { // Custom settings
  760. const saveBtn = document.querySelector('#savesettings');
  761.  
  762. const table = document.querySelector("#editsettings tbody");
  763. let saveTr = table.querySelectorAll("tr");
  764. saveTr = saveTr[saveTr.length - 1];
  765.  
  766. const headerTr = document.createElement('tr');
  767. const header = document.createElement('th');
  768. Object.assign(header, { id: 'extraSettings', textContent: 'Extra Settings' });
  769. headerTr.appendChild(header);
  770.  
  771. const moreTr = document.createElement('tr');
  772. const more = document.createElement('td');
  773. 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.";
  774. moreTr.appendChild(more);
  775.  
  776. const settingsTr = document.createElement('tr');
  777. const localMsg = document.createElement('span');
  778. const settingsTd = document.createElement('td');
  779. 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!";
  780. const plusTable = document.createElement('table');
  781. const plusTbody = document.createElement('tbody');
  782. plusTable.appendChild(plusTbody);
  783. settingsTd.appendChild(localMsg);
  784. settingsTd.appendChild(newBr());
  785. settingsTd.appendChild(newBr());
  786. settingsTd.appendChild(plusTable);
  787. settingsTr.appendChild(settingsTd);
  788.  
  789. plusTable.style = "text-align: center;";
  790.  
  791. // Create checkbox (soooo much better)
  792. const createCheckbox = (text, checked, id) => {
  793. const optionTr = plusTbody.insertRow(plusTbody.childNodes.length);
  794. const optionTextTd = optionTr.insertCell(0);
  795. const optionLabel = document.createElement('label');
  796. const optionInputTd = optionTr.insertCell(1);
  797. const optionInput = document.createElement('input');
  798. optionInputTd.appendChild(optionInput);
  799.  
  800. optionTextTd.appendChild(optionLabel);
  801.  
  802. optionLabel.setAttribute('for', id);
  803. optionLabel.textContent = text;
  804. optionInput.type = "checkbox";
  805. optionInput.checked = checked;
  806. optionInput.id = id;
  807.  
  808. return optionInput;
  809. }
  810.  
  811. const spoilerInput = createCheckbox("Automatically open spoilers:", settings.autospoiler, 'autospoiler');
  812. const preloadInput = createCheckbox("Preload images for the pages immediately before and after:", settings.preload, 'preload');
  813. const errorInput = createCheckbox("Automatically reload Cloudflare 502 error pages:", settings.auto502, 'auto502');
  814. const commandScrollInput = createCheckbox("Scroll back up to the nav bar when switching page:", settings.commandScroll, 'commandScroll');
  815. const updateInput = createCheckbox("Automatically check for updates:", settings.autoUpdate, 'autoUpdate');
  816. const pixelFixInput = createCheckbox("Change pixel scaling to nearest neighbour:", settings.pixelFix, 'pixelFix');
  817. const textFixInput = createCheckbox("Attempt to fix text errors (experimental)*:", settings.textFix, 'textFix');
  818.  
  819. const cssTr = plusTbody.insertRow(plusTbody.childNodes.length);
  820. const cssTextTd = cssTr.insertCell(0);
  821. const cssSelectTd = cssTr.insertCell(1);
  822. const cssSelect = document.createElement('select');
  823. cssSelectTd.appendChild(cssSelect);
  824.  
  825. cssTextTd.textContent = "Change style:";
  826.  
  827. const customTr = plusTbody.insertRow(plusTbody.childNodes.length);
  828. const customTextTd = customTr.insertCell(0);
  829. const customCssTd = customTr.insertCell(1);
  830. const customCssInput = document.createElement('input');
  831. customCssTd.appendChild(customCssInput);
  832.  
  833. customTextTd.textContent = "Custom CSS URL:";
  834. customCssInput.style.width = "99px";
  835. customCssInput.value = settings.styleURL;
  836.  
  837. styleOptions.forEach(o => cssSelect.appendChild(new Option(o, o)));
  838.  
  839. saveTr.parentNode.insertBefore(headerTr, saveTr);
  840. saveTr.parentNode.insertBefore(settingsTr, saveTr);
  841. saveTr.parentNode.insertBefore(moreTr, saveTr);
  842. cssSelect.selectedIndex = settings.style;
  843.  
  844. const buttonSpan = document.createElement('span');
  845. const draftButton = document.createElement('input');
  846. const spoilerButton = document.createElement('input');
  847. draftButton.style = 'margin: 0 9.5px;';
  848. draftButton.value = 'Manage Drafts';
  849. draftButton.className = 'major';
  850. draftButton.type = 'button';
  851. spoilerButton.value = 'Manage Spoiler Values';
  852. spoilerButton.className = 'major';
  853. spoilerButton.type = 'button';
  854. buttonSpan.appendChild(draftButton);
  855. buttonSpan.appendChild(spoilerButton);
  856. settingsTd.appendChild(buttonSpan);
  857.  
  858. const draftMsg = window.MSPFA.parseBBCode('Here you can manage the drafts that you have saved for your adventure(s).\n');
  859. const listTable = document.createElement('table');
  860. listTable.style = 'max-height: 250px; overflow-y: scroll; border: 1px solid grey; padding: 2px; width: 100%; text-align: center; vertical-align: middle;';
  861. const listTbody = document.createElement('tbody');
  862. listTable.appendChild(listTbody);
  863.  
  864. if (Object.keys(drafts).length === 0) {
  865. draftButton.disabled = true;
  866. }
  867.  
  868. draftButton.addEventListener('click', () => {
  869. draftMsg.appendChild(listTable);
  870. listTbody.innerHTML = '';
  871. Object.keys(drafts).forEach(adv => {
  872. window.MSPFA.request(0, {
  873. do: "story",
  874. s: adv
  875. }, story => {
  876. if (typeof story !== "undefined") {
  877. const storyTr = listTbody.insertRow(listTable.rows);
  878. const titleLink = document.createElement('a');
  879. Object.assign(titleLink, { className: 'major', href: `/my/stories/pages/?s=${adv}&click=d`, textContent: story.n, target: '_blank' });
  880. storyTr.insertCell(0).appendChild(titleLink);
  881. const deleteButton = document.createElement('input');
  882. Object.assign(deleteButton, { className: 'major', type: 'button', value: 'Delete' });
  883. storyTr.insertCell(1).appendChild(deleteButton);
  884.  
  885. deleteButton.addEventListener('click', () => {
  886. setTimeout(() => {
  887. window.MSPFA.dialog('Delete adventure draft?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
  888. if (output === "Yes") {
  889. delete drafts[adv];
  890.  
  891. if (settings.drafts && settings.drafts[adv]) {
  892. delete settings.drafts[adv];
  893. saveData(settings);
  894. }
  895.  
  896. saveDrafts(drafts);
  897.  
  898. setTimeout(() => {
  899. draftButton.click();
  900. }, 1);
  901.  
  902. if (Object.keys(drafts).length === 0) {
  903. draftButton.disabled = true;
  904. }
  905. }
  906. });
  907. }, 1);
  908. });
  909. }
  910. });
  911. });
  912.  
  913. window.MSPFA.dialog('Manage Drafts', draftMsg, ["Delete All", "Close"], (output, form) => {
  914. if (output === "Delete All") {
  915. setTimeout(() => {
  916. window.MSPFA.dialog('Delete all Drafts?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
  917. if (output === "Yes") {
  918. drafts = {};
  919. saveDrafts(drafts);
  920.  
  921. if (typeof settings.drafts !== "undefined") {
  922. delete settings.drafts;
  923. saveData(settings);
  924. }
  925.  
  926. draftButton.disabled = true;
  927. }
  928. });
  929. }, 1);
  930. }
  931. });
  932. });
  933.  
  934. if (Object.keys(settings.spoilerValues).length === 0) {
  935. spoilerButton.disabled = true;
  936. }
  937.  
  938. const spoilerMsg = window.MSPFA.parseBBCode('Here you can manage the spoiler values that you have set for your adventure(s).\nClick on an adventure\'s title to see the values.\n');
  939.  
  940. spoilerButton.addEventListener('click', () => {
  941. spoilerMsg.appendChild(listTable);
  942. listTbody.innerHTML = '';
  943. Object.keys(settings.spoilerValues).forEach(adv => {
  944. window.MSPFA.request(0, {
  945. do: "story",
  946. s: adv
  947. }, story => {
  948. if (typeof story !== "undefined") {
  949. const storyTr = listTbody.insertRow(listTable.rows);
  950. const titleLink = document.createElement('a');
  951. Object.assign(titleLink, { className: 'major', href: `/my/stories/pages/?s=${adv}&click=s`, textContent: story.n, target: '_blank' });
  952. storyTr.insertCell(0).appendChild(titleLink);
  953. const deleteButton = document.createElement('input');
  954. Object.assign(deleteButton, { className: 'major', type: 'button', value: 'Delete' });
  955. storyTr.insertCell(1).appendChild(deleteButton);
  956.  
  957. deleteButton.addEventListener('click', () => {
  958. setTimeout(() => {
  959. window.MSPFA.dialog('Delete adventure spoilers?', document.createTextNode('Are you really sure?\nThis action cannot be undone!'), ["Yes", "No"], (output, form) => {
  960. if (output === "Yes") {
  961. delete settings.spoilerValues[adv];
  962. saveData(settings);
  963.  
  964. setTimeout(() => {
  965. spoilerButton.click();
  966. }, 1);
  967.  
  968. if (Object.keys(settings.spoilerValues).length === 0) {
  969. spoilerButton.disabled = true;
  970. }
  971. }
  972. });
  973. }, 1);
  974. });
  975. }
  976. });
  977. });
  978. window.MSPFA.dialog('Manage Spoiler Values', spoilerMsg, ["Delete All", "Close"], (output, form) => {
  979. if (output === "Delete All") {
  980. setTimeout(() => {
  981. window.MSPFA.dialog('Delete all Spoiler Values?', 'Are you sure you want to delete all spoiler values?\nThis action cannot be undone!', ["Yes", "No"], (output, form) => {
  982. if (output === "Yes") {
  983. settings.spoilerValues = {};
  984. saveData(settings);
  985. draftButton.disabled = true;
  986. }
  987. });
  988. }, 1);
  989. }
  990. });
  991. });
  992.  
  993. // Add event listeners
  994. plusTbody.querySelectorAll('input, select').forEach(elm => {
  995. elm.addEventListener("change", () => {
  996. saveBtn.disabled = false;
  997. });
  998. });
  999.  
  1000. saveBtn.addEventListener('mouseup', () => {
  1001. settings.autospoiler = spoilerInput.checked;
  1002. settings.style = cssSelect.selectedIndex;
  1003. settings.styleURL = customCssInput.value;
  1004. settings.auto502 = errorInput.checked;
  1005. settings.textFix = textFixInput.checked;
  1006. settings.pixelFix = pixelFixInput.checked;
  1007. settings.autoUpdate = updateInput.checked;
  1008. settings.commandScroll = commandScrollInput.checked;
  1009. settings.preload = preloadInput.checked;
  1010. settings.night = false;
  1011. console.log(settings);
  1012. saveData(settings);
  1013.  
  1014. updateTheme(settings.style == styleOptions.length - 1 ? settings.styleURL : styleUrls[settings.style]);
  1015.  
  1016. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  1017.  
  1018. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '') + ' *{transition:1s}';
  1019. setTimeout(() => {
  1020. dropStyle.textContent = dropStyleText + (settings.pixelFix ? ' '+pixelFixText : '');
  1021. }, 1000);
  1022. });
  1023. }
  1024. else if (location.pathname === "/my/messages/") { // New buttons
  1025. const btnStyle = "margin: 10px 5px;";
  1026.  
  1027. // Select all read messages button.
  1028. const selRead = document.createElement('input');
  1029. selRead.style = btnStyle;
  1030. selRead.value = "Select Read";
  1031. selRead.id = "selectread";
  1032. selRead.classList.add("major");
  1033. selRead.type = "button";
  1034.  
  1035. // On click, select all messages with the style attribute indicating it as read.
  1036. selRead.addEventListener('mouseup', () => {
  1037. document.querySelectorAll('td[style="border-left: 8px solid rgb(221, 221, 221);"] > input').forEach((m) => m.click());
  1038. });
  1039.  
  1040. // Select duplicate message (multiple update notifications).
  1041. const selDupe = document.createElement('input');
  1042. selDupe.style = btnStyle;
  1043. selDupe.value = "Select Same";
  1044. selDupe.id = "selectdupe";
  1045. selDupe.classList.add("major");
  1046. selDupe.type = "button";
  1047.  
  1048. selDupe.addEventListener('mouseup', evt => {
  1049. const temp = document.querySelectorAll('#messages > tr');
  1050. const msgs = [];
  1051. for (let i = temp.length - 1; i >= 0; i--) {
  1052. msgs.push(temp[i]);
  1053. }
  1054. const titles = [];
  1055. msgs.forEach((msg) => {
  1056. const title = msg.querySelector('a.major').textContent;
  1057. // Select only adventure updates
  1058. if (/^New update: /.test(title)) {
  1059. if (titles.indexOf(title) === -1) {
  1060. if (msg.querySelector('td').style.cssText !== "border-left: 8px solid rgb(221, 221, 221);") {
  1061. titles.push(title);
  1062. }
  1063. } else {
  1064. msg.querySelector('input').click();
  1065. }
  1066. }
  1067. });
  1068. });
  1069.  
  1070. // Prune button
  1071. const pruneButton = document.createElement('input');
  1072. Object.assign(pruneButton, { type: 'button', value: 'Prune', style: btnStyle, className: 'major' });
  1073.  
  1074. pruneButton.addEventListener('click', () => {
  1075. const ageInput = document.createElement('input');
  1076. Object.assign(ageInput, { type: 'number', min: 1, max: 10, value: 1 });
  1077.  
  1078. const msgState = document.createElement('select');
  1079. ['all', 'all unread', 'all read'].forEach(option => {
  1080. const op = document.createElement('option');
  1081. op.textContent = option;
  1082. msgState.appendChild(op);
  1083. });
  1084.  
  1085. const timeUnit = document.createElement('select');
  1086. ['month(s)', 'week(s)', 'day(s)'].forEach(option => {
  1087. const op = document.createElement('option');
  1088. op.textContent = option;
  1089. timeUnit.appendChild(op);
  1090. });
  1091.  
  1092. const msg = document.createElement('span');
  1093. msg.appendChild(document.createTextNode('Prune '));
  1094. msg.appendChild(msgState);
  1095. msg.appendChild(document.createTextNode(' messages older than '));
  1096. msg.appendChild(ageInput);
  1097. msg.appendChild(timeUnit);
  1098.  
  1099. window.MSPFA.dialog('Prune messages', msg, ['Prune', 'Cancel'], (output, form) => {
  1100. if (output === 'Prune') {
  1101. document.querySelector('#messages').childNodes.forEach(node => {
  1102. if (node.firstChild.firstChild.checked) {
  1103. node.firstChild.firstChild.click();
  1104. }
  1105.  
  1106. const selectedState = msgState.selectedOptions[0].textContent;
  1107. const selectedUnit = timeUnit.selectedOptions[0].textContent;
  1108.  
  1109. if (selectedState === 'all unread') {
  1110. if (node.firstChild.style.borderLeftColor === 'rgb(221, 221, 221)') {
  1111. return;
  1112. }
  1113. }
  1114. else if (selectedState === 'all read') {
  1115. if (node.firstChild.style.borderLeftColor === 'rgb(92, 174, 223)') {
  1116. return;
  1117. }
  1118. }
  1119. const dateText = node.childNodes[2].childNodes[2].textContent.split(' - ');
  1120. const messageDate = new Date(dateText[dateText.length-1]);
  1121. const currentDate = Date.now();
  1122. const diff = Math.round((currentDate-messageDate)/(1000*60*60*24)); // Difference in days
  1123. let unitDiff = diff - 1;
  1124.  
  1125. if (selectedUnit === 'month(s)') {
  1126. unitDiff = Math.floor(diff / 30);
  1127.  
  1128. if (unitDiff >= ageInput.value) {
  1129. console.log(node.querySelector('a.major').textContent);
  1130. node.firstChild.firstChild.click();
  1131. }
  1132. }
  1133. else if (selectedUnit === 'week(s)') {
  1134. unitDiff = Math.floor(diff / 7);
  1135.  
  1136. if (unitDiff >= ageInput.value) {
  1137. console.log(node.querySelector('a.major').textContent);
  1138. node.firstChild.firstChild.click();
  1139. }
  1140. }
  1141. else if (selectedUnit === 'day(s)') {
  1142. if (unitDiff >= ageInput.value) {
  1143. console.log(node.querySelector('a.major').textContent);
  1144. node.firstChild.firstChild.click();
  1145. }
  1146. }
  1147. });
  1148.  
  1149. setTimeout(() => {
  1150. document.querySelector('input[value=Delete]').click();
  1151. }, 1);
  1152. }
  1153. });
  1154. });
  1155.  
  1156. // Maybe add a "Merge Updates" button?
  1157. // [Merge Updates] would create a list of updates, similar to [Select Same]
  1158.  
  1159. // Add buttons to the page.
  1160. const del = document.querySelector('#deletemsgs');
  1161. del.parentNode.appendChild(newBr());
  1162. del.parentNode.appendChild(selRead);
  1163. del.parentNode.appendChild(selDupe);
  1164. del.parentNode.appendChild(pruneButton);
  1165.  
  1166. // Click the green cube to open the update in a new tab, and mark notification as read.
  1167. pageLoad(() => {
  1168. if (document.querySelector('#messages').childNodes.length > 0) {
  1169. if (document.querySelector('#messages').textContent === 'No new messages were found.') {
  1170. // Disable some buttons if there are no messages.
  1171. pruneButton.disabled = true;
  1172. selDupe.disabled = true;
  1173. return true;
  1174. } else {
  1175. document.querySelector('#messages').childNodes.forEach(node => {
  1176. if (node.textContent.includes('New update:') && node.textContent.includes('MS Paint Fan Adventures')) {
  1177. const link = addLink(node.querySelector('.cellicon'), node.querySelector('.spoiler a').href);
  1178. link.addEventListener('mouseup', () => {
  1179. const spoiler = node.querySelector('.spoiler');
  1180. const button = spoiler.querySelector('input');
  1181. spoiler.className = 'spoiler closed';
  1182. button.click();
  1183. button.click();
  1184. });
  1185. }
  1186. });
  1187. return true;
  1188. }
  1189. }
  1190. });
  1191. }
  1192. else if (location.pathname === "/my/messages/new/" && location.search) { // Auto-fill user when linked from a user page
  1193. const recipientInput = document.querySelector('#addrecipient');
  1194. recipientInput.value = params.u;
  1195. pageLoad(() => {
  1196. const recipientButton = document.querySelector('#addrecipientbtn');
  1197. if (recipientButton) {
  1198. recipientButton.click();
  1199. if (recipientInput.value === "") { // If the button press doesn't work
  1200. return true;
  1201. }
  1202. }
  1203. });
  1204. }
  1205. else if (location.pathname === "/my/stories/") {
  1206. // Add links to buttons
  1207. pageLoad(() => {
  1208. const adventures = document.querySelectorAll('#stories tr');
  1209. if (adventures.length > 0) {
  1210. adventures.forEach(story => {
  1211. const buttons = story.querySelectorAll('input.major');
  1212. const id = story.querySelector('a').href.replace('https://mspfa.com/', '').replace('&p=1', '');
  1213. if (id) {
  1214. addLink(buttons[0], `/my/stories/info/${id}`);
  1215. addLink(buttons[1], `/my/stories/pages/${id}`);
  1216. }
  1217. });
  1218. return true;
  1219. }
  1220. if (pageLoaded) return true;
  1221. });
  1222.  
  1223. // Add user guides
  1224. const guides = ["A Guide To Uploading Your Comic To MSPFA", "MSPFA Etiquette", "Fanventure Guide for Dummies", "CSS Guide", "HTML and CSS Things", ];
  1225. const links = ["https://docs.google.com/document/d/17QI6Cv_BMbr8l06RrRzysoRjASJ-ruWioEtVZfzvBzU/edit?usp=sharing", "/?s=27631", "/?s=29299", "/?s=21099", "/?s=23711"];
  1226. const authors = ["Farfrom Tile", "Radical Dude 42", "nzar", "MadCreativity", "seymour schlong"];
  1227.  
  1228. const parentTd = document.querySelector('.container > tbody > tr:last-child > td');
  1229. const unofficial = parentTd.querySelector('span');
  1230. unofficial.textContent = "Unofficial Guides";
  1231. const guideTable = document.createElement('table');
  1232. const guideTbody = document.createElement('tbody');
  1233. guideTable.style.width = "100%";
  1234. guideTable.style.textAlign = "center";
  1235.  
  1236. guideTable.appendChild(guideTbody);
  1237. parentTd.appendChild(guideTable);
  1238.  
  1239. for (let i = 0; i < guides.length; i++) {
  1240. const guideTr = guideTbody.insertRow(i);
  1241. const guideTd = guideTr.insertCell(0);
  1242. const guideLink = document.createElement('a');
  1243. guideLink.href = links[i];
  1244. guideLink.textContent = guides[i];
  1245. guideLink.className = "major";
  1246. guideTd.appendChild(guideLink);
  1247. guideTd.appendChild(newBr());
  1248. guideTd.appendChild(document.createTextNode('by '+authors[i]));
  1249. guideTd.appendChild(newBr());
  1250. guideTd.appendChild(newBr());
  1251. }
  1252. }
  1253. else if (location.pathname === "/my/stories/info/" && location.search) {
  1254. // Button links
  1255. addLink(document.querySelector('#userfavs'), `/readers/?s=${params.s}`);
  1256. addLink(document.querySelector('#editpages'), `/my/stories/pages/?s=${params.s}`);
  1257. }
  1258. else if (location.pathname === "/my/stories/pages/" && location.search) {
  1259. const adventureID = params.s;
  1260.  
  1261. if (!drafts[adventureID]) {
  1262. drafts[adventureID] = {}
  1263. }
  1264.  
  1265. // Button links
  1266. addLink(document.querySelector('#editinfo'), `/my/stories/info/?s=${adventureID}`);
  1267.  
  1268. // Default spoiler values
  1269. const replaceButton = document.querySelector('#replaceall');
  1270. const spoilerButton = document.createElement('input');
  1271. spoilerButton.classList.add('major');
  1272. spoilerButton.value = 'Default Spoiler Values';
  1273. spoilerButton.type = 'button';
  1274. spoilerButton.id = 'spoilers';
  1275. replaceButton.parentNode.insertBefore(spoilerButton, replaceButton);
  1276. replaceButton.parentNode.insertBefore(newBr(), replaceButton);
  1277. replaceButton.parentNode.insertBefore(newBr(), replaceButton);
  1278.  
  1279. const spoilerSpan = document.createElement('span');
  1280. const spoilerOpen = document.createElement('input');
  1281. const spoilerClose = document.createElement('input');
  1282. spoilerSpan.appendChild(document.createTextNode('Open button text:'));
  1283. spoilerSpan.appendChild(newBr());
  1284. spoilerSpan.appendChild(spoilerOpen);
  1285. spoilerSpan.appendChild(newBr());
  1286. spoilerSpan.appendChild(newBr());
  1287. spoilerSpan.appendChild(document.createTextNode('Close button text:'));
  1288. spoilerSpan.appendChild(newBr());
  1289. spoilerSpan.appendChild(spoilerClose);
  1290.  
  1291. if (!settings.spoilerValues[adventureID]) {
  1292. settings.spoilerValues[adventureID] = {
  1293. open: 'Show',
  1294. close: 'Hide'
  1295. }
  1296. }
  1297.  
  1298. spoilerOpen.value = settings.spoilerValues[adventureID].open;
  1299. spoilerClose.value = settings.spoilerValues[adventureID].close;
  1300.  
  1301. spoilerButton.addEventListener('click', evt => {
  1302. window.MSPFA.dialog('Default Spoiler Values', spoilerSpan, ['Save', 'Cancel'], (output, form) => {
  1303. if (output === 'Save') {
  1304. settings.spoilerValues[adventureID].open = spoilerOpen.value === '' ? 'Show' : spoilerOpen.value;
  1305. settings.spoilerValues[adventureID].close = spoilerClose.value === '' ? 'Hide' : spoilerClose.value;
  1306. if (settings.spoilerValues[adventureID].open === 'Show' && settings.spoilerValues[adventureID].close === 'Hide') {
  1307. delete settings.spoilerValues[adventureID];
  1308. }
  1309. saveData(settings);
  1310. }
  1311. });
  1312. });
  1313.  
  1314. document.querySelector('input[title="Spoiler"]').addEventListener('click', evt => {
  1315. document.querySelector('#dialog input[name="open"]').value = settings.spoilerValues[adventureID].open;
  1316. document.querySelector('#dialog input[name="close"]').value = settings.spoilerValues[adventureID].close;
  1317. document.querySelector('#dialog input[name="open"]').placeholder = settings.spoilerValues[adventureID].open;
  1318. document.querySelector('#dialog input[name="close"]').placeholder = settings.spoilerValues[adventureID].close;
  1319. });
  1320.  
  1321. // Buttonless spoilers
  1322. const flashButton = document.querySelector('input[title="Flash');
  1323. const newSpoilerButton = document.createElement('input');
  1324. newSpoilerButton.setAttribute('data-tag', 'Buttonless Spoiler');
  1325. newSpoilerButton.title = 'Buttonless Spoiler';
  1326. newSpoilerButton.type = 'button';
  1327. newSpoilerButton.style = 'background-position: -66px -88px; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAAAXNSR0IArs4c6QAAARNJREFUOI3V1LFqhEAQBuDfPaxcLkFwsbhgJ2STWlhQG4twvT7IWecZ8iB3L5In2BfIbaVNglaipMkQPAiBMAnkr2Zg52OZYoD/lg0AKKVMkiQmiqK7russ9cMwvPm+v03T9MHzvKtlWUD1OI5nrXVzOUO9AICyLA9cPzXGfFp1XR+54JWllDJcMFkCAIQQr1wwWQIAiqJouWCyBADM8/zCBa+sMAy3XDBZAgCqqnrkgsmiVVxzwSsrjuNbLpgsAQB5nt9zwWSJj77hgskSAOCcO3GpnNbX+Y0jJL57+NNsAEBKuTPGtFrrxlp7yrLsYIxp+74/B0Fws9/vn6SUu2maPKqdc891XR8vZwDAWvsHe+bOOy65ZXyuZ2jeAAAAAElFTkSuQmCC");';
  1328.  
  1329. newSpoilerButton.addEventListener('click', evt => {
  1330. const bbe = document.querySelector('#bbtoolbar').parentNode.querySelector('textarea');
  1331. if (bbe) {
  1332. bbe.focus();
  1333. const start = bbe.selectionStart;
  1334. const end = bbe.selectionEnd;
  1335. bbe.value = bbe.value.slice(0, start) + '<div class="spoiler"><div>' + bbe.value.slice(start, end) + '</div></div>' + bbe.value.slice(end);
  1336. bbe.selectionStart = start + 26;
  1337. bbe.selectionEnd = end + 26;
  1338. }
  1339. });
  1340.  
  1341. flashButton.parentNode.insertBefore(newSpoilerButton, flashButton);
  1342.  
  1343. // Open preview in new tab with middle mouse
  1344. document.body.addEventListener('mouseup', evt => {
  1345. if (evt.toElement.value === "Preview" && evt.button === 1) {
  1346. evt.toElement.click(); // TODO: Find a way to prevent the middle mouse scroll after clicking there.
  1347. evt.preventDefault();
  1348. return false;
  1349. }
  1350. });
  1351.  
  1352. // -- Drafts --
  1353. // Accessing draft text
  1354. const accessDraftsButton = document.createElement('input');
  1355. accessDraftsButton.classList.add('major');
  1356. accessDraftsButton.value = 'Saved Drafts';
  1357. accessDraftsButton.type = 'button';
  1358. accessDraftsButton.id = 'drafts';
  1359. replaceButton.parentNode.insertBefore(accessDraftsButton, replaceButton);
  1360. accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);
  1361. accessDraftsButton.parentNode.insertBefore(newBr(), replaceButton);
  1362.  
  1363. accessDraftsButton.addEventListener('click', () => {
  1364. const draftDialog = window.MSPFA.parseBBCode('Use the textbox below to copy out the data and save to a file somewhere else.\nYou can also paste in data to replace the current drafts to ones stored there.');
  1365. const draftInputTextarea = document.createElement('textarea');
  1366. draftInputTextarea.placeholder = 'Paste your draft data here';
  1367. draftInputTextarea.style = 'width: 100%; box-sizing: border-box; resize: vertical;';
  1368. draftInputTextarea.rows = 8;
  1369. draftDialog.appendChild(newBr());
  1370. draftDialog.appendChild(newBr());
  1371. draftDialog.appendChild(draftInputTextarea);
  1372. setTimeout(() => {
  1373. draftInputTextarea.focus();
  1374. draftInputTextarea.selectionStart = 0;
  1375. draftInputTextarea.selectionEnd = 0;
  1376. draftInputTextarea.scrollTop = 0;
  1377. }, 1);
  1378.  
  1379. draftInputTextarea.value = JSON.stringify(drafts[adventureID], null, 4);
  1380.  
  1381. window.MSPFA.dialog('Saved Drafts', draftDialog, ['Load Draft', 'Cancel'], (output, form) => {
  1382. if (output === "Load Draft") {
  1383. if (draftInputTextarea.value === '') {
  1384. setTimeout(() => {
  1385. window.MSPFA.dialog('Saved Drafts', window.MSPFA.parseBBCode('Are you sure you want to delete this adventure\'s draft data?\nMake sure you have it saved somewhere!'), ["Delete", "Cancel"], (output, form) => {
  1386. if (output === "Delete") {
  1387. drafts[adventureID] = {};
  1388.  
  1389. if (settings.drafts && settings.drafts[adventureID]) {
  1390. delete settings.drafts[adventureID];
  1391. saveData(settings);
  1392. }
  1393.  
  1394. saveDrafts(drafts);
  1395. }
  1396. });
  1397. }, 1);
  1398. } else if (draftInputTextarea.value !== JSON.stringify(drafts[adventureID], null, 4)) {
  1399. setTimeout(() => {
  1400. window.MSPFA.dialog('Saved Drafts', window.MSPFA.parseBBCode('Are you sure you want to load this draft data?\nAll previous draft data for this adventure will be lost!'), ["Load", "Cancel"], (output, form) => {
  1401. if (output === "Load") {
  1402. let newData = {};
  1403. try { // Just in case the data given is invalid.
  1404. newData = JSON.parse(draftInputTextarea.value);
  1405. } catch (err) {
  1406. console.error(err);
  1407. setTimeout(() => {
  1408. window.MSPFA.dialog('Error', window.MSPFA.parseBBCode('The entered data is invalid.'), ["Okay"]);
  1409. }, 1);
  1410. return;
  1411. }
  1412.  
  1413. drafts[adventureID] = newData;
  1414. saveDrafts(drafts);
  1415. }
  1416. });
  1417. }, 1);
  1418. }
  1419. }
  1420. });
  1421. });
  1422.  
  1423. // Draft stuff
  1424. const msg = document.createElement('span');
  1425. msg.appendChild(document.createTextNode('Command:'));
  1426. msg.appendChild(document.createElement('br'));
  1427.  
  1428. const commandInput = document.createElement('input');
  1429. commandInput.style = 'width: 100%; box-sizing: border-box;';
  1430. commandInput.readOnly = true;
  1431. commandInput.value = 'yes';
  1432.  
  1433. msg.appendChild(commandInput);
  1434. msg.appendChild(document.createElement('br'));
  1435. msg.appendChild(document.createElement('br'));
  1436.  
  1437. msg.appendChild(document.createTextNode('Body:'));
  1438.  
  1439. const bodyInput = document.createElement('textarea');
  1440. bodyInput.style = 'width: 100%; box-sizing: border-box; resize: vertical;';
  1441. bodyInput.readOnly = true;
  1442. bodyInput.rows = 8;
  1443. bodyInput.textContent = '';
  1444.  
  1445. msg.appendChild(bodyInput);
  1446.  
  1447. const showDraftDialog = (pageNum) => {
  1448. const pageElement = document.querySelector(`#p${pageNum}`);
  1449.  
  1450. let shownMessage = msg;
  1451. let optionButtons = [];
  1452.  
  1453. const commandElement = pageElement.querySelector('input[name="cmd"]');
  1454. const pageContentElement = pageElement.querySelector('textarea[name="body"]');
  1455.  
  1456. if (typeof drafts[adventureID][pageNum] === "undefined") {
  1457. shownMessage = document.createTextNode('There is no draft saved for this page.');
  1458. optionButtons = ["Save New", "Close"];
  1459. } else {
  1460. commandInput.value = drafts[adventureID][pageNum].command;
  1461. bodyInput.textContent = drafts[adventureID][pageNum].pageContent;
  1462. optionButtons = ["Save New", "Load", "Delete", "Close"];
  1463. }
  1464.  
  1465. window.MSPFA.dialog(`Page ${pageNum} Draft`, shownMessage, optionButtons, (output, form) => {
  1466. if (output === "Save New") {
  1467. if (typeof drafts[adventureID][pageNum] === "undefined") {
  1468. drafts[adventureID][pageNum] = {
  1469. command: commandElement.value,
  1470. pageContent: pageContentElement.value
  1471. }
  1472. saveDrafts(drafts);
  1473. } else {
  1474. setTimeout(() => {
  1475. window.MSPFA.dialog('Overwrite current draft?', document.createTextNode('Doing this will overwrite your current draft with what is currently written in the page box. Are you sure?'), ["Yes", "No"], (output, form) => {
  1476. if (output === "Yes") {
  1477. drafts[adventureID][pageNum] = {
  1478. command: commandElement.value,
  1479. pageContent: pageContentElement.value
  1480. }
  1481. saveDrafts(drafts);
  1482. }
  1483. });
  1484. }, 1);
  1485. }
  1486. } else if (output === "Load") {
  1487. if (pageContentElement.value === '' && (commandElement.value === '' || commandElement.value === document.querySelector('#defaultcmd').value)) {
  1488. commandElement.value = drafts[adventureID][pageNum].command;
  1489. pageContentElement.value = drafts[adventureID][pageNum].pageContent;
  1490. pageElement.querySelector('input[value="Save"]').disabled = false;
  1491. } else {
  1492. setTimeout(() => {
  1493. window.MSPFA.dialog('Overwrite current page?', document.createTextNode('Doing this will overwrite the page\'s content with what is currently written in the draft. Are you sure?'), ["Yes", "No"], (output, form) => {
  1494. if (output === "Yes") {
  1495. commandElement.value = drafts[adventureID][pageNum].command;
  1496. pageContentElement.value = drafts[adventureID][pageNum].pageContent;
  1497. pageElement.querySelector('input[value="Save"]').disabled = false;
  1498. }
  1499. });
  1500. }, 1);
  1501. }
  1502. } else if (output === "Delete") {
  1503. setTimeout(() => {
  1504. window.MSPFA.dialog('Delete this draft?', document.createTextNode('This action is unreversable! Are you sure?'), ["Yes", "No"], (output, form) => {
  1505. if (output === "Yes") {
  1506. delete drafts[adventureID][pageNum];
  1507.  
  1508. if (settings.drafts && settings.drafts[adventureID] && settings.drafts[adventureID][pageNum]) {
  1509. delete settings.drafts[adventureID][pageNum];
  1510. saveData(settings);
  1511. }
  1512.  
  1513. saveDrafts(drafts);
  1514. }
  1515. });
  1516. }, 1);
  1517. }
  1518. });
  1519. }
  1520.  
  1521. const createDraftButton = (form) => {
  1522. const draftButton = document.createElement('input');
  1523. draftButton.className = 'major draft';
  1524. draftButton.type = 'button';
  1525. draftButton.value = 'Draft';
  1526. draftButton.style = 'margin-right: 9.5px;';
  1527. draftButton.addEventListener('click', () => {
  1528. showDraftDialog(form.id.replace('p', ''));
  1529. });
  1530. return draftButton;
  1531. }
  1532.  
  1533. pageLoad(() => {
  1534. let allPages = document.querySelectorAll('#storypages form:not(#newpage)');
  1535. if (allPages.length !== 0) {
  1536. allPages.forEach(form => {
  1537. const prevButton = form.querySelector('input[name="preview"]');
  1538. prevButton.parentNode.insertBefore(createDraftButton(form), prevButton);
  1539. });
  1540. document.querySelector('input[value="Add"]').addEventListener('click', () => {
  1541. allPages = document.querySelectorAll('#storypages form:not(#newpage)');
  1542. const form = document.querySelector(`#p${allPages.length}`);
  1543. const prevButton = form.querySelector('input[name="preview"]');
  1544. prevButton.parentNode.insertBefore(createDraftButton(form), prevButton);
  1545. });
  1546. return true;
  1547. }
  1548. });
  1549.  
  1550. if (params.click) {
  1551. if (params.click === 's') {
  1552. spoilerButton.click();
  1553. } else if (params.click === 'd') {
  1554. accessDraftsButton.click();
  1555. }
  1556. }
  1557.  
  1558. /* // Removed because apparently MSPFA already does this fine!
  1559. if (hashSearch) {
  1560. pageLoad(() => {
  1561. const element = document.querySelector(hashSearch);
  1562. if (element) {
  1563. if (element.style.display === "none") {
  1564. element.style = '';
  1565. }
  1566. return true;
  1567. }
  1568. });
  1569. }/**/
  1570. }
  1571. else if (location.pathname === "/my/profile/") {
  1572. // Add profile CSS box
  1573. /*
  1574. pageLoad(() => {
  1575. if (window.MSPFA && window.MSPFA.me && window.MSPFA.me.i) {
  1576. // add 15 to char cap for <style></style>
  1577. // replace
  1578. const bio = document.querySelector('textarea[name="userdesc"]');
  1579. const styleRow = document.querySelector('#editprofile tbody').insertRow(7);
  1580. styleRow.style = 'text-align: left;';
  1581. const styleCell = styleRow.insertCell(0);
  1582. styleCell.colSpan = 2;
  1583. const styleTextarea = document.createElement('textarea');
  1584. styleTextarea.style = 'width: 100%; box-sizing: border-box; resize: vertical;';
  1585. styleTextarea.rows = 8;
  1586. styleCell.appendChild(document.createTextNode('Custom profile style:'));
  1587. styleCell.appendChild(newBr());
  1588. styleCell.appendChild(styleTextarea);
  1589.  
  1590. styleTextarea.value = /<style>(.*?)<\/style>/i.exec(bio.value)[1];
  1591. bio.value = bio.value.replace(/<style>(.*?)<\/style>/i, '');
  1592.  
  1593. return true;
  1594. }
  1595. },0.25);/**/
  1596. }
  1597. else if (location.pathname === "/user/") {
  1598. // Button links
  1599. pageLoad(() => {
  1600. const msgButton = document.querySelector('#sendmsg');
  1601. if (msgButton) {
  1602. addLink(msgButton, `/my/messages/new/?u=${params.u}`);
  1603. addLink(document.querySelector('#favstories'), `/favs/?u=${params.u}`);
  1604. return true;
  1605. }
  1606. });
  1607.  
  1608. // Add extra user stats
  1609. pageLoad(() => {
  1610. if (window.MSPFA) {
  1611. const stats = document.querySelector('#userinfo table');
  1612.  
  1613. const joinTr = stats.insertRow(1);
  1614. const joinTextTd = joinTr.insertCell(0);
  1615. joinTextTd.appendChild(document.createTextNode("Account created:"));
  1616. const joinDate = joinTr.insertCell(1);
  1617. const joinTime = document.createElement('b');
  1618. joinTime.textContent = "Loading...";
  1619. joinDate.appendChild(joinTime);
  1620.  
  1621. const advCountTr = stats.insertRow(2);
  1622. const advTextTd = advCountTr.insertCell(0);
  1623. advTextTd.appendChild(document.createTextNode("Adventures created:"));
  1624. const advCount = advCountTr.insertCell(1);
  1625. const advCountText = document.createElement('b');
  1626. advCountText.textContent = "Loading...";
  1627. advCount.appendChild(advCountText);
  1628.  
  1629. // Show adventure creation date
  1630. window.MSPFA.request(0, {
  1631. do: "user",
  1632. u: params.u
  1633. }, user => {
  1634. if (typeof user !== "undefined") {
  1635. joinTime.textContent = new Date(user.d).toString().split(' ').splice(1, 4).join(' ');
  1636. }
  1637.  
  1638. // Show created adventures
  1639. window.MSPFA.request(0, {
  1640. do: "editor",
  1641. u: params.u
  1642. }, s => {
  1643. if (typeof s !== "undefined") {
  1644. advCountText.textContent = s.length;
  1645. }
  1646.  
  1647. // Show favourites
  1648. if (document.querySelector('#favstories').style.display !== 'none') {
  1649. const favCountTr = stats.insertRow(3);
  1650. const favTextTd = favCountTr.insertCell(0);
  1651. favTextTd.appendChild(document.createTextNode("Adventures favorited:"));
  1652. const favCount = favCountTr.insertCell(1);
  1653. const favCountText = document.createElement('b');
  1654. favCountText.textContent = "Loading...";
  1655. window.MSPFA.request(0, {
  1656. do: "favs",
  1657. u: params.u
  1658. }, s => {
  1659. if (typeof s !== "undefined") {
  1660. favCountText.textContent = s.length;
  1661. }
  1662. });
  1663. favCount.appendChild(favCountText);
  1664. }
  1665. });
  1666. });
  1667.  
  1668. return true;
  1669. }
  1670. });
  1671. }
  1672. else if (location.pathname === "/favs/" && location.search) {
  1673. // Button links
  1674. pageLoad(() => {
  1675. const stories = document.querySelectorAll('#stories tr');
  1676. let favCount = 0;
  1677.  
  1678. if (stories.length > 0) {
  1679. stories.forEach(story => {
  1680. favCount++;
  1681. const id = story.querySelector('a').href.replace('https://mspfa.com/', '');
  1682. pageLoad(() => {
  1683. if (window.MSPFA.me.i) {
  1684. addLink(story.querySelector('.edit.major'), `/my/stories/info/${id}`);
  1685. return true;
  1686. }
  1687. if (pageLoaded) return true;
  1688. });
  1689. addLink(story.querySelector('.rss.major'), `/rss/${id}`);
  1690. });
  1691.  
  1692. // Fav count
  1693. const username = document.querySelector('#username');
  1694. username.parentNode.appendChild(newBr());
  1695. username.parentNode.appendChild(newBr());
  1696. username.parentNode.appendChild(document.createTextNode(`Favorited adventures: ${favCount}`));
  1697.  
  1698. return true;
  1699. }
  1700. if (pageLoaded) return true;
  1701. });
  1702. }
  1703. else if (location.pathname === "/search/" && location.search) {
  1704. // Character and word statistics
  1705. const statTable = document.createElement('table');
  1706. const statTbody = document.createElement('tbody');
  1707. const statTr = statTbody.insertRow(0);
  1708. const charCount = statTr.insertCell(0);
  1709. const wordCount = statTr.insertCell(0);
  1710. const statParentTr = document.querySelector('#pages').parentNode.parentNode.insertRow(2);
  1711. const statParentTd = statParentTr.insertCell(0);
  1712.  
  1713. const statHeaderTr = statTbody.insertRow(0);
  1714. const statHeader = document.createElement('th');
  1715. statHeader.colSpan = '2';
  1716.  
  1717. statHeaderTr.appendChild(statHeader);
  1718. statHeader.textContent = 'Statistics may not be entirely accurate.';
  1719.  
  1720. statTable.style.width = "100%";
  1721.  
  1722. charCount.textContent = "Character count: loading...";
  1723. wordCount.textContent = "Word count: loading...";
  1724.  
  1725. statTable.appendChild(statTbody);
  1726. statParentTd.appendChild(statTable);
  1727.  
  1728. pageLoad(() => {
  1729. if (document.querySelector('#pages br')) {
  1730. const bbc = window.MSPFA.BBC.slice();
  1731. bbc.splice(0, 3);
  1732.  
  1733. window.MSPFA.request(0, {
  1734. do: "story",
  1735. s: params.s
  1736. }, story => {
  1737. if (typeof story !== "undefined") {
  1738. const pageContent = [];
  1739. story.p.forEach(p => {
  1740. pageContent.push(p.c);
  1741. pageContent.push(p.b);
  1742. });
  1743.  
  1744. const storyText = pageContent.join(' ')
  1745. .replace(/\n/g, ' ')
  1746. .replace(bbc[0][0], '$1')
  1747. .replace(bbc[1][0], '$1')
  1748. .replace(bbc[2][0], '$1')
  1749. .replace(bbc[3][0], '$1')
  1750. .replace(bbc[4][0], '$2')
  1751. .replace(bbc[5][0], '$3')
  1752. .replace(bbc[6][0], '$3')
  1753. .replace(bbc[7][0], '$3')
  1754. .replace(bbc[8][0], '$3')
  1755. .replace(bbc[9][0], '$3')
  1756. .replace(bbc[10][0], '$2')
  1757. .replace(bbc[11][0], '$1')
  1758. .replace(bbc[12][0], '$3')
  1759. .replace(bbc[13][0], '$3')
  1760. .replace(bbc[14][0], '')
  1761. .replace(bbc[16][0], '$1')
  1762. .replace(bbc[17][0], '$2 $4 $5')
  1763. .replace(bbc[18][0], '$2 $4 $5')
  1764. .replace(bbc[19][0], '')
  1765. .replace(bbc[20][0], '')
  1766. .replace(/<(.*?)>/g, '');
  1767.  
  1768. wordCount.textContent = `Word count: ${storyText.split(/ +/g).length}`;
  1769. charCount.textContent = `Character count: ${storyText.replace(/ +/g, '').length}`;
  1770. }
  1771. });
  1772. return true;
  1773. }
  1774. });
  1775. }
  1776. else if (location.pathname === "/stories/" && location.search) {
  1777. const adventureList = document.querySelector('#doit');
  1778. const resultAmount = document.createElement('span');
  1779. adventureList.parentNode.appendChild(resultAmount);
  1780.  
  1781. pageLoad(() => {
  1782. if (window.MSPFA) {
  1783. window.MSPFA.request(0, {
  1784. do: "stories",
  1785. n: params.n,
  1786. t: params.t,
  1787. h: params.h,
  1788. o: params.o,
  1789. p: params.p,
  1790. m: 20000
  1791. }, (s) => {
  1792. resultAmount.textContent = `Number of results: ${s.length}`;
  1793. return true;
  1794. });
  1795. return true;
  1796. }
  1797. },1);
  1798.  
  1799. pageLoad(() => {
  1800. const stories = document.querySelector('#stories');
  1801. if (stories.childNodes.length > 0) {
  1802. if (params.load && stories.childNodes.length === 1) {
  1803. stories.querySelector('a').click();
  1804. }
  1805.  
  1806. stories.querySelectorAll('tr').forEach(story => {
  1807. const storyID = story.querySelector('a.major').href.split('&')[0].replace(/\D/g, '');
  1808. addLink(story.querySelector('.rss'), `/rss/?s=${storyID}`);
  1809.  
  1810. pageLoad(() => {
  1811. if (window.MSPFA.me.i) {
  1812. addLink(story.querySelector('.edit.major'), `/my/stories/info/?s=${storyID}`);
  1813. return true;
  1814. }
  1815. if (pageLoaded) return true;
  1816. });
  1817. });
  1818. return true;
  1819. }
  1820. if (pageLoaded) return true;
  1821. });
  1822. }
  1823. })();