巴友暱稱紀錄

對天尊特攻寶具

安装此脚本?
作者推荐脚本

您可能也喜欢巴友IP紀錄

安装此脚本
// ==UserScript==
// @name         巴友暱稱紀錄
// @description  對天尊特攻寶具
// @namespace    https://smilin.net
// @author       smilin
// @version      0.10
// @license MIT
// @homepage     https://home.gamer.com.tw/homeindex.php?owner=a33073307
// @match        https://forum.gamer.com.tw/C.php*
// @match        https://forum.gamer.com.tw/Co.php*
// @icon         https://forum.gamer.com.tw/favicon.ico
// @grant        none
// ==/UserScript==

(function () {
	if (typeof indexedDB === "undefined") return;
	if (!document.querySelector(".c-post__header__author")) return;

	//#region indexedDB
	const dbName = "nameRecordDB";
	const storeName = "nameRecordStore";
	const dbVersion = 1;

	function openDB() {
		return new Promise((resolve, reject) => {
			const openRequest = indexedDB.open(dbName, dbVersion);
			openRequest.onerror = function (event) {
				reject("Error opening DB");
			};
			openRequest.onsuccess = function (event) {
				resolve(event.target.result);
			};
			openRequest.onupgradeneeded = function (event) {
				const db = event.target.result;
				if (!db.objectStoreNames.contains(storeName)) {
					db.createObjectStore(storeName, { keyPath: "id" });
				}
			};
		});
	}

	async function setItem(key, value) {
		const db = await openDB();
		const transaction = db.transaction(storeName, "readwrite");
		const store = transaction.objectStore(storeName);
		return new Promise((resolve, reject) => {
			const request = store.put({ id: key, value: value });
			request.onsuccess = function () {
				resolve();
			};
			request.onerror = function (event) {
				reject("Error storing data");
			};
		});
	}

	async function getItem(key) {
		const db = await openDB();
		const transaction = db.transaction(storeName, "readonly");
		const store = transaction.objectStore(storeName);
		return new Promise((resolve, reject) => {
			const request = store.get(key);
			request.onsuccess = function (event) {
				resolve(event.target.result ? event.target.result.value : null);
			};
			request.onerror = function (event) {
				reject("Error fetching data");
			};
		});
	}
	//#endregion

	const localStorageName = "record-name";

	//#region DOM 生成
	function nameList(localStor, userid) {
		// 創建 table 元素
		const table = document.createElement("table");
		table.className = "name-list themepage-entrance__preview";
		table.style.whiteSpace = "nowrap";
		table.style.display = "none";

		// 創建 tbody 元素
		const tbody = document.createElement("tbody");
		table.appendChild(tbody);

		// 創建標題行
		const headerRow = document.createElement("tr");
		const headerNameCell = createTableCell("名字");
		const headerDayCell = createTableCell("發現時間(本地)");
		headerRow.appendChild(headerNameCell);
		headerRow.appendChild(headerDayCell);
		tbody.appendChild(headerRow);

		// 根據 localStor.data 創建表格的每一行
		localStor[userid].data.forEach((element) => {
			const row = document.createElement("tr");
			const nameCell = createTableCell(element.name);
			const dayCell = createTableCell(element.day);
			row.appendChild(nameCell);
			row.appendChild(dayCell);
			tbody.appendChild(row);
		});

		return table;
	}

	function createTableCell(text) {
		const td = document.createElement("td");
		td.textContent = text;

		// 設置 td 的 padding
		td.style.padding = "8px";

		return td;
	}

	function clickButton(localStor, userid) {
		const button = document.createElement("button");
		button.type = "button";
		button.className = "usertitle";
		button.setAttribute("isshow", "false");
		button.style.borderWidth = "0px";
		button.style.padding = "1px 6px";
		// 未讀高亮
		if (!localStor[userid].isRead) {
			notReadStyle(button, 0);
		}
		button.onclick = function () {
			showMessage(this);
			// 已讀關閉高亮
			userDataIsReal(localStor, userid);
			isReadStyle(button, 0);
		};
		button.textContent = "歷史紀錄";
		return button;
	}

	const mainDiv = (localStor, userid) => {
		const names = nameList(localStor, userid);
		const buttoned = clickButton(localStor, userid);
		const div = document.createElement("div");
		div.className = "name-list-main-div";
		div.style.position = "relative";
		div.style.display = "inline";

		div.appendChild(buttoned);
		div.appendChild(names);

		return div;
	};

	const replyDiv = (localStor, userid, contentUser) => {
		const div = document.createElement("div");
		div.className = "name-list-reply-div";
		div.style.position = "relative";
		div.style.display = "inline";

		const readText = document.createElement("span");

		// 未讀高亮
		if (!localStor[userid].isRead) notReadStyle(readText, 1);

		const names = nameList(localStor, userid);
		setHoverShow(contentUser, names, () => {
			// 已讀關閉高亮
			userDataIsReal(localStor, userid);
			isReadStyle(readText, 1);
		});

		div.appendChild(readText);
		div.appendChild(names);

		return div;
	};
	//#endregion

	//#region 一些 DOM 模組化的效果
	function setHoverShow(triggerElement, targetElement, callback = () => {}) {
		// 初始隱藏 target 元素
		targetElement.style.display = "none";

		// 當 trigger 元素被滑過時,顯示 target 元素
		triggerElement.addEventListener("mouseover", function () {
			targetElement.style.display = "block";
			callback();
		});

		// 當滑鼠離開 trigger 元素時,隱藏 target 元素
		triggerElement.addEventListener("mouseout", function () {
			targetElement.style.display = "none";
		});
	}

	// type 0 = 發文 1 = 留言
	function notReadStyle(element, type = 0) {
		if (type === 0) {
			element.style.borderWidth = "1px";
			element.style.borderColor = "#e66465 #9198e5 #9198e5 #e66465";
		} else if (type === 1) {
			element.textContent = "???";
			element.style.paddingLeft = "4px";
			element.style.color = "red";
			element.style.background =
				"-webkit-linear-gradient(45deg, rgb(253,18,226) 20%, rgb(164,67,221), rgb(84,126,255) 90% )";
			element.style.webkitBackgroundClip = "text";
			element.style.webkitTextFillColor = "transparent";
		}
	}

	function isReadStyle(element, type = 0) {
		if (type === 0) {
			element.style.borderWidth = "0px";
		} else if (type === 1) {
			element.innerText = "";
			element.style.paddingLeft = "0px";
		}
	}
	//#endregion

	//#region 資料管理 api
	function initUser(userid, username, localStor) {
		localStor = {
			...localStor,
			[userid]: {
				userid: userid,
				lastUpdated: null,
				isRead: true,
				noteName: "",
				data: [
					{
						name: username,
						day: new Date().toISOString().split("T")[0],
					},
				],
			},
		};
		setItem(localStorageName, localStor);
		return localStor;
	}

	function addUsername(userid, username, localStor) {
		localStor[userid].lastUpdated = new Date().toISOString().split("T")[0];
		localStor[userid].isRead = false;
		localStor[userid].data.push({
			name: username,
			day: new Date().toISOString().split("T")[0],
		});
		setItem(localStorageName, localStor);
		return localStor;
	}

	function userDataIsReal(localStor, userid) {
		localStor[userid].isRead = true;
		setItem(localStorageName, localStor);
		return localStor;
	}

	function checkLocalStor(userid, username, localStor) {
		let isUse = false;
		localStor[userid].data.forEach((element) => {
			if (element.name === username) isUse = true;
		});
		if (!isUse) {
			localStor = addUsername(userid, username, localStor);
		}
		return localStor;
	}

	async function searchUsername(userid, username) {
		let localStor = await getItem(localStorageName);
		if (!localStor || localStor[userid] === undefined) {
			localStor = initUser(userid, username, localStor);
		} else {
			localStor = checkLocalStor(userid, username, localStor);
		}
		return localStor;
	}
	//#endregion

	//#region dom 渲染
	async function render() {
		const postDom = document.querySelectorAll(".c-post__header__author");
		const replyDom = document.querySelectorAll(".reply-content");
		await renderPostHeader(postDom);
		await renderReplyContent(replyDom);
		listenerAllMoreReplyButton();
	}

	// 渲染發文者
	async function renderPostHeader(dom) {
		for (let d of dom) {
			const userid = d.querySelector(".userid").textContent;
			const username = d.querySelector(".username").textContent.trim();
			const localStor = await searchUsername(userid, username);
			d.appendChild(mainDiv(localStor, userid));
		}
	}

	// 渲染回文者
	async function renderReplyContent(dom) {
		for (let d of dom) {
			const contentUser = d.querySelector(".reply-content__user");
			// const contentFooter = d.querySelector(".reply-content__footer");
			const match = contentUser.href.match(/home.gamer.com.tw\/([^\/]+)/);
			const userid = match ? match[1] : null;
			if (!userid) continue;
			const username = contentUser.textContent.trim();
			const localStor = await searchUsername(userid, username);
			contentUser.appendChild(replyDiv(localStor, userid, contentUser));
			d.style.overflow = "unset";
			d.setAttribute("data-modified", "true");
		}
	}
	//#endregion

	//#region 按鍵監聽 & DOM 渲染
	function listenerAllMoreReplyButton() {
		let timeoutID = null;
		const moreButtons = document.querySelectorAll(".more-reply");
		const hideButtons = document.querySelectorAll(".hide-reply");
		for (let button of moreButtons) {
			button.addEventListener("click", function () {
				clearTimeout(timeoutID);
				timeoutID = setTimeout(() => {
					// 獲取所有未被標記過的元素
					const dom = document.querySelectorAll(
						".reply-content:not([data-modified])"
					);
					renderReplyContent(dom);
				}, 3000);
			});
		}
		for (let button of hideButtons) {
			button.addEventListener("click", function () {
				clearTimeout(timeoutID);
				// 折疊後 data-modified 還在,功能卻要重設,太麻煩了
				// 太麻煩了,先清除 timeout 就好...
			});
		}
	}
	//#endregion

	// 歷史紀錄 click
	const showMessage = function showMessage(element) {
		const nextElement = element.nextElementSibling;
		if (element.getAttribute("isshow") === "true") {
			nextElement.style.display = "none";
			element.setAttribute("isshow", "false");
		} else {
			nextElement.style.display = "flex";
			element.setAttribute("isshow", "true");
		}
	};

	render();
})();