Fanfiction.net: Not-Crossover Link

Add link to not-Crossover page to Fanfiction.net.

  1. // ==UserScript==
  2. // @name Fanfiction.net: Not-Crossover Link
  3. // @namespace https://greasyfork.org/en/users/163551-vannius
  4. // @version 1.8
  5. // @license MIT
  6. // @description Add link to not-Crossover page to Fanfiction.net.
  7. // @author Vannius
  8. // @match https://www.fanfiction.net/crossovers/*
  9. // @match https://www.fanfiction.net/*Crossovers/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=fanfiction.net
  11. // @grant GM_openInTab
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. // Config
  18. const OPEN_IN_NEW_TAB = true;
  19.  
  20. // Functions
  21. // Replace and delete prohibited characters.
  22. function deleteProhibitedCharacter (text) {
  23. return text.replace(/('\. |\/| & | - | + )+/g, ' ').replace(/[.,+!:;|☆★!?]+/g, '');
  24. }
  25.  
  26. // Return imgTag with click event
  27. function addClickToNotCrossover (imgTag, url) {
  28. imgTag.addEventListener('click', function (e) {
  29. e.preventDefault();
  30. // eslint-disable-next-line no-undef
  31. fetch(url, { method: 'GET', mode: 'same-origin', cache: 'default' })
  32. .then(response => response.text())
  33. .then(body => {
  34. const doc = document.implementation.createHTMLDocument('myBody');
  35. doc.documentElement.innerHTML = body;
  36. const content = doc.getElementById('content_wrapper_inner');
  37.  
  38. const list = content.getElementsByClassName('z-list');
  39.  
  40. let destination = '';
  41. if (list.length) destination = url; // Bingo.
  42.  
  43. const candidates = content.querySelectorAll('.gui_normal a');
  44. if (candidates.length === 1) {
  45. destination = candidates[0].href; // Only candidate.
  46. } else if (candidates.length > 1) {
  47. destination = url; // Suggestion page. There can be several candidates for same fandom.
  48. }
  49.  
  50. // Move to Not-Crossover page.
  51. if (OPEN_IN_NEW_TAB) {
  52. // eslint-disable-next-line no-undef
  53. GM_openInTab(window.location.origin + destination);
  54. } else {
  55. window.location.href = destination;
  56. }
  57. }).catch(error => console.log(error));
  58. });
  59. return imgTag;
  60. }
  61.  
  62. // Main
  63. const splitPath = window.location.href.split('/');
  64.  
  65. if (/^.+[-_]Crossovers$/.test(splitPath[3]) && splitPath[5] !== '0') {
  66. // Crossover page of two specific fandoms
  67. // Scrape each fandom link
  68. const divTag = document.getElementById('content_wrapper_inner');
  69. const titleTags = [divTag.children[1], divTag.children[2]];
  70.  
  71. for (let titleTag of titleTags) {
  72. // Make joinTitle by replacing and deleting prohibited symbols
  73. const joinedTitle = deleteProhibitedCharacter(titleTag.textContent).split(' ').join('-');
  74. const url = [window.location.origin, 'anime', joinedTitle, ''].join('/');
  75.  
  76. // Make link to Not-Crossover
  77. const imgTag = document.querySelector('#content_wrapper_inner > img');
  78. const addImgTag = addClickToNotCrossover(imgTag.cloneNode(false), url);
  79. addImgTag.title = "Not-Crossover";
  80. addImgTag.style.transform = "scale(-1, 1)";
  81.  
  82. // Add link and place adjustment
  83. const fragment = document.createDocumentFragment();
  84. fragment.appendChild(addImgTag);
  85. fragment.appendChild(document.createTextNode(' '));
  86. imgTag.parentNode.insertBefore(fragment, titleTag);
  87. }
  88. } else {
  89. // Crossover page of one specific fandom
  90. // Make title and url by splitPath[4] or splitPath[3]
  91. const title = (splitPath[3] === 'crossovers') ? splitPath[4] : splitPath[3].slice(0, -"-Crossovers".length);
  92. const url = [window.location.origin, 'anime', title, ''].join('/');
  93.  
  94. // Make link to Not-Crossover
  95. const imgTag = document.querySelector('#content_wrapper_inner > img');
  96. const addImgTag = addClickToNotCrossover(imgTag.cloneNode(false), url);
  97. addImgTag.title = "Not-Crossover";
  98. addImgTag.style.transform = "scale(-1, 1)";
  99.  
  100. // Add link and place adjustment
  101. const fragment = document.createDocumentFragment();
  102. fragment.appendChild(document.createTextNode(' '));
  103. fragment.appendChild(addImgTag);
  104. imgTag.parentNode.insertBefore(fragment, imgTag.nextSibling);
  105.  
  106. // Crossover page of one specific fandom and all fandoms
  107. if (splitPath[5] === '0') {
  108. splitPath.pop();
  109. splitPath[6] = '';
  110. splitPath[5] = splitPath[4];
  111. splitPath[4] = splitPath[3].slice(0, -"-Crossovers".length);
  112. splitPath[3] = 'crossovers';
  113. const selectFandomLink = splitPath.join('/');
  114.  
  115. const textNodeTag = document.querySelector('#content_wrapper_inner > img + img').nextSibling;
  116. const splitText = textNodeTag.textContent.split(/(\n\s+)/g);
  117. const splitFandomText = splitText.filter(x => /\w/.test(x) && x !== 'Crossovers')[0];
  118. const selectFandomLinkTag = document.createElement('a');
  119. selectFandomLinkTag.href = selectFandomLink;
  120. selectFandomLinkTag.textContent = splitFandomText;
  121.  
  122. const fragment = document.createDocumentFragment();
  123. splitText.filter(x => x).forEach(x => {
  124. if (/\w/.test(x) && x !== 'Crossovers') {
  125. fragment.appendChild(selectFandomLinkTag);
  126. } else {
  127. fragment.appendChild(document.createTextNode(x));
  128. }
  129. });
  130. textNodeTag.parentNode.replaceChild(fragment, textNodeTag);
  131. }
  132. }
  133. })();