Scribble Hub: Display Current Chapter Word Count

Adds the word count of the current chapter underneath the chapter statistics

// ==UserScript==
// @name           Scribble Hub: Display Current Chapter Word Count
// @namespace      https://github.com/w4tchdoge
// @version        1.1.2-20240513_200521
// @description    Adds the word count of the current chapter underneath the chapter statistics
// @author         w4tchdoge
// @homepage       https://github.com/w4tchdoge/MISC-UserScripts
// @match          *://*.scribblehub.com/read/*/chapter/*
// @license        AGPL-3.0-or-later
// @history        1.1.2 — Fix script not working on chapters without an author note
// @history        1.1.1 — Switch to using Intl.NumberFormat for making the word count thousands separated
// @history        1.1.0 — Change position of word count element and change wording of word count
// @history        1.0.0 — Initial release
// ==/UserScript==

(function () {
	`use strict`;

	// Attempted conversion of the Ruby regex code AO3 uses to JavaScript by looking at:
	// https://github.com/otwcode/otwarchive/blob/943f585818005be8df269d84ca454af478150e75/config/config.yml#L453
	// https://github.com/otwcode/otwarchive/blob/943f585818005be8df269d84ca454af478150e75/lib/word_counter.rb#L26
	// https://github.com/otwcode/otwarchive/blob/943f585818005be8df269d84ca454af478150e75/lib/word_counter.rb#L30C9-L31C95
	// Has not been tested on non-English works, feedback would be appreciated
	const word_count_regex = /\p{Script=Han}|\p{Script=Hiragana}|\p{Script=Katakana}|\p{Script=Thai}|((?!\p{Script=Han}|\p{Script=Hiragana}|\p{Script=Katakana}|\p{Script=Thai})\w)+/gu;

	// Get element that contains the chapter stats
	const chp_stats = document.querySelector(`#main\\ read\\ chapter .chapter_stats`);

	// Def vars used in word counting
	// var chp_raw_node, auth_note_node, chp_raw_text, auth_note_text, chp_raw_word_count, auth_note_word_count, word_count;
	var chp_raw_node, auth_note_node, chp_raw_text, auth_note_text, chp_raw_word_count, auth_note_word_count, word_count;

	// Get 1st elem to be word counted
	chp_raw_node = document.querySelector(`#chp_raw`).cloneNode(true);

	// Get the text context of the elements to be counted
	// chp_raw_text = chp_raw_node.textContent;
	// auth_note_text = auth_note_node.textContent;

	// Count the number of words
	// Counting method from:
	// https://stackoverflow.com/a/76673564/11750206
	// Regex substitutions from:
	// https://github.com/otwcode/otwarchive/blob/943f585818005be8df269d84ca454af478150e75/lib/word_counter.rb#L30C33-L30C68
	// chp_raw_word_count = [...chp_raw_text.replaceAll(/--/g, `—`).replaceAll(/['’‘-]/g, ``).matchAll(word_count_regex)].length;
	// auth_note_word_count = [...auth_note_text.replaceAll(/--/g, `—`).replaceAll(/['’‘-]/g, ``).matchAll(word_count_regex)].length;

	// if-else for the case that the author note does not exist
	if (Boolean(document.querySelector(`#chp_raw div .wi_authornotes_body`))) { // if author notes exists

		// Remove auth note from chp_raw_node
		chp_raw_node.removeChild(chp_raw_node.querySelector(`div.wi_authornotes`));

		// Get chp text to be counted
		chp_raw_text = chp_raw_node.textContent;

		// Get 2nd elem to be word counted
		auth_note_node = document.querySelector(`#chp_raw div .wi_authornotes_body`).cloneNode(true);

		// Get auth note text to be counted
		auth_note_text = auth_note_node.textContent;

		// Count the number of words
		// Counting method from:
		// https://stackoverflow.com/a/76673564/11750206
		// Regex substitutions from:
		// https://github.com/otwcode/otwarchive/blob/943f585818005be8df269d84ca454af478150e75/lib/word_counter.rb#L30C33-L30C68
		chp_raw_word_count = [...chp_raw_text.replaceAll(/--/g, `—`).replaceAll(/['’‘-]/g, ``).matchAll(word_count_regex)].length;
		auth_note_word_count = [...auth_note_text.replaceAll(/--/g, `—`).replaceAll(/['’‘-]/g, ``).matchAll(word_count_regex)].length;

		// Check if chapter word count is very small
		// If true use auth_note_word_count as word count
		// const chp_raw_frac_total_count = chp_raw_word_count / (chp_raw_word_count + auth_note_word_count);
		const chp_raw_frac_total_count = chp_raw_word_count / (chp_raw_word_count + auth_note_word_count);
		if (chp_raw_frac_total_count < 0.05) {
			word_count = auth_note_word_count;
		} else {
			word_count = chp_raw_word_count;
		}

	}
	else {
		// Get the text to be counted
		chp_raw_text = chp_raw_node.textContent;
		// count the text
		word_count = [...chp_raw_text.replaceAll(/--/g, `—`).replaceAll(/['’‘-]/g, ``).matchAll(word_count_regex)].length;
	}

	// Add thousands seperators to the word count
	// Reference for Intl.NumberFormat: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
	word_count = new Intl.NumberFormat({ style: "decimal" }).format(word_count);

	// console.log(`SH Ch Word Counter Output: ${word_count}`);

	// Create element that will display the current chapter word count
	var WordCount_elm = Object.assign(document.createElement(`span`), {
		id: `userscript-word-count-span`,
		style: `display: block; text-align: center; margin: 0.5em 0 0 0`,
		innerHTML: `Words in Chapter: ${word_count}`
	});

	// Add the word count element
	chp_stats.after(WordCount_elm);
})();