Greasy Fork is available in English.

NUtools

Tools to interact with novelupdates.com site.

Fra 13.08.2018. Se den seneste versjonen.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         NUtools
// @namespace    JDoe_NUtoolsV2
// @version      6
// @description  Tools to interact with novelupdates.com site.
// @author       John Doe
// @match        http*://*.novelupdates.com/
// @match        http*://*.novelupdates.com/?pg=*
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery.serializeJSON/2.9.0/jquery.serializejson.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery-popup-overlay/1.7.13/jquery.popupoverlay.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/alasql/0.4.5/alasql.min.js
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// ==/UserScript==
((() => {
	const VERSION = 6;
	const DBVERSION = 5;
	const DEBUG = false;
	const DEFAULTCONFIGS = {
		cfg_move_to_list_id: 1,
		cfg_move_req_confirm: 0,
		cfg_cover_show_icon: 1,
		cfg_move_reload: 1
	};
	const LANGS_OPTIONS = [
		{
			isoAlpha3: "CHN",
			isoAlpha2: "CN",
			m49: 156,
			aliases: [156, "cn", "chn", "china", "chinese", "mandarim", "cantonese"]
		},
		{
			isoAlpha3: "JPN",
			isoAlpha2: "JP",
			m49: 392,
			aliases: ["jp", "jpn", "japan", "japanese"]
		},
		{
			isoAlpha3: "PHL",
			isoAlpha2: "PH",
			m49: 608,
			aliases: ["ph", "phl", "philippines", "filipino"]
		},
		{
			isoAlpha3: "IDN",
			isoAlpha2: "ID",
			m49: 360,
			aliases: ["id", "idn","indonesia", "indonesian"]
		},
		{
			isoAlpha3: "KHM",
			isoAlpha2: "KH",
			m49: 116,
			aliases: ["kh", "khm", "cambodia", "cambodian", "khmer"]
		},
		{
			isoAlpha3: "KOR",
			isoAlpha2: "KR",
			m49: 408,
			aliases: ["kr", "kor", "korea", "korean", 410, "prk","kp"]
		},
		{
			isoAlpha3: "MYS",
			isoAlpha2: "MY",
			m49: 458,
			aliases: ["my", "mys", "malaysia", "malaysian"]
		},
		{
			isoAlpha3: "THA",
			isoAlpha2: "TH",
			m49: 764,
			aliases: ["th", "tha", "thailand", "thai"]
		},
		{
			isoAlpha3: "VNM",
			isoAlpha2: "VN",
			m49: 704,
			aliases: ["vn", "vnm", "viet nam", "vietnamese"]
		}
	];
	const cssFiles = [
		"https://fonts.googleapis.com/icon?family=Material+Icons",
		"https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.css"
	];
	const htmlStyles = `
<style type="text/css">
/* Rules for sizing the icon. */
.material-icons.md-12 { font-size: 12px; }
.material-icons.md-18 { font-size: 18px; }
.material-icons.md-20 { font-size: 20px; }
.material-icons.md-24 { font-size: 24px; }
.material-icons.md-36 { font-size: 36px; }
.material-icons.md-48 { font-size: 48px; }
/* Rules for using icons as black on a light background. */
.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); }
.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); }
/* Rules for using icons as white on a dark background. */
.material-icons.md-light { color: rgba(255, 255, 255, 1); }
.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); }
.material-icons {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	vertical-align: middle;
}
/* ################################  */
.js-nutools-hidden { display:none; }
.js-nutools-show-cover,.js-nutools-move-to-list { cursor: pointer; }
#js-nutools-settings-overlay {
	-webkit-transform: scale(0.8);
	-moz-transform: scale(0.8);
	-ms-transform: scale(0.8);
	transform: scale(0.8);
}
.popup_visible #js-nutools-settings-overlay {
	-webkit-transform: scale(1);
	-moz-transform: scale(1);
	-ms-transform: scale(1);
	transform: scale(1);
}
#js-nutools-settings-overlay fieldset {
	border: 3px solid #1F497D;
	background: #ddd;
	border-radius: 2px;
	padding: 5px;
	margin-top: 30px;
}
#js-nutools-settings-overlay fieldset legend {
	background: #1F497D;
	color: #fff;
	padding: 5px 20px ;
	font-size: 20px;
	border-radius: 5px;
	box-shadow: 0 0 0 1px #ddd;
	margin-left: 20px;
}
.js-nutools-well{
	min-height:20px;
	padding:19px;
	margin-bottom:20px;
	background-color:#f5f5f5;
	border:1px solid #e3e3e3;
	border-radius:4px;
	-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);
	box-shadow:inset 0 1px 1px rgba(0,0,0,.05)
}
.js-nutools-well blockquote{
	border-color:#ddd;
	border-color:rgba(0,0,0,.15)
}
.js-nutools-well-lg{
	padding:24px;
	border-radius:6px
}
.js-nutools-well-sm{
	padding:9px;
	border-radius:3px
}
</style>
`;
	const htmlCPbutton = `
<div class="">
	<p><button id="js-nutools-open-userscript-cp"><i class="material-icons md-18">settings</i>NUtools Settings</button></p>
	<p><button id="js-nutools-get-language-button">Add language label</button></p>
</div>
`;
	const htmlPageAppend = `
<div class="js-nutools-hidden">
<!-- NUtools overlay -->

<div id="js-nutools-move-confirm-overlay" class="js-nutools-well">
<div class="message">
Move '<b class="novel-title"></b>' to reading list [ <b class="reading-list-id"></b> ]?
</div>
<br><br>
<center>
<button type="button" class="js-nutools-move-confirm-overlay_close">Cancel</button>
<button type="button" id="js-nutools-move-confirm-overlay-move-button" data-reading-list-id="" data-novel-id="">Move</button>
</center>
</div>

<div id="js-nutools-get-language-confirm-overlay" class="js-nutools-well">
<div class="message">
<h3>Warning</h3>
<p>This action will create multiple requests to website and it is very intensive and take a long time to complete. Use it with extreme moderation.</p>
</div>
<br><br>
<center>
<button type="button" class="js-nutools-get-language-confirm-overlay_close">Cancel</button>
<button type="button" id="js-nutools-get-language-confirm-button">Get language</button>
</center>
</div>

<div id="js-nutools-cover-overlay" class="js-nutools-well">
</div>

<div id="js-nutools-settings-overlay" class="js-nutools-well">
<form id="settings_form">
<h3>Settings:</h3>
<fieldset>
<legend><i class="material-icons">format_indent_increase</i> Reading List</legend>
	<p> Move to <a href="https://www.novelupdates.com/reading-list/">Reading List ID</a> :
	<select name="cfg_move_to_list_id" id="cfg_move_to_list_id">
	</select>
</p>
	<p>Require confirmation before moving to list? :<br>
<input type="radio" name="cfg_move_req_confirm" value="1"> Yes
<input type="radio" name="cfg_move_req_confirm" value="0" checked="checked"> No
</p>
	<p>Reload page after moving:<br>
<input type="radio" name="cfg_move_reload" value="1"> Yes
<input type="radio" name="cfg_move_reload" value="0" checked="checked"> No
</p>

</fieldset>
<fieldset>
<legend><i class="material-icons">photo</i> Cover </legend>
	</p>Show icon ? :<br>
<input type="radio" name="cfg_cover_show_icon" value="1"> Yes
<input type="radio" name="cfg_cover_show_icon" value="0" checked="checked"> No</li>
	</p>
</fieldset>

<center>
<button type="button" class="js-nutools-settings-overlay_close">Close</button>
<button type="submit" class="js-nutools-settings-overlay_save">Save</button>
</center>
</form>
<style> input:invalid { border-color: #DD2C00; }</style>

</div>
<!-- /NUtools overlay -->
</div>
`;

	// functions
	let storage = {
		options: {
			prefix: ""
		},
		// “Set” means “add if absent, replace if present.”
		set: function(key, value) {
			let storageVals = this.read(key);

			if (typeof storageVals === "undefined" || !storageVals) {
				// add if absent
				return this.add(key, value);
			} else {
				// replace if present
				this.write(key, value);
				return true;
			}
		},
		// “Add” means “add if absent, do nothing if present” (if a uniquing collection).
		add: function(key, value) {
			let storageVals = this.read(key, false);

			if (typeof storageVals === "undefined" || !storageVals) {
				this.write(key, value);
				return true;
			} else {
				if (this._isArray(storageVals)) { // is array
					let index = storageVals.indexOf(value);

					if (index !== -1) {
						// do nothing if present
						return false;
					} else {
						// add if absent
						storageVals.push(value);
						this.write(key, storageVals);
						return true;
					}
				} else if (this._isObject(storageVals)) { // is object
					// merge obj value on obj
					let result;
					let objToMerge = value;

					result = Object.assign(storageVals, objToMerge);
					this.write(key, result);
					return false;
				}
				return false;
			}
		},
		// “Replace” means “replace if present, do nothing if absent.”
		replace: function(key, itemFind, itemReplacement) {
			let storageVals = this.read(key, false);

			if (typeof storageVals === "undefined" || !storageVals) {
				// do nothing if absent
				return false;
			} else {
				if (this._isArray(storageVals)) { // is Array
					let index = storageVals.indexOf(itemFind);

					if (index !== -1) {
						// replace if present
						storageVals[index] = itemReplacement;
						this.write(key, storageVals);
						return true;
					} else {
						// do nothing if absent
						return false;
					}
				} else if (this._isObject(storageVals)) {
					// is Object
					// replace property's value
					storageVals[itemFind] = itemReplacement;
					this.write(key, storageVals);
					return true;
				}
				return false;
			}
		},
		// “Remove” means “remove if present, do nothing if absent.”
		remove: function(key, value) {
			if (typeof value === "undefined") { // remove key
				this.delete(key);
				return true;
			} else { // value present
				let storageVals = this.read(key);

				if (typeof storageVals === "undefined" || !storageVals) {
					return true;
				} else {
					if (this._isArray(storageVals)) { // is Array
						let index = storageVals.indexOf(value);

						if (index !== -1) {
							// remove if present
							storageVals.splice(index, 1);
							this.write(key, storageVals);
							return true;
						} else {
							// do nothing if absent
							return false;
						}
					} else if (this._isObject(storageVals)) { // is Object
						let property = value;

						delete storageVals[property];
						this.write(key, storageVals);
						return true;
					}
					return false;
				}
			}
		},
		get: function(key, defaultValue) {
			return this.read(key, defaultValue);
		},
		// GM storage API
		read: function(key, defaultValue) {
			return this.unserialize(GM_getValue(this._prefix(key), defaultValue));
		},
		write: function(key, value) {
			return GM_setValue(this._prefix(key), this.serialize(value));
		},
		delete: function(key) {
			return GM_deleteValue(this._prefix(key));
		},
		readKeys: function() {
			return GM_listValues();
		},
		// /GM Storage API
		getAll: function() {
			const keys = this._listKeys();
			let obj = {};

			for (let i = 0, len = keys.length; i < len; i++) {
				obj[keys[i]] = this.read(keys[i]);
			}
			return obj;
		},
		getKeys: function() {
			return this._listKeys();
		},
		getPrefix: function() {
			return this.options.prefix;
		},
		empty: function() {
			const keys = this._listKeys();

			for (let i = 0, len = keys.lenght; i < len; i++) {
				this.delete(keys[i]);
			}
		},
		has: function(key) {
			return this.get(key) !== null;
		},
		forEach: function(callbackFunc) {
			const allContent = this.getAll();

			for (let prop in allContent) {
				callbackFunc(prop, allContent[prop]);
			}
		},
		unserialize: function(value) {
			if (this._isJson(value)) {
				return JSON.parse(value);
			}
			return value;
		},
		serialize: function(value) {
			if (this._isJson(value)) {
				return JSON.stringify(value);
			}
			return value;
		},
		_listKeys: function(usePrefix = false) {
			const prefixed = this.readKeys();
			let unprefixed = [];

			if (usePrefix) {
				return prefixed;
			} else {
				for (let i = 0, len = prefixed.length; i < len; i++) {
					unprefixed[i] = this._unprefix(prefixed[i]);
				}
				return unprefixed;
			}
		},
		_prefix: function(key) {
			return this.options.prefix + key;
		},
		_unprefix: function(key) {
			return key.substring(this.options.prefix.length);
		},
		_isJson: function(item) {
			try {
				JSON.parse(item);
			} catch (e) {
				return false;
			}
			return true;
		},
		_isObject: function(a) {
			return (!!a) && (a.constructor === Object);
		},
		_isArray: function(a) {
			return (!!a) && (a.constructor === Array);
		}
	};

	function isObject(val) {
		if (val === null) {
			return false;
		}
		return ((typeof val === "function") || (typeof val === "object"));
	}

	function setDebug(isDebug = false) {
		if (isDebug) {
			window.debug = window.console.log.bind(window.console, "%s: %s");
		} else {
			window.debug = function() {};
			window.console.log = function() {};
		}
	}

	function appendFilesToHead(arr = [], forceExt = false) {

		for (let i = 0; i < arr.length; i++) {
			let urlStr = arr[i];
			let ext = (forceExt) ? forceExt : urlStr.slice((Math.max(0, urlStr.lastIndexOf(".")) || Infinity) + 1);
			let ele = null;

			switch (ext) {
				case "js":
					ele = document.createElement("script");
					ele.type = "text/javascript";
					ele.src = urlStr;
					break;
				case "css":
					ele = document.createElement("link");
					ele.rel = "stylesheet";
					ele.type = "text/css";
					ele.href = urlStr;
					break;
				default:
					ele = document.createElement("script");
					ele.type = "text/javascript";
					ele.src = urlStr;
			}
			document.getElementsByTagName("head")[0].appendChild(ele);
		}
	}

	function onlyUnique(value, index, self) {
		return self.indexOf(value) === index;
	}

	function decodeHtml(html) {
		let txt = document.createElement("textarea");
		txt.innerHTML = html;
		return txt.value;
	}
	function get_lang_code(langStr) {
		let niddle = langStr.toString().trim().toLowerCase();
		for(let i=0,l=LANGS_OPTIONS.length; i<l; i++) {
			let aliases = LANGS_OPTIONS[i].aliases;
			if (aliases.indexOf(niddle) > -1) {
				return LANGS_OPTIONS[i].isoAlpha3;
			}
		}
		return "";
	}

	function readConfig() {
		let configs = storage.get("configs", DEFAULTCONFIGS);

		configs = Object.assign({}, DEFAULTCONFIGS, configs);
		debug("loading", JSON.stringify(configs));
		return configs;
	}

	function saveConfig(args) {
		args.cfg_move_to_list_id = (args.cfg_move_to_list_id=="---") ? "" : args.cfg_move_to_list_id;
		let configs = {
			cfg_cover_show_icon: args.cfg_cover_show_icon,
			cfg_move_to_list_id: args.cfg_move_to_list_id,
			cfg_move_req_confirm: args.cfg_move_req_confirm,
			cfg_move_reload: args.cfg_move_reload
		};
		storage.set("configs", configs);
		debug("saving", JSON.stringify(configs));
		return configs;
	}

	function sys_check_dbversion() {
		let version = storage.get("sys_dbversion", 0);
		if (version < 5) {
			_upgrade_v5();
			storage.set("sys_dbversion", 5)
		}
		return;
	}

	function _upgrade_v5() {
		res = mybase.exec("SELECT * FROM novels WHERE lang='N/A' ");
		res.forEach(function(arr) {
			let id = arr.id;
			mybase.exec("DELETE FROM novels WHERE id='"+ id +"' ");
		});
		mybase.exec("UPDATE novels SET lang='' WHERE lang NOT IN ('(JP)','(CN)','(KR)')");
		mybase.exec("UPDATE novels SET lang='JPN' WHERE lang='(JP)'");
		mybase.exec("UPDATE novels SET lang='CHN' WHERE lang='(CN)'");
		mybase.exec("UPDATE novels SET lang='KOR' WHERE lang='(KR)'");
		debug("saving db to localstorage", alasql.databases.mybase.tables.novels.data);
		storage.set("noveldb", alasql.databases.mybase.tables.novels.data);
	};

	function db_init() {
		storage_novel_db = storage.get("noveldb", []);
		mybase = new alasql.Database("mybase");
		mybase.exec("CREATE TABLE novels (id INT, title STRING, lang STRING)");
		debug("localstorage novel db data .lenght", storage_novel_db.length);
		if (storage_novel_db.length >= 1) {
			//debug("direct assign data to:", "alasql.databases.mybase.tables.novels.data");
			alasql.databases.mybase.tables.novels.data = storage_novel_db;
		}
	}

	// https://stackoverflow.com/questions/7298364/using-jquery-and-json-to-populate-forms
	function populateForm($form, data) {
		$.each(data, (key, value) => {// all json fields ordered by name
			let $ctrls = $form.find("[name='" + key + "']"); //all form elements for a name. Multiple checkboxes can have the same name, but different values
			if ($ctrls.is("select")) { //special form types
				$("option", $ctrls).each(function() {
					if (this.value == value) {
						this.selected = true;
					}
				});
			} else if ($ctrls.is("textarea")) {
				$ctrls.val(value);
			} else {
				switch ($ctrls.attr("type")) { //input type
					case "text":
					case "hidden":
						$ctrls.val(value);
						break;
					case "radio":
						if ($ctrls.length >= 1) {
							$.each($ctrls, function(index) { // every individual element
								let elemValue = $(this).attr("value");
								let singleVal = value;
								let elemValueInData = singleVal;
								if (elemValue === value) {
									$(this).prop("checked", true);
								} else {
									$(this).prop("checked", false);
								}
							});
						}
						break;
					case "checkbox":
						if ($ctrls.length > 1) {
							$.each($ctrls, function(index) { // every individual element
								let elemValue = $(this).attr("value");
								let elemValueInData;
								let singleVal;
								for (let i = 0; i < value.length; i++) {
									singleVal = value[i];
									debug("singleVal", singleVal + " value[i][1]" + value[i][1]);
									if (singleVal === elemValue) {
										elemValueInData = singleVal;
									}
								}
								if (elemValueInData) {
									$(this).prop("checked", true);
									//$(this).prop("value", true);
								} else {
									$(this).prop("checked", false);
									//$(this).prop("value", false);
								}
							});
						} else if ($ctrls.length == 1) {
							$ctrl = $ctrls;
							if (value) {
								$ctrl.prop("checked", true);
							} else {
								$ctrl.prop("checked", false);
							}
						}
						break;
				} //switch input type
			} // if/else
		}); // all json fields
	} // populate form
	// end functions

	setDebug(DEBUG);
	storage.options.prefix = "nutools_";
	toastr.options = {
		closeButton: false,
		debug: false,
		newestOnTop: false,
		progressBar: false,
		positionClass: "toast-top-right",
		preventDuplicates: false,
		onclick: null,
		showDuration: "300",
		hideDuration: "1000",
		timeOut: "5000",
		extendedTimeOut: "1000",
		showEasing: "swing",
		hideEasing: "linear",
		showMethod: "fadeIn",
		hideMethod: "fadeOut"
	};
	let storage_novel_db = null;
	let mybase = null;
	let cfgs = readConfig();

	db_init();
	sys_check_dbversion();

	appendFilesToHead(cssFiles, "css");
	$("head").append(htmlStyles);
	$("body").append(htmlPageAppend);
	$(".l-content").prepend(htmlCPbutton);

	setTimeout(function() {
		let ln_rows = 0;
		let ln_without_lang = {};

		$("td[class^='sid']").each(function() {
			let $this = $(this);
			let str = $this.attr("class");
			let id = parseInt(str.replace("sid", ""));
			let title = $this.find("a").attr("title");
			let surl = $this.find("a").attr("href");
			let tr = $this.parent().closest("tr");
			let html = "";
			let lang = "";
			let html_cover = (cfgs.cfg_cover_show_icon == 1) ? `<span class="js-nutools-show-cover" data-novel-url="${surl}" title="Show Cover"><i class="material-icons md-18">photo</i></span> ` : "";
			let html_moveToList = ` <span class="js-nutools-move-to-list" data-novel-id="${id}" data-novel-title="${title}" title="Move to list"><i class="material-icons md-18">format_indent_increase</i></span> `;
			let html_lang = ` <span class="js-nutools-lang" data-novel-id="${id}" data-novel-lang=""></span>`;

			// alasql database
			let result = mybase.exec("SELECT * FROM novels WHERE id=" + id + " LIMIT 1");
			if (result.length > 0) {
				debug("FOUND novel", result);
				if (result[0].lang == "") {
					ln_without_lang[id] = surl;
				}
				lang = result[0].lang;
				html_lang = ` <span class="js-nutools-lang" data-novel-id="${id}" data-novel-lang="${result[0].lang}">`;
				if (lang !="" ){
					html_lang += `(<b>${lang}</b>)`;
				} else  {
					html_lang += `<b></b>`;
				}
				html_lang += `</span>`;
			} else {
				debug("NOT FOUND novel, INSERT", [id, title, ""]);
				mybase.exec("INSERT INTO novels (?,?,?)", [id, title, ""]);
				ln_without_lang[id] = surl;
			}

			tr.attr("data-novel-id", id);
			$(this).prepend(`<span class="js-nutools-wrap" data-novel-id="${id}" data-novel-title="${title}">${html}${html_cover}${html_moveToList}${html_lang}</span> `);
			ln_rows++;
		});
		debug("ln without lang", ln_without_lang);
		debug("page nl rows", ln_rows);
		debug("saving db to localstorage", alasql.databases.mybase.tables.novels.data);
		storage.set("noveldb", alasql.databases.mybase.tables.novels.data);

		// Event handlers
		$(".js-nutools-show-cover").click(function() {
			let url = $(this).attr("data-novel-url");

			$.ajax({
				url: url,
				success: function(newHTML, textStatus, jqXHR) {
					let img_html = $(newHTML).find(".seriesimg img, .serieseditimg img").first();
					$("#js-nutools-cover-overlay").html(img_html).popup("show");
				},
				error: function(jqXHR, textStatus, errorThrown) {}
			});
		});
		$(".js-nutools-move-to-list").click(function() {
			let id = parseInt($(this).attr("data-novel-id"));
			let title = $(this).attr("data-novel-title");
			let url = "https://www.novelupdates.com/updatelist.php?lid=" + cfgs.cfg_move_to_list_id + "&act=move&sid=" + id;
			// let url = `https://www.novelupdates.com/updatelist.php?lid=${cfgs.cfg_move_to_list_id}&act=move&sid=${id}`;

			if (cfgs.cfg_move_req_confirm == 1) {
				let message = "Move <i>" + title + "</i> to the reading list [ " + cfgs.cfg_move_to_list_id + " ]?";
				$("#js-nutools-move-confirm-overlay .novel-title").html(title);
				$("#js-nutools-move-confirm-overlay .reading-list-id").html(cfgs.cfg_move_to_list_id);
				$("#js-nutools-move-confirm-overlay-move-button").attr("data-novel-id", id);
				$("#js-nutools-move-confirm-overlay").popup({
					color: "white",
					opacity: 1,
					transition: "0.3s",
					scrolllock: true,
					blur: false
				});
				$("#js-nutools-move-confirm-overlay").popup("show");
			} else {
				$.get(url, function(data) {
					debug("ajax get", url);
					toastr.success("Novel moved to list");
				});
				if (cfgs.cfg_move_reload == 1) {
					location.reload();
				}
			}
		});
		$("body").on("click", "#js-nutools-move-confirm-overlay-move-button", function() {
			let id = parseInt($(this).attr("data-novel-id"));
			let title = $(this).attr("data-novel-title");
			let url = "https://www.novelupdates.com/updatelist.php?lid=" + cfgs.cfg_move_to_list_id + "&act=move&sid=" + id;
			// let url = `https://www.novelupdates.com/updatelist.php?lid=${cfgs.cfg_move_to_list_id}&act=move&sid=${id}`;
			$.get(url, function(data) {
				debug("ajax get", url);
				$("#js-nutools-move-confirm-overlay").popup("hide");
				if (cfgs.cfg_move_reload == 1) {
					location.reload();
				}
			});
		});
		$("#js-nutools-get-language-button").click(function() {
			$("#js-nutools-get-language-confirm-overlay").popup({
				color: "white",
				opacity: 0.5,
				transition: "0.3s",
				scrolllock: true,
				blur: false
			});
			$("#js-nutools-get-language-confirm-overlay").popup("show");
		});
		$("#js-nutools-get-language-confirm-button").click(function() {
			let deferreds = [];
			let novels_lang = [];

			$("#js-nutools-get-language-confirm-overlay").popup("hide");
			for (let key in ln_without_lang) {
				let id = key;
				let url = ln_without_lang[key];
				let deferred = $.ajax(url, {
					success: function(html) {
						let lang = "";

						lang = $(html).find("#showlang a").first().text();
						console.log( lang );
						if (lang != "") {
							lang.replace(/()/gi, "");
						} else {
							lang = "N/A";
						}
						novels_lang[id] = lang;
					}
				});
				deferreds.push(deferred);
			}
			$.when.apply($, deferreds).then(function() {
				for (let key in novels_lang) {
					let isoAlpha3Code = get_lang_code(novels_lang[key]);

					$(".js-nutools-lang[data-novel-id='" + key + "']").html("(<b>" + isoAlpha3Code + "</b>)");
					debug("UPDATE novel entry", [key, isoAlpha3Code]);
					mybase.exec("UPDATE novels SET lang='" + isoAlpha3Code + "' WHERE id=" + key + "");
				}
				debug("saving db to localstorage", alasql.databases.mybase.tables.novels.data);
				storage.set("noveldb", alasql.databases.mybase.tables.novels.data);
			});
		});
		$("#js-nutools-open-userscript-cp").click(function() {
            let url = "/reading-list/";
			$.ajax({
				url: url,
				success: function(newHTML, textStatus, jqXHR) {
					let html = $(newHTML).find("SELECT[name='taskOption']").html();
					debug('html', html);
					$("#cfg_move_to_list_id").html(html);
					$("#js-nutools-settings-overlay").popup("show");
					let form = $("#settings_form");
					populateForm(form, cfgs);
				},
				error: function(jqXHR, textStatus, errorThrown) {}
			});
		});
		$(".js-nutools-settings-overlay_save").click(function() {
			event.preventDefault();
			let args = $("#settings_form").serializeJSON();
			cfgs = saveConfig(args);
			$("#js-nutools-settings-overlay").popup("hide");
			location.reload();
		});
		// $("#settings_form").on("focusin", "input", function() {
		// 	//$(this).data("val", $(this).val());
		// }).on("change", "input", function() {''
		// 	let args = $("#settings_form").serializeJSON();
		// 	cfgs = saveConfig(args);
		// });
		$("#js-nutools-settings-overlay").popup({
			color: "white",
			opacity: 1,
			transition: "0.3s",
			scrolllock: true,
			blur: false
		});

	}, 79); // milisec
}))();