AO3 Formatted Copy

Copy the curretly open AO3 work in the folloring MarkDown format '- [work name](work url) – [author name](author url) — '

// ==UserScript==
// @name           AO3 Formatted Copy
// @namespace      https://github.com/w4tchdoge
// @version        2.7.0-20250223_195541
// @description    Copy the curretly open AO3 work in the folloring MarkDown format '- [work name](work url) – [author name](author url) — '
// @author         w4tchdoge
// @homepage       https://github.com/w4tchdoge/MISC-UserScripts
// @match          *://archiveofourown.org/*works/*
// @match          *://archiveofourown.org/*series/*
// @match          *://archiveofourown.org/*bookmarks/*
// @match          *://archiveofourown.org/*works/*/bookmarks
// @icon           https://archiveofourown.org/favicon.ico
// @grant          GM_setClipboard
// @grant          GM.setClipboard
// @grant          GM_registerMenuCommand
// @grant          GM.registerMenuCommand
// @require        https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @license        AGPL-3.0-or-later
// @history        2.7.0 — Add Initially Read Formatted Copy functionality
// @history        2.6.1 — Fix the already read chapters input in re-read function not being padded correctly
// @history        2.6.0 — Cleanup; Add feature to re-read that adds how many chapters have already beed re-read
// @history        2.5.1 — Fix script not running on series pages
// @history        2.5.0 — Rewrite a bunch of code so that the re-read & note formats can be called from the bookmark pages where it will add the bookmark link to the output text
// @history        2.4.2 — Add a match rule for series pages
// @history        2.4.1 — Modify the match rule so that it matches collections/*/works URLs as well; Add an exlude role so it doesn't work on works/*/bookmarks pages as it isn't designed to
// @history        2.4.0 — Add ability to copy work as a generic Note formatting for the Notes section
// @history        2.3.4 — Fix Re-read Formatting not escaping underscores that are not part of a URL
// ==/UserScript==

