巴友暱稱紀錄

對天尊特攻寶具

安裝腳本?
作者推薦腳本

您可能也會喜歡 巴友IP紀錄

安裝腳本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
})();