Scribble Hub: Display Series Word Count on Series Page

Displays the word count of the current series next to the chapter count

  1. // ==UserScript==
  2. // @name Scribble Hub: Display Series Word Count on Series Page
  3. // @namespace https://github.com/w4tchdoge
  4. // @version 1.0.0-20240829_004816
  5. // @description Displays the word count of the current series next to the chapter count
  6. // @author w4tchdoge
  7. // @homepage https://github.com/w4tchdoge/MISC-UserScripts
  8. // @match *://www.scribblehub.com/series/*
  9. // @run-at document-start
  10. // @license AGPL-3.0-or-later
  11. // @history 1.0.0 — Initial commit
  12. // ==/UserScript==
  13.  
  14. (async function () {
  15. `use strict`;
  16.  
  17. // from https://stackoverflow.com/a/61511955/11750206
  18. function waitForElm(selector, search_root = document) {
  19. return new Promise(resolve => {
  20. if (search_root.querySelector(selector)) {
  21. return resolve(search_root.querySelector(selector));
  22. }
  23.  
  24. const observer = new MutationObserver(mutations => {
  25. if (search_root.querySelector(selector)) {
  26. observer.disconnect();
  27. resolve(search_root.querySelector(selector));
  28. }
  29. });
  30.  
  31. // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
  32. observer.observe(document.documentElement, {
  33. childList: true,
  34. subtree: true
  35. });
  36. });
  37. }
  38.  
  39. const current_page = new URL(window.location);
  40. const wordcount_xpath = `.//div[contains(concat(" ",normalize-space(@class)," ")," wi_novel_details ")]//table/tbody/tr/th[contains(translate(normalize-space(), 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'),translate("Word Count", 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'))]/following-sibling::*[1]/self::td`;
  41.  
  42. const word_count = await (async () => {
  43. if (current_page.pathname.includes(`/stats`) == false) {
  44.  
  45. // const statsPG_fetch_url = new URL((`${current_page.href.replace(/\/$/i, ``)}/stats`));
  46. const statsPG_fetch_url = `./stats`;
  47. const statsPG_resp_txt = await (async () => {
  48. const fetch_resp = await fetch(statsPG_fetch_url);
  49. const resp_text = await fetch_resp.text();
  50. return resp_text;
  51. })();
  52.  
  53. const html_parser = new DOMParser();
  54. const statsPG_html = html_parser.parseFromString(statsPG_resp_txt, `text/html`);
  55. const wordcount_str = document.evaluate(wordcount_xpath, statsPG_html, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent.trim();
  56. return wordcount_str;
  57.  
  58. }
  59. else {
  60.  
  61. const stats_table_elmParent = await waitForElm(`.fic_row:has(.wi_novel_details)`);
  62.  
  63. const wordcount_str = document.evaluate(wordcount_xpath, stats_table_elmParent, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent.trim();
  64. return wordcount_str;
  65.  
  66. }
  67. })();
  68.  
  69. const chap_elm = document.querySelector(`div.fic_stats > span.st_item:has(> i.fa-list-alt)`);
  70. const word_count_elm = (() => {
  71.  
  72. // const svg_elm = Object.assign(document.createElementNS(`http://www.w3.org/2000/svg`, `svg`), {
  73. // innerHTML: `<path fill="currentColor" d="M64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-384c0-35.3-28.7-64-64-64L64 0zM96 64l192 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L96 160c-17.7 0-32-14.3-32-32l0-32c0-17.7 14.3-32 32-32zm32 160a32 32 0 1 1 -64 0 32 32 0 1 1 64 0zM96 352a32 32 0 1 1 0-64 32 32 0 1 1 0 64zM64 416c0-17.7 14.3-32 32-32l96 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-96 0c-17.7 0-32-14.3-32-32zM192 256a32 32 0 1 1 0-64 32 32 0 1 1 0 64zm32 64a32 32 0 1 1 -64 0 32 32 0 1 1 64 0zm64-64a32 32 0 1 1 0-64 32 32 0 1 1 0 64zm32 64a32 32 0 1 1 -64 0 32 32 0 1 1 64 0zM288 448a32 32 0 1 1 0-64 32 32 0 1 1 0 64z"></path>`
  74. // });
  75. // svg_elm.setAttribute(`viewBox`, `0 0 384 512`);
  76. // svg_elm.setAttribute(`style`, `width: 1em; height: 1em; vertical-align: -0.125em;`);
  77. // const svg_elm = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" style="width: 1em; height: 1em; vertical-align: -0.125em;"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="currentColor" d="M64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-384c0-35.3-28.7-64-64-64L64 0zM96 64l192 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L96 160c-17.7 0-32-14.3-32-32l0-32c0-17.7 14.3-32 32-32zm32 160a32 32 0 1 1 -64 0 32 32 0 1 1 64 0zM96 352a32 32 0 1 1 0-64 32 32 0 1 1 0 64zM64 416c0-17.7 14.3-32 32-32l96 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-96 0c-17.7 0-32-14.3-32-32zM192 256a32 32 0 1 1 0-64 32 32 0 1 1 0 64zm32 64a32 32 0 1 1 -64 0 32 32 0 1 1 64 0zm64-64a32 32 0 1 1 0-64 32 32 0 1 1 0 64zm32 64a32 32 0 1 1 -64 0 32 32 0 1 1 64 0zM288 448a32 32 0 1 1 0-64 32 32 0 1 1 0 64z"></path></svg>`;
  78. const svg_elm = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style="width: 1em; height: 1em; vertical-align: -0.125em;"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="currentColor" d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/></svg>`;
  79.  
  80. const wc_element = chap_elm.cloneNode(true);
  81. // wc_element.replaceChild(calc_svg, wc_element.querySelector(`*:has(~ span.mb_stat)`));
  82. wc_element.innerHTML = `${svg_elm} ${word_count} <span class="mb_stat">Words</span>`;
  83.  
  84. return wc_element;
  85.  
  86. })();
  87.  
  88. chap_elm.after(word_count_elm);
  89.  
  90. })();