(function () {
	function html_decode(txt_str) {
		let doc = new DOMParser().parseFromString(txt_str, `text/html`);
		return doc.documentElement.textContent;
	}

	function GetPadAmt(input_array) {
		const max_num_len = parseInt(input_array.reduce((a, b) => Math.max(a, b), -Infinity)).toString().length;
		if (max_num_len < 2) {
			return 2;
		} else {
			return max_num_len;
		}
	}

	const curr_page_url = new URL(window.location);

	// Regular Expressions used in multiple Format Copy functions
	const
		re_mu = /(?<!(\\|http(\S(\S+?))?))(_)/gmi,                                             /* Regex for escaping underscores that are not part of URLs for Markdown */
		re_ms = /(~|\*)/gmi,                                                                   /* Regex for escaping characters used in MD syntax that may appear in work or series titles – DO NO USE ON ANYTHING THAT COULD CONTAIN A URL */
		re_ap = /’|ʼ|‘/gmi,                                                                    /* Regex for replacing multiple apostrophe-like characters with an apostrophe */
		re_ck = /(^https?:\/\/)(.*\.)?(archiveofourown\.org)(.*?)(\/(works|series)\/\d+)\/?.*(?<!\/bookmarks)$/gmi,
		re_bmk_url_1 = /(^https?:\/\/)(.*\.)?(archiveofourown\.org)(.*?)(\/(works)\/\d+)(\/bookmarks)$/,
		re_bmk_url_2 = /(^https?:\/\/)(.*\.)?(archiveofourown\.org)(.*?)(\/(bookmarks)\/\d+)/;


	function AO3_genWork_Copy(s_t) {
		// Regular Expressions used only in this function
		const
			re_wu = /(^https?:\/\/)(.*\.)?(archiveofourown\.org)(.*?)(\/works\/\d+)\/?.*$/gmi;    /* Regex for extracting work URL without specific chapter */

		// Get Work Title & Work URL
		const [work_title, work_url] = (() => {
			const
				work_title_detect = document.querySelector(`#main .title`),
				work_title_bkmrk = document.querySelector(`.bookmark.index.group h4.heading a`);

			if (Boolean(work_title_detect)) {
				const
					work_title = html_decode(work_title_detect.textContent.trim().replace(re_ap, `'`).replace(re_ms, `\\$1`)),
					work_url = curr_page_url.href.replace(re_wu, `$1$3$5`);
				return [work_title, work_url];
			} else {
				const
					work_title = html_decode(work_title_bkmrk.textContent.trim().replace(re_ap, `'`).replace(re_ms, `\\$1`)),
					work_url = work_title_bkmrk.href.replace(re_wu, `$1$3$5`);
				return [work_title, work_url];
			}
		})();

		console.log(
			`
Work Title:
${work_title}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		console.log(
			`
Work URL:
${work_url}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		// Getting the array of Authors of a work
		const [a_nm, a_pg, authors] = (() => {
			const
				a_nm = [],
				a_pg = [],
				authors = [];

			// Getting authors from Bookmark pages
			if (curr_page_url.pathname.includes(`/bookmarks`)) {
				// Check for Anonymous author
				// auth_anon_check = 1 → Author is anonymous
				// auth_anon_check > 1 → Author is NOT anonymous
				const auth_anon_check = document.querySelector(`.bookmark.index.group .header > h4.heading`).childElementCount;

				switch (true) {
					case (auth_anon_check <= 1):
						a_nm.push(`Anonymous`);
						a_pg.push(`https://archiveofourown.org/collections/anonymous`);
						authors.push(`Anonymous`);
						return [a_nm, a_pg, authors];

					case (auth_anon_check > 1):
						const auth_list = Array.from(document.querySelectorAll(`.bookmark.index.group .header > h4.heading > a[rel='author']`));
						auth_list.forEach(function (elm) {
							a_nm.push(elm.textContent.replace(re_ms, `\\$1`));
							a_pg.push(elm.href);
							authors.push(`[${elm.textContent.replace(re_ms, `\\$1`)}](${elm.href})`);
						});
						return [a_nm, a_pg, authors];

					default:
						throw Error();
				}
			}
			// Getting authors from normal Work pages
			else {
				// Check for Anonymous author
				// auth_anon_check = 0 → Author is anonymous
				// auth_anon_check > 0 → Author is NOT anonymous
				const auth_anon_check = document.querySelector(`#workskin > .preface > .byline`).childElementCount;

				switch (true) {
					case (auth_anon_check < 1):
						a_nm.push(`Anonymous`);
						a_pg.push(`https://archiveofourown.org/collections/anonymous`);
						authors.push(`Anonymous`);
						return [a_nm, a_pg, authors];

					case (auth_anon_check >= 1):
						const auth_list = Array.from(document.querySelectorAll(`#workskin > .preface > .byline > a[rel='author']`));
						auth_list.forEach(function (elm) {
							a_nm.push(elm.textContent.replace(re_ms, `\\$1`));
							a_pg.push(elm.href);
							authors.push(`[${elm.textContent.replace(re_ms, `\\$1`)}](${elm.href})`);
						});
						return [a_nm, a_pg, authors];

					default:
						throw Error();
				}
			}
		})();

		console.log(
			`
Author(s):
${a_nm.join(`\n`)}
------------------------
Author Page(s):
${a_pg.join(`\n`)}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		return {
			work_title: work_title,
			work_url: work_url,
			authors: authors
		};
	}

	function generic_rr_frmt(s_t, rr_sta, rr_end) {
		// Select the Chapter # element & Wordcount element of the work via XPath and assign it to a variable for later
		// var chp_xp = `.//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dd[contains(concat(" ",normalize-space(@class)," ")," chapters ")]`;
		// var chp_cnt = document.evaluate(chp_xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
		const chp_cnt = (() => {
			const chp_xp = `.//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dd[contains(concat(" ",normalize-space(@class)," ")," chapters ")]`;
			const out_str = document.evaluate(chp_xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent.split(`/`).at(0);
			return out_str;
		})();

		const word_count = (() => {
			if ((chp_cnt == rr_end - rr_sta + 1) || (chp_cnt == 1)) {
				const wrd_cnt_elm = (() => {
					const wrd_xp = `.//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dd[contains(concat(" ",normalize-space(@class)," ")," words ")]`;
					return document.evaluate(wrd_xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
				})();

				const wrd_cnt = wrd_cnt_elm.textContent.replace(/,/g, ``);
				console.log(
					`
# of Chapters to Re-read = # of Chapters Published
Word Count set to work's current word cout (${wrd_cnt} words)
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
				);
				return wrd_cnt;
			} else {
				const wrd_cnt = `?`;
				console.log(
					`
# of Chapters to Re-read ≠ # of Chapters Published
Word Count left as "?" (unknown)
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
				);
				return wrd_cnt;
			}
		})();

		const cr_dt = (() => {
			const right_now_obj = new Date();
			const right_now = `${right_now_obj.getFullYear()}/${(right_now_obj.getMonth() + 1).toString().padStart(2, `0`)}/${right_now_obj.getDate().toString().padStart(2, `0`)}`;
			return right_now;
		})();

		return {
			word_count: word_count,
			cr_dt: cr_dt
		};
	}

	function AO3_Frmt_Copy() {
		// Regular Expressions used only in this function
		const
			re_ch = /(\d(\d+)?)(\/)(\?|\d(\d+)?)/gmi,                                              /* Regex for spacing out chapter counts (e.g. 3/? → 3 / ?) */
			re_su = /(^https?:\/\/)(.*\.)?(archiveofourown\.org)(.*?)(\/series\/\d+)\/?.*$/gmi;    /* Regex for extracting series URL while stripping unnecessary parts */


		const s_t = performance.now();

		console.log(
			`
Beginning execution of AO3 Formatted Copy Shortcut (UserScript)
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		if (curr_page_url.pathname.includes(`works`)) {		/* Check if the page is a work page */

			console.log(
				`
Executing General Formatting for \"Works\"
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			var { work_title, work_url, authors } = AO3_genWork_Copy(s_t);

			/* Generate final MD formatted text */
			let final_out = `[${work_title}](${work_url}) – ${authors.join(`, `)} — `.replace(re_mu, `\\$4`);
			console.log(
				`
Final Clipboard:
${final_out}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Paste final MD formatted text to clipboard */
			GM.setClipboard(final_out);
			console.log(
				`
final_out pasted to clipboard
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

		}

		if (curr_page_url.pathname.includes(`series`)) {		/* Check if the page is a series page */

			console.log(
				`
Executing Formatting for \"Series\"
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Get Series Title */
			let srs_title = html_decode(document.querySelector(`#inner #main h2.heading`).textContent.trim().replace(re_ap, `'`).replace(re_ms, `\\$1`));
			console.log(
				`
Series Title:
${srs_title}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Get Series URL */
			let srs_url = curr_page_url.href.replace(re_su, `$1$3$5`);
			console.log(
				`
Series URL:
${srs_url}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Get Author(s) of the series */
			let auth_list = document.querySelectorAll(`dl.series.meta.group a[rel='author']`);
			let a_nm = [];
			let a_pg = [];
			let authors = [];
			for (let x of auth_list) {
				a_nm.push(x.textContent.replace(re_ms, `\\$1`));
				a_pg.push(x.href);
				authors.push(`[${x.textContent.replace(re_ms, `\\$1`)}](${x.href})`);
			}
			console.log(
				`
Author(s):
${a_nm.join(`\n`)}
------------------------
Author Page(s):
${a_pg.join(`\n`)}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Get works in the series */
			let wr_list = document.querySelectorAll(`ul.series.work.index.group li.work`);
			let w_nm = [];
			let w_pg = [];
			let works = [];
			// var worksx = [];
			for (let x of wr_list) {

				// Select the Title element of the work via XPath and assign it to a variable
				let w_xp = `.//div[contains(concat(" ",normalize-space(@class)," ")," header ")]//h4[contains(concat(" ",normalize-space(@class)," ")," heading ")]/a[contains(@href,"works")][not(preceding-sibling::*)]`;
				let wx = document.evaluate(w_xp, x, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

				// Select the Chapter # element of the work via XPath and assign it to a variable
				let chp_xp = `.//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dd[contains(concat(" ",normalize-space(@class)," ")," chapters ")]`;
				let wkx = document.evaluate(chp_xp, x, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

				// Format the work title and url into MarkDown hyperlink and assign it to a variable
				let wfmt = `[${wx.textContent.replace(re_ms, `\\$1`)}](${wx.href})`;

				// Add the work title and url to two separate arrays
				w_nm.push(wx.textContent.replace(re_ms, `\\$1`));
				w_pg.push(wx.href);

				// works.push(wfmt);
				// worksx.push(`\t${1 + Array.from(wr_list).indexOf(x)}. ${wfmt} — ${wkx.textContent.replace(re_ch, `$1 $3 $4`)}`)

				// Add the MD list formatted work hyperlink to an array
				works.push(`\t${1 + Array.from(wr_list).indexOf(x)}. ${wfmt} — ${wkx.textContent.replace(re_ch, `$1 $3 $4`)}`);

			}
			// for (let x of works) {
			// 	worksx.push(`\t${1 + works.indexOf(x)}. ${x} — ${flr}`);
			// }
			console.log(
				`
Work Name(s):
${w_nm.join(`\n`)}
------------------------
Work Page(s):
${w_pg.join(`\n`)}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Get publishing status of the series */
			let pub_xp = `//dl[contains(concat(" ",normalize-space(@class)," ")," series ")][contains(concat(" ",normalize-space(@class)," ")," meta ")][contains(concat(" ",normalize-space(@class)," ")," group ")]//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dt[contains(text(), "Complete")]/following-sibling::*[1]`;
			let pub_r = document.evaluate(pub_xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent;
			if (pub_r == `No`) {
				var pb_s = `Ongoing`;
				var pub_st = `? \\[Ongoing\\]`;
				var wks_end = `\n\t${wr_list.length + 1}. ?`;
			} else if (pub_r == `Yes`) {
				var pb_s = `Completed`;
				var pub_st = `${wr_list.length} \\[Completed\\]`;
				var wks_end = ``;
			}
			console.log(
				`
Series Publishing Status:
${pb_s}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Generate final MD formatted text */
			/* var srs_f_o = `- [${srs_title}](${srs_url}) – ${authors.join(`, `)} (**Series** | ${wr_list.length} / ${pub_st})
	${worksx.join(`\n`)}${wks_end}`.replace(re_mu, `\\$4`); */
			let srs_f_o = `- [${srs_title}](${srs_url}) – ${authors.join(`, `)} (**Series** | ${wr_list.length} / ${pub_st})
${works.join(`\n`)}${wks_end}`.replace(re_mu, `\\$4`);
			console.log(
				`
Final Clipboard:
${srs_f_o}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Paste final MD formatted text to clipboard */
			GM.setClipboard(srs_f_o);
			console.log(
				`
final_out pasted to clipboard
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

		}

		console.log(
			`
AO3 Formatted Copy Shortcut (UserScript) executed successfully
------------------------
Time Elapsed: ${performance.now() - s_t}ms
———————————————————————————`
		);
	}

	function AO3_Reread_Format_Copy() {
		// RegEx that will be used
		const
			re_se_si = /^(\d(?:\d+)?)$/,                          /* Regex for matching single chapter input */
			re_se_mu = /^(\d(?:\d+)?)\D(?:\D+)?(\d(?:\d+)?)$/;    /* Regex for matching multiple chapter input */

		const rr_start_end = (() => {
			let user_input = prompt(
				`Input start and end chapter numbers for re-read in the form:
'start_#, end_#'`
			);
			while ((re_se_mu.test(user_input) || re_se_si.test(user_input)) == false) {
				user_input = prompt(
					`Incorrect input, try again.

Input start and end chapter numbers for re-read in the form: 'start_#, end_#'`
				);
			}

			try {
				user_input = JSON.parse(`[${user_input.replace(re_se_mu, `$1,$2`)}]`);
			} catch (SyntaxError) {
				user_input = JSON.parse(`[${parseInt(user_input)}]`);
			}

			return user_input;
		})();

		if (rr_start_end.length == 1) {

			const s_t = performance.now();

			console.log(
				`
Beginning execution of AO3 Formatted Copy Shortcut (UserScript)
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			console.log(
				`
Executing Re-read Formatting for \"Works\"
Single Chapter Re-read
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			const { work_title, work_url, authors } = AO3_genWork_Copy(s_t);

			console.log(`
Re-reading Chapter: ${rr_start_end.at(-0).toString().padStart(2, `0`)}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			// var bookmark_link = `Bookmark Link`;
			const { word_count, cr_dt } = generic_rr_frmt(s_t);
			const bookmark_link = (() => {
				switch (true) {
					case (re_bmk_url_1.test(curr_page_url.href)):
						return `[Bookmark Link](${document.querySelector(`li > [id^="bookmark_form_trigger"]`).href.replace(`/edit`, ``)})`;
					case (re_bmk_url_2.test(curr_page_url.href)):
						return `[Bookmark Link](${curr_page_url.href.toString()})`;
					default:
						return `Bookmark Link`;
				}
			})();

			/* Generate final MD formatted text */
			const final_out = `- [${work_title}](${work_url}) – ${authors.join(`, `)} —— ${bookmark_link}
\t- Re-read Chapter ${rr_start_end.at(-0).toString().padStart(2, `0`)} (${word_count} words) –– ${cr_dt}`.replace(re_mu, `\\$4`);
			console.log(
				`
Final Clipboard:
${final_out}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Paste final MD formatted text to clipboard */
			GM.setClipboard(final_out);
			console.log(
				`
final_out pasted to clipboard
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

		} else {

			const re_se_si_wminus = /^(\d(?:\d+)?)|-1$/;    /* Regex for matching single chapter input while allowing for -1 */

			// For getting the chapters that have already been read
			const alr_read_start_end = (() => {
				let user_input = prompt(
					`Input start and end chapter numbers for chapters that have already been re-read in the form:
'start_#, end_#'

If none, input '0'
If all, input '-1'`
				);
				while ((re_se_mu.test(user_input) || re_se_si_wminus.test(user_input)) == false) {
					user_input = prompt(
						`Incorrect input, try again.

Input start and end chapter numbers for chapters that have already been re-read in the form:
'start_#, end_#'

If none, input '0'
If all, input '-1'`
					);
				}

				try {
					user_input = JSON.parse(`[${user_input.replace(re_se_mu, `$1,$2`)}]`);
				} catch (SyntaxError) {
					user_input = JSON.parse(`[${parseInt(user_input)}]`);
				}

				return user_input;
			})();

			const s_t = performance.now();	// used for measuring time taken to execute script

			console.log(
				`
Beginning execution of AO3 Formatted Copy Shortcut (UserScript)
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			console.log(
				`
Executing Re-read Formatting for \"Works\"
Multi Chapter Re-read
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			const { work_title, work_url, authors } = AO3_genWork_Copy(s_t);

			const [rr_sta, rr_end] = rr_start_end;

			const [rr_sta_padded, rr_end_padded] = rr_start_end.map((elm, index, array) => elm.toString().padStart(GetPadAmt(array), `0`));

			console.log(
				`
Re-read Start Chapter: ${rr_sta_padded}
Re-read End Chapter  : ${rr_end_padded}

# of Chapters to Re-read:
${rr_end - rr_sta + 1}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			// var bookmark_link = `Bookmark Link`;
			const { word_count, cr_dt } = generic_rr_frmt(s_t, rr_sta, rr_end);
			const bookmark_link = (() => {
				switch (true) {
					case (re_bmk_url_1.test(curr_page_url.href)):
						return `[Bookmark Link](${document.querySelector(`li > [id^="bookmark_form_trigger"]`).href.replace(`/edit`, ``)})`;
					case (re_bmk_url_2.test(curr_page_url.href)):
						return `[Bookmark Link](${curr_page_url.href.toString()})`;
					default:
						return `Bookmark Link`;
				}
			})();

			// console.log(bookmark_link)

			const [f_o_dt_str, f_o_chps_rd_str] = (() => {
				// Define vars to be used in the switch statement
				let date_str, chprd_str;
				const [alr_read_sta, alr_read_end] = alr_read_start_end.map(elm => elm.toString().padStart(GetPadAmt(rr_start_end), `0`));

				// Get first elm in the already read input arr for checking
				const alread_first_elm = alr_read_start_end.at(0);

				switch (alread_first_elm) {
					case -1:
						date_str = cr_dt;
						chprd_str = ``;
						break;

					case 0:
						date_str = `${cr_dt} - ?`;
						chprd_str = `\n\t\t- ${cr_dt} -- Chapter ${rr_sta_padded}`;
						break;

					default:
						date_str = `${cr_dt} - ?`;
						chprd_str = `\n\t\t- ${cr_dt} -- Chapter ${alr_read_sta} - Chapter ${alr_read_end}`;
						break;
				}

				if (alr_read_start_end.length == 1 && /0|-1/.test(alread_first_elm) == false) {
					chprd_str = `\n\t\t- ${cr_dt} -- Chapter ${alr_read_sta}`;
				}

				return [date_str, chprd_str];
			})();

			/* Generate final MD formatted text */
			const final_out = `- [${work_title}](${work_url}) – ${authors.join(`, `)} —— ${bookmark_link}
\t- Re-read Chapter ${rr_sta_padded} - Chapter ${rr_end_padded} (${word_count} words) –– ${f_o_dt_str}${f_o_chps_rd_str}`.replace(re_mu, `\\$4`);
			console.log(
				`
Final Clipboard:
${final_out}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);

			/* Paste final MD formatted text to clipboard */
			GM.setClipboard(final_out);
			console.log(
				`
final_out pasted to clipboard
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
			);
		}
	}

	function NoteSection_Frmt_Copy() {

		const s_t = performance.now();

		console.log(
			`
Beginning execution of AO3 Formatted Copy Shortcut (UserScript)
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		console.log(
			`
Executing Note Formatting for \"Works\"
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		var { work_title, work_url, authors } = AO3_genWork_Copy(s_t);

		var bookmark_link = `Bookmark Link`;

		if (re_bmk_url_1.test(curr_page_url.href)) { bookmark_link = `[Bookmark Link](${document.querySelector(`li > [id^="bookmark_form_trigger"]`).href.replace(`/edit`, ``)})`; }
		if (re_bmk_url_2.test(curr_page_url.href)) { bookmark_link = `[Bookmark Link](${curr_page_url.href.toString()})`; }

		/* Generate final MD formatted text */
		let final_out = `- [${work_title}](${work_url}) – ${authors.join(`, `)} —— ${bookmark_link}
\t- `.replace(re_mu, `\\$4`);
		console.log(
			`
Final Clipboard:
${final_out}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		/* Paste final MD formatted text to clipboard */
		GM.setClipboard(final_out);
		console.log(
			`
final_out pasted to clipboard
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);
	}

	function IniRead_Frmt_Copy() {

		const
			re_se_si = /^(\d(?:\d+)?)$/,                          /* Regex for matching single chapter input */
			re_se_mu = /^(\d(?:\d+)?)\D(?:\D+)?(\d(?:\d+)?)$/;    /* Regex for matching multiple chapter input */

		const s_t = performance.now();

		const iniread_start_end = (() => {
			let user_input = prompt(
				`Input start and end chapter numbers for initially read chapters in the form:
		'start_#, end_#'`
			);
			while ((re_se_mu.test(user_input) || re_se_si.test(user_input)) == false && user_input != -1) {
				user_input = prompt(
					`Incorrect input, try again.

		Input start and end chapter numbers for re-read in the form: 'start_#, end_#'`
				);
			}

			if (user_input == -1) {
				throw new Error("UserScript execution cancelled");
			}

			try {
				user_input = JSON.parse(`[${user_input.replace(re_se_mu, `$1,$2`)}]`);
			} catch (SyntaxError) {
				user_input = JSON.parse(`[${parseInt(user_input)}]`);
			}

			return user_input;
		})();

		const chap_str_pad_amt = GetPadAmt(iniread_start_end);
		const chap_str = iniread_start_end.map(input => input.toString().padStart(chap_str_pad_amt, `0`)).join(` - `);

		const days_ago = Math.abs(parseInt(prompt(`How many days ago was this initially read?`, 1)));
		const prev_date = ((d) => {
			const date = new Date(d.setDate(d.getDate() - days_ago));
			const date_str = `${date.getFullYear()}/${((date.getMonth()) + 1).toString().padStart(2, `0`)}/${date.getDate().toString().padStart(2, `0`)}`;
			return date_str;
		})((new Date()));

		console.log(
			`
Beginning execution of AO3 Formatted Copy Shortcut (UserScript)
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		console.log(
			`
Executing Initially Read Formatting for \"Works\"
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		const { work_title, work_url, authors } = AO3_genWork_Copy(s_t);

		let bookmark_link = `Bookmark Link`;

		if (re_bmk_url_1.test(curr_page_url.href)) { bookmark_link = `[Bookmark Link](${document.querySelector(`li > [id^="bookmark_form_trigger"]`).href.replace(`/edit`, ``)})`; }
		if (re_bmk_url_2.test(curr_page_url.href)) { bookmark_link = `[Bookmark Link](${curr_page_url.href.toString()})`; }

		/* Generate final MD formatted text */
		const final_out = `- [${work_title}](${work_url}) – ${authors.join(`, `)} —— ${bookmark_link}
\t- Chapter ${chap_str} –– Initially Read on ${prev_date}`.replace(re_mu, `\\$4`);
		console.log(
			`
Final Clipboard:
${final_out}
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);

		/* Paste final MD formatted text to clipboard */
		GM.setClipboard(final_out);
		console.log(
			`
final_out pasted to clipboard
------------------------
Time Elapsed:
${performance.now() - s_t} ms
———————————————————————————`
		);
	}

	if (re_ck.test(curr_page_url.href)) {
		/* Below taken from https://stackoverflow.com/a/2511474/11750206 */
		/* Handler for the Keyboard Shortcut for AO3 Formatted Copy */
		function AO3FC_handler(e) {

			/* Keyboard Shortcut for AO3 Formatted Copy — Shift + Alt + K */
			if (e.shiftKey && e.altKey && e.code === `KeyK`) {

				/* Execute AO3 Formatted Copy */
				AO3_Frmt_Copy();
			}
		}

		/* Register the AO3FC handler */
		document.addEventListener(`keyup`, AO3FC_handler, false);
		GM.registerMenuCommand(`Copy Work/Series as formatted`, AO3_Frmt_Copy);
	}

	/* Add Options to the Tampermonkey Popup Menu to execute each function */
	GM.registerMenuCommand(`Copy Work w/ re-read formatting`, AO3_Reread_Format_Copy);
	GM.registerMenuCommand(`Copy Work w/ Note Formatting`, NoteSection_Frmt_Copy);
	GM.registerMenuCommand(`Copy Work w/ Initially Read Formatting`, IniRead_Frmt_Copy);

})();