Greasy Fork

Greasy Fork is available in English.

碧航艦隊科技工具

海事局的艦隊科技頁面 可以點擊該行來標記已120的船 顯示艦船頭像

当前为 2021-11-25 提交的版本,查看 最新版本

// ==UserScript==
// @name         碧航艦隊科技工具
// @namespace    https://github.com/x94fujo6rpg/SomeTampermonkeyScripts
// @version      0.04
// @description  海事局的艦隊科技頁面 可以點擊該行來標記已120的船 顯示艦船頭像
// @author       x94fujo6
// @match        https://wiki.biligame.com/blhx/%E8%88%B0%E9%98%9F%E7%A7%91%E6%8A%80
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

/**
 * [changelog]
 * 0.04
 * 顯示艦船頭像(海事局)
 * 自動更新並快取
 * 
 * 0.03
 * 修改標記顏色/等待的selector、正確應用delay參數
 * 因用手機看wiki時部分欄位會被隱藏,標記方式改為整行都能觸發
 * 
 * 0.02
 * 自動修復當頁面在背景中載入後標題列的錯位問題 (WIKI本身問題 關掉腳本一樣會)
 * https://i.imgur.com/kQWlEU1.jpg
 * 
 */

(function () {
	'use strict';
	const
		key = { ship_id: "ship_id", ship_icon: "ship_icon" },
		bg_color = "silver",
		getValue = (_key) => GM_getValue(_key, []),
		setValue = (_key, _list) => GM_setValue(_key, (_list instanceof Array) ? _list : []),
		log = (...any) => console.log(`%c[碧航艦隊科技工具]%c`, "color:OrangeRed;", "", ...any),
		sleep = (ms = 0) => new Promise(resolve => setTimeout(resolve, ms));

	waitPageLoad(30, "#CardSelectTr>thead", main);

	async function waitPageLoad(retry = 30, selector = "", run = () => { }, delay = 1000) {
		await waitTab();
		waitEle();

		function waitTab() {
			return new Promise(resolve => {
				if (document.visibilityState === "visible") return resolve();
				log("tab in background, script paused");
				document.addEventListener("visibilitychange", () => {
					if (document.visibilityState === "visible") {
						log("script unpaused");
						return resolve();
					}
				});
			});
		}

		async function waitEle() {
			if (selector.length) {
				let target = document.querySelector(selector);
				if (!target && retry > 0) {
					retry--;
					log(`target not found, remaining retries [${retry}]`);
					setTimeout(() => waitEle(retry), delay);
				} else {
					await sleep(delay);
					run();
				}
			} else {
				throw Error("selector is empty");
			}
		}
	}


	async function main() {
		let
			pos = document.querySelector("#CardSelectTr"),
			table = pos.querySelector("tbody"),
			msg = document.createElement("div"),
			changeColor = (eles, color = "") => {
				eles.forEach(ele => ele.style.backgroundColor = color);
			},
			updateList = (list) => {
				document.querySelector("#wiki_tool_marked_count").textContent = list.size;
				document.querySelector("#wiki_tool_marked_list").value = [...list];
			},
			addCss = () => {
				let css = document.createElement("style");
				css.textContent = `
					.fleet_tech_tool_ship_icon {
						display: inline-block;
						border: 1px solid #a2a9b1;
						border-radius: 7px;
					}

					.fleet_tech_tool_ship_name {
						display: inline-block;
						width: calc(100% - 40px);
						/*justify-content: center;*/
						word-break: keep-all;
						vertical-align: middle;
					}
				`;
				document.head.appendChild(css);
			},
			data_srcipt = [];

		addCss();
		fixDataHeader();
		table.children.forEach(tr => {
			addSwitch([...tr.children], tr);
		});
		await checkIcon();

		msg.innerHTML = `
			<div style="display: inline-block;">
				<div style="display: inline-block; color: red;">艦隊科技工具</div>作用中,點擊艦船該行任意位置進行標記。
				已標記: <div id="wiki_tool_marked_count" style="display: inline-block;">${getValue(key.ship_id).length}</div>
			</div>
			<div>
				<button id="wiki_tool_edit" class="btn btn-default">編輯/查看列表</button>
				<div id="wiki_tool_box" style="display: none;">
					<textarea id="wiki_tool_marked_list"></textarea>
					<button id="wiki_tool_save" class="btn btn-default">儲存並更新</button>
				</div>
			</div>
		`;
		pos.insertAdjacentElement("beforebegin", msg);
		document.querySelector("#wiki_tool_edit").onclick = () => {
			let
				box = document.querySelector("#wiki_tool_box"),
				display = box.style.display;
			box.style.display = (display == "none") ? "" : "none";
			document.querySelector("#wiki_tool_marked_list").value = getValue(key.ship_id);
		};
		document.querySelector("#wiki_tool_save").onclick = () => {
			let
				_list = new Set(document.querySelector("#wiki_tool_marked_list").value.split(",")),
				_table = document.querySelector("#CardSelectTr>tbody");
			_list.delete('');
			log("載入清單", [..._list]);
			setValue(key.ship_id, [..._list]);
			_table.children.forEach(tr => {
				let
					_id = tr.children[0].textContent.trim(),
					_isInList = _list.has(_id);
				changeColor([...tr.children], _isInList ? bg_color : "");
			});
			updateList(_list);
		};

		log("載入清單", getValue(key.ship_id));
		loadIcon();

		function loadIcon() {
			let
				_getData = () => {
					let
						list = getValue(key.ship_icon),
						data = {};
					list.forEach(o => data[o.id] = o.icon);
					return data;
				},
				_data = _getData();

			log(`開始載入icon`);
			table.children.forEach(tr => {
				let
					_id = tr.children[0].textContent.trim(),
					_name = tr.children[1],
					_img = _name.querySelector("img"),
					_div = _name.querySelector("div");
				tr.style.padding = "0px";
				if (_img) _img.remove(); // remove old img if exist
				if (_data[_id]) {
					_img = document.createElement("img");
					_img.src = _data[_id];
					_img.className = "fleet_tech_tool_ship_icon";
					_name.insertAdjacentElement("afterbegin", _img);
					if (!_div) {
						_div = document.createElement("div");
						_div.className = "fleet_tech_tool_ship_name";
						_div.appendChild(_name.querySelector("a"));
						_name.insertAdjacentElement("beforeend", _div);
					}
				}
			});
		}

		async function checkIcon() {
			let icon_length = getValue(key.ship_icon).length;
			if (data_srcipt.length != icon_length) {
				//add update btn
				log(`檢查icon, 本頁${data_srcipt.length} != 已取得${icon_length}, 開始更新`);
				await iconHarvester();
			} else {
				log(`檢查icon, 本頁${data_srcipt.length} == 已取得${icon_length}, 不需更新`);
			}

			async function iconHarvester() {
				let
					url = "https://wiki.biligame.com/blhx/%E5%AE%9E%E8%A3%85%E6%97%A5%E6%9C%9F",
					parser = new DOMParser(),
					page = await fetch(url);
				if (page.status !== 200) {
					throw Error(`unable to get ${url}`);
				} else {
					let
						html_text = await page.text(),
						dom = parser.parseFromString(html_text, "text/html"),
						waitParser = new Promise((resolve) => { if (dom.readyState == "complete") resolve(true); }),
						data_icon = [],
						promise_list = [];
					log("wait dom");

					await waitParser.then(() => {
						log("dom loaded");
						let data_wiki = extractData(dom);
						data_wiki.forEach(o => {
							if (data_srcipt.find(id => id == o.id)) {
								data_icon.push({
									id: o.id,
									icon: o.icon,
								});
							}
						});
						log("更新完畢", { data_wiki, data_srcipt, data_icon });
					});

					log("開始轉換為快取");
					data_icon.forEach(data => {
						promise_list.push(
							fetchImageToDataURI(data.icon)
								.then(data_url => data.icon = data_url)
						);
					});

					await Promise.all(promise_list);
					log("轉換完畢");
					setValue(key.ship_icon, data_icon);

					return true;
				}

				async function fetchImageToDataURI(url = "", test = false) {
					let local = window.location.protocol == "file:" ? true : false;
					if (test || local) {
						return url; // can't fetch in local file
					} else {
						return fetch(url).then(r => {
							return r.blob();
						}).then(blob => {
							return blobToURL(blob);
						});
					}
					function blobToURL(blob) {
						return new Promise((resolve, reject) => {
							var fr = new FileReader();
							fr.onload = () => { resolve(fr.result); };
							fr.onerror = reject;
							fr.readAsDataURL(blob);
						});
					}
				}

				function extractData(dom) {
					let
						extractor = (ele) => {
							let _data = [],
								nor = (node) => node.textContent.trim().replace(/[\s·]/mg, "");
							ele.querySelector("tbody")
								.children
								.forEach((line, i) => {
									if (i > 0) {
										let img = line.querySelector("img");
										if (img) {
											_data.push({
												id: nor(line.children[0]),
												name: nor(line.children[1]),
												//date: nor(node.children[2]),
												icon: img.src,
											});
										}
									}
								});
							return _data;
						},
						list = [],
						target = dom.querySelector("#mw-content-text");
					//log([target]);
					target.querySelector("div[class=row]")
						.children
						.forEach(ele => list = list.concat(extractor(ele)));
					//log(JSON.stringify(list));
					//log(list);
					return list;
				}
			}
		}

		function fixDataHeader() {
			let head = document.querySelector("#CardSelectTr>thead");
			if (head.children.length == 1) {
				let bottom = document.querySelector("#CardSelectTr>tbody>tr");
				log("missing head, trying to fix it");
				if (!(bottom.innerHTML.match(/dataHeader headerSort/gm))) {
					throw Error(`element not found, abort\n${bottom.innerHTML}`);
				} else {
					head.appendChild(bottom);
					log("head fixed");
				}
			} else {
				log("normal datahead");
			}
		}

		function addSwitch(elelist, target) {
			target.onclick = () => checkList();
			checkList(true); // at start

			async function checkList(ini = false) {
				let
					addToList = async () => {
						_list.add(`${_id}`);
						setValue(key.ship_id, [..._list]);
					},
					removeFromList = async () => {
						_list.delete(`${_id}`);
						setValue(key.ship_id, [..._list]);
					},
					_list = new Set(getValue(key.ship_id)),
					_id = elelist[0].innerText.trim(),
					_name = elelist[1].innerText.trim(),
					_isInList = _list.has(_id);
				if (ini) data_srcipt.push(_id);
				if (_isInList) {
					if (!ini) {
						await removeFromList(_id);
						log(`remove ${_id} ${_name}, list size:${_list.size}`);
						changeColor(elelist);
						updateList(_list);
					} else {
						changeColor(elelist, bg_color);
					}
				} else {
					if (!ini) {
						await addToList(_id);
						log(`add ${_id} ${_name}, list size:${_list.size}`);
						changeColor(elelist, bg_color);
						updateList(_list);
					} else {
						changeColor(elelist);
					}
				}
			}
		}
	}
})();