Tapas.io RSS Button

Adds RSS buttons to webcomics on tapas.io.

目前為 2020-11-12 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Tapas.io RSS Button
  3. // @description Adds RSS buttons to webcomics on tapas.io.
  4. // @namespace https://avhn.us/
  5. // @version 2.01
  6. // @license CC0-1.0
  7. // Copyright: the embedded RSS icon and the script's icon are both derived from assets from tapas.io
  8. // I believe that their use in this script is okay, since the RSS icon is generic and universal,
  9. // and both the tapas logo and their RSS sprite have been transformed from the original.
  10. // @supportURL https://forums.tapas.io/t/rss-feeds-gone/41724
  11. // @icon https://avhn.us/lib/exe/fetch.php?media=blog:tapas_rss:tapas-rss.gif
  12. // @compatible firefox Only browser tested by author
  13. // @match https://tapas.io/series/*
  14. // @match https://tapas.io/episode/*
  15. // @grant none
  16. // ==/UserScript==
  17.  
  18. const rssIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfkBg8CGwrcReAoAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAPxJREFUKM99kD9LwgEURc/PQJAQIwkqopaawm/Q3lI0Orrkx2hxaXBrdQmsJmlJ6CsU9IegKQoiSgIriginOg2+zCy643uHd+99ic8M80iLa/bZS+4ZlP36sGnhP0D13ZqZ733iIi+MMskCS+RjesJKcvvbbMiSN3Hnzin+khnrgRyHkWtuWLForgdVAqn9DNmxajaQesQtDLY4dzqMulmagIdue2Cnh2QBLMVfxr98x2wEUo1GD6qW+xs0IksOwE1VdzFxznRc6RoVAVxV9TTFFhccmYakzRkA8wC0AJhIsQwUmAHgCoARAJ4AyKdY55UdLgGYBaANQPdxb589AOJXV9GZEgAAAABJRU5ErkJggg==";
  19.  
  20. // tapas.io webcomic series all have a unique ID: a few-digit (4-ish?) base-10 number.
  21. // The ID is needed to construct the RSS URL.
  22. // Fortunately, there are a couple of places to find the ID.
  23. // The following functions return the ID, or -1 if they fail to find it.
  24.  
  25. function findIDfromMetas(){
  26. // tapas.io sends a great deal of meta tags in their pages.
  27. // Many of these tags are to allow different mobile apps to get to the same page.
  28. // These tags have the format "tapastic://series/id[/episode/N]".
  29. const tapastic_regex = new RegExp("tapastic:\/\/series\/[0-9]+");
  30. const id_regex = new RegExp("[0-9]+");
  31. const metas = document.getElementsByTagName('meta');
  32. console.log(metas);
  33. for (let i=0; i<metas.length; i++){
  34. let meta = metas[i];
  35. let content = meta.getAttribute("content");
  36. if (tapastic_regex.test(content)) {
  37. console.log("Found a series ID in", meta);
  38. //let id=content.match("[0-9]+")[0];
  39. let id = id_regex.exec(content)[0];
  40. return id;
  41. }
  42. }
  43. console.log("Couldn't find a meta tag with the series ID!");
  44. return -1;
  45. }
  46.  
  47. function findIDfromSubscribers(){
  48. // Get the js-subscribe-cnt classed elements, they have an href with the series ID.
  49. // In a browser, you can see the URL (and ID) by hovering over the subscriber count in the info page.
  50. let jscs = document.getElementsByClassName("js-subscribe-cnt");
  51. if (jscs.length < 1) {console.log("No place to get series id!"); return -1;}
  52. jsc = jscs[0];
  53. console.log(jsc.href);
  54. if (jscs.length > 1) {console.log("More than one \"js-subscribe-cnt\"s found. Using the first one and ignoring the rest.");}
  55. let numbers = jsc.href.match("[0-9]+"); // regex: a series of one or more digits
  56. if (numbers.length < 1) {console.log("No series ID found! Searched this string for the ID, a base-10 number:", jsc.href); return -1;}
  57. console.log("Found a series ID in", jsc);
  58. let id = numbers[0];
  59. if (numbers.length > 1) {console.log("More than one base-10 number found where ID should be! Using the first one and ignoring the rest.");}
  60. return id;
  61. }
  62.  
  63. function findID(){
  64. let id = findIDfromMetas();
  65. if (id == -1){
  66. id = findIDfromSubscribers();
  67. if (id == -1){
  68. console.log("Tried everything but couldn't find series ID! Script is broken!");
  69. console.log("Please file a bug at https://forums.tapas.io/t/rss-feeds-gone/41724 and ping the author!");
  70. }
  71. }
  72. return id;
  73. }
  74.  
  75. function rssURL(id){
  76. return "https://tapas.io/rss/series/" + id;
  77. }
  78.  
  79. function addRSSButtons(id){
  80. if (id < 0){return;}
  81. // There are 4 places to add an RSS button:
  82. // In the Button Wrapper in series-root__footer (visible on the mobile website)
  83. let srfs = document.getElementsByClassName("series-root__footer");
  84. console.log(srfs);
  85. if (srfs.length > 0){
  86. console.log("There is a series root footer. Looking for its button-wrapper.");
  87. let srf = srfs[0];
  88. let bws = srf.getElementsByClassName("button-wrapper");
  89. console.log(bws);
  90. if (bws.length > 0){
  91. console.log("There is a button-wrapper in the series root footer.");
  92. let bw = bws[0];
  93. let rbs = bw.getElementsByClassName("button-rss");
  94. if (rbs.length > 0) {
  95. console.log("This button-wrapper already has an RSS button. Not adding another one.");
  96. } else {
  97. console.log("Adding an RSS button to the series root footer's button-wrapper.");
  98. let img = document.createElement("img");
  99. img.style = "filter: invert(100%)";
  100. img.alt = "RSS Icon";
  101. img.src = rssIcon;
  102. let btn = document.createElement("a");
  103. btn.className = "button button--subscribe button-rss";
  104. btn.href = rssURL(id);
  105. btn.appendChild(img);
  106. bw.appendChild(btn);
  107. console.log("Button added to", bw);
  108. }
  109. }
  110. }
  111. // In the Button Wrapper in section__top
  112. let sts = document.getElementsByClassName("section__top");
  113. console.log(sts);
  114. if (sts.length > 0){
  115. // There are 2 section__top elements. One of them is section__top--simple. Don't put a button on it.
  116. // The other is just section__top. It (and not section__top--simple) has a button-wrapper. Do put a button in there.
  117. let st;
  118. for(let i=0; i<sts.length; i++){
  119. let st = sts[i];
  120. let bws = st.getElementsByClassName("button-wrapper");
  121. if (bws.length > 0){
  122. console.log(st, "has a button-wrapper.");
  123. let bw = bws[0];
  124. let rbs = bw.getElementsByClassName("button-rss");
  125. if (rbs.length > 0){
  126. console.log("This button-wrapper already has an RSS button. Not adding another one.");
  127. } else {
  128. console.log("Adding an RSS button to this section__top's button-wrapper.");
  129. let img = document.createElement("img");
  130. img.alt = "RSS Icon";
  131. img.src = rssIcon;
  132. let btn = document.createElement("a");
  133. btn.className = "button button--read button-rss";
  134. // This particular button needs to be re-styled.
  135. btn.style = "min-width: 16px; padding: 12px 12px; margin-left: 6px;"
  136. btn.href = rssURL(id);
  137. btn.appendChild(img);
  138. bw.appendChild(btn);
  139. console.log("Button added to", bw);
  140. }
  141. }
  142. }
  143. }
  144. // In the center of the Toolbar at the bottom of the comic browser
  145. let trcs = document.getElementsByClassName("toolbar__row--center");
  146. if (trcs.length > 0){
  147. console.log(trcs);
  148. let trc = trcs[0];
  149. let ris = trc.getElementsByClassName("row-item");
  150. console.log(ris);
  151. if (ris.length > 0){
  152. let ri = ris[0];
  153. let rbs = ri.getElementsByClassName("button-rss");
  154. if (rbs.length > 0){
  155. console.log("This row-item alreads has an RSS button. Not adding another one.");
  156. } else {
  157. console.log("Adding an RSS button to this toolbar__row--center's row-item.");
  158. let a = document.createElement("a");
  159. a.className = "toolbar-btn button-rss";
  160. a.href = rssURL(id);
  161. let div = document.createElement("div");
  162. div.className = "ico-wrap";
  163. let img = document.createElement("img");
  164. img.class = "ico";
  165. img.style = "margin: auto;";
  166. img.alt = "RSS Icon";
  167. img.src = rssIcon;
  168. div.appendChild(img);
  169. let span = document.createElement("span");
  170. let text = document.createTextNode("RSS");
  171. span.appendChild(text);
  172. a.appendChild(div);
  173. a.appendChild(span);
  174. ri.appendChild(a);
  175. console.log("Button added to", ri);
  176. }
  177. }
  178. }
  179. // In the Dropdown List in the tpFloatMenu (on the mobile website)
  180. let tfm = document.getElementById("tpFloatMenu");
  181. if (tfm != null){
  182. console.log("Found a tpFloatMenu:", tfm);
  183. if (tfm.text.indexOf("button\-rss") > -1){
  184. console.log("The tpFloatMenu already contains an RSS button. Not adding another one.");
  185. } else {
  186. // tpFloatMenu is a script tag with a text node.
  187. // The text node contains the HTML that goes into the menu:
  188. // a <div> containing a <ul>, which contains a few <li>s.
  189. // We want to insert a new <li> after the first <li>.
  190. console.log(tfm.text);
  191. let lis = tfm.text.split("\<\/li\>");
  192. lis.splice(1,0,"\n <li class=\"dropdown-list__item\"> <a href=\"https://tapas.io/rss/series/" + id + "\" class=\"dropdown-list__button button-rss\"\> <span class=\"ico-wrapper\"><i class=\"ico\"> <img src=\"" + rssIcon + "\"> </i></span>RSS Feed</a>")
  193. tfm.text = lis.join("</li>");
  194. console.log(tfm.text);
  195. }
  196. }
  197. }
  198.  
  199. function addLink(id){
  200. let ls = document.head.getElementsByTagName("link");
  201. for (let i=0; i<ls.length; i++){
  202. let l = ls[i];
  203. if (l.type == "application/rss+xml"){
  204. if (l.href == rssURL(id)){
  205. console.log("This page's head already has a link tag for the RSS feed. Not adding another one.");
  206. return;
  207. }
  208. }
  209. }
  210. let l = document.createElement("link");
  211. l.rel = "alternate";
  212. l.type = "application/rss+xml";
  213. l.title = "RSS Feed";
  214. l.href = rssURL(id);
  215. document.head.appendChild(l);
  216. console.log("Added link tag to head:", l);
  217. }
  218.  
  219. function addButtons(){
  220. let id = findID();
  221. if (id < 0) {return;}
  222. addRSSButtons(id);
  223. addLink(id);
  224. }
  225.  
  226. // Tapas has a website feature where,
  227. // if while reading a comic you click on the "more..." in the description in the top-right,
  228. // the comic's info page loads and is overlaid over the episode page.
  229. // This changes the window URL and loads a bunch of new content,
  230. // which needs an RSS button added.
  231. // But since the overlaid info page is loaded and rendered after the page load,
  232. // the script will not have added an RSS button to it.
  233. // But because the window URL has changed,
  234. // if you click the "more..." link and then refresh the page,
  235. // the RSS button will be added to the info page!
  236. // The script also works fine if you navigate directly to the info page.
  237. // To get around this post-page-load content-and-URL-change,
  238. // the script detects clicks on the "more..." link,
  239. // waits 2 seconds to give time for the overlaid info page to load,
  240. // and then adds the RSS button.
  241. function delayAddButtons(){window.setTimeout(addButtons, 2000);}
  242. let mlbs = document.getElementsByClassName("more-less-btn");
  243. console.log(mlbs.length);
  244. if (mlbs.length > 0) {
  245. mlb = mlbs[0];
  246. console.log("Adding callback to more-less-button:", mlb);
  247. mlb.addEventListener("click", delayAddButtons);
  248. }
  249.  
  250. // Do the thing!
  251. addButtons();
  252.