Royal Road: Add Word Count to Statistics Section

Adds the word count of a fiction (taken from the information tooltip in the Pages statistic) as it's own statistic

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           Royal Road: Add Word Count to Statistics Section
// @namespace      https://github.com/w4tchdoge
// @version        1.1.0-20250611_121836
// @description    Adds the word count of a fiction (taken from the information tooltip in the Pages statistic) as it's own statistic
// @author         w4tchdoge
// @homepage       https://github.com/w4tchdoge/MISC-UserScripts
// @match          *://*.royalroad.com/fiction/*
// @exclude        *://*.royalroad.com/fiction/*/*/chapter/*
// @license        AGPL-3.0-or-later
// @run-at         document-start
// @history        1.1.0 — Add word count next to chapter count in the ToC header bar thing
// @history        1.0.0 — Initial release
// ==/UserScript==

(async function () {
	`use strict`;

	// modified from https://stackoverflow.com/a/61511955/11750206
	function waitForElm(selector, search_root = document) {
		return new Promise(resolve => {
			if (search_root.querySelector(selector)) {
				return resolve(search_root.querySelector(selector));
			}

			const observer = new MutationObserver(mutations => {
				if (search_root.querySelector(selector)) {
					observer.disconnect();
					resolve(search_root.querySelector(selector));
				}
			});

			// If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
			observer.observe(document.documentElement, {
				childList: true,
				subtree: true
			});
		});
	}

	// wait for fic info section to load in
	const ficinfo = await waitForElm(`div.fiction-info`);

	// Get the element which contains the word "Pages" and which also contains the word count in an informational tooltip
	const statsPage_pages_elem = ficinfo.querySelector(`.fiction-stats .stats-content .list-unstyled li:has(> i)`);

	// Get Word Count
	const word_count_str = statsPage_pages_elem.querySelector(`i`).getAttribute(`data-content`).replace(/.*calculated from (.*) words./gmi, `$1`);
	console.log(`
RR WORD COUNT SCRIPT
WORD COUNT: ${word_count_str}`);

	// Make the "Heading" element that describes the data below it. i.e. it says "Words"
	const elm_wordcount_title = (() => {
		let outelm = Object.assign(document.createElement(`li`), {
			id: `userscript-words-title`,
			innerHTML: `Words :`
		});

		// Add the bold and uppercase classes to make it look like the other stats page entries
		outelm.classList.add(`bold`, `uppercase`);

		return outelm;
	})();

	// Make the "Data" element that has the actual data. i.e. it says the word count
	const elm_wordcount_data = (() => {
		let outelm = Object.assign(document.createElement(`li`), {
			id: `userscript-words-data`,
			innerHTML: `${word_count_str}`
		});

		// Add the bold and uppercase classes to make it look like the other stats page entries
		outelm.classList.add(`bold`, `uppercase`, `font-red-sunglo`);

		return outelm;
	})();

	// Add the word count elements before the page count elements
	statsPage_pages_elem.before(elm_wordcount_title, elm_wordcount_data);

	const tocbar_ch_cnt = (() => {
		const tocbar = ficinfo.querySelector(`div.portlet > .portlet-title:has(> .caption > a#toc)`);
		const xpath = `.//*[contains(concat(" ",normalize-space(@class)," ")," actions ")]/span[contains(normalize-space(),"Chapter")]`;
		const chcntpar = document.evaluate(xpath, tocbar, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
		return chcntpar;
	})();

	const tocbar_wc = (() => {
		let base_elm = tocbar_ch_cnt.cloneNode(true);
		base_elm.textContent = `${word_count_str} Words`;
		base_elm.style.marginRight = `0.25em`;
		return base_elm;
	})();

	tocbar_ch_cnt.after(tocbar_wc);
})();