您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
醒目IP、相似IP提示
// ==UserScript== // @name 巴友IP紀錄 // @description 醒目IP、相似IP提示 // @namespace https://smilin.net // @author smilin // @version 0.05 // @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")) return; //#region indexedDB const dbName = "smilinIpRecordDB"; const storeName = "smilinIpRecordStore"; 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-ip"; //#region dom 生成 const ipList = (localStor, userid, ip) => { // 創建 table 元素 const tableContainer = document.createElement("div"); const table1 = document.createElement("table"); const table2 = document.createElement("table"); createTableContainerStyle(tableContainer); createTableStyle(table1); createTableStyle(table2); tableContainer.appendChild(table1); tableContainer.appendChild(table2); // 創建 tbody 元素 const tbody = document.createElement("tbody"); table1.appendChild(tbody); // 創建標題行 const headerRow = document.createElement("tr"); toggleDataRows(headerRow); const headerNameCell = createTableCell("IP"); const headerDayCell = createTableCell("發文時間"); createThStyle(headerNameCell); createThStyle(headerDayCell); headerRow.appendChild(headerNameCell); headerRow.appendChild(headerDayCell); tbody.appendChild(headerRow); // 根據 localStor.data 創建表格的每一行 localStor[userid].data.forEach((element) => { const row = document.createElement("tr"); createTrStyle(row); const ipCell = createTableCell(element.ip); const dayCell = createTableCell(element.time); row.appendChild(ipCell); row.appendChild(dayCell); tbody.appendChild(row); }); // 相似ip const ipTbody = document.createElement("tbody"); table2.appendChild(ipTbody); const ipHeaderRow = document.createElement("tr"); toggleDataRows(ipHeaderRow); const ipHeaderNameCell = createTableCell("使用者名稱"); const ipHeaderIpCell = createTableCell("相似IP"); const ipHeaderTimeCell = createTableCell("發文時間"); createThStyle(ipHeaderNameCell); createThStyle(ipHeaderIpCell); createThStyle(ipHeaderTimeCell); ipHeaderRow.appendChild(ipHeaderNameCell); ipHeaderRow.appendChild(ipHeaderIpCell); ipHeaderRow.appendChild(ipHeaderTimeCell); ipTbody.appendChild(ipHeaderRow); localStor[userid].data.forEach((element) => { localStor[element.ip].data.forEach((element) => { if (element.userid === userid) return; const row = document.createElement("tr"); createTrStyle(row); const nameA = document.createElement("a"); nameA.href = `https://home.gamer.com.tw/profile/index.php?&owner=${element.userid}`; nameA.textContent = element.username; const nameCell = document.createElement("td"); nameCell.style.padding = "8px"; nameCell.appendChild(nameA); const ipCell = createTableCell(element.ip); const dayCell = createTableCell(element.time); row.appendChild(nameCell); row.appendChild(ipCell); row.appendChild(dayCell); ipTbody.appendChild(row); }); }); return tableContainer; }; function createTableCell(text) { const td = document.createElement("td"); td.textContent = text; createTdStyle(td); return td; } //#region table css function createTableContainerStyle(tableContainer) { tableContainer.className = "ip-list"; tableContainer.style.display = "none"; tableContainer.style.maxHeight = "300px"; tableContainer.style.overflowY = "auto"; tableContainer.style.backgroundColor = "#ccc"; tableContainer.style.padding = "10px"; tableContainer.style.borderRadius = "10px"; tableContainer.style.margin = "5px 0px 5px 0px"; tableContainer.style.color = "#464646"; tableContainer.style.flexDirection = "column"; } function createTableStyle(table) { table.style.width = "100%"; table.style.borderCollapse = "collapse"; } function createThStyle(th) { th.style.backgroundColor = "#118A9B"; th.style.color = "#fff"; } function createTdStyle(td) { td.style.padding = "12px"; td.style.borderBottom = "1px solid #ddd"; td.style.textAlign = "left"; } function createTrStyle(tr) { tr.addEventListener("mouseover", function () { tr.style.backgroundColor = "#f5f5f5"; }); tr.addEventListener("mouseout", function () { tr.style.backgroundColor = ""; }); } //#endregion //#region table js // 折疊 td 行 function toggleDataRows(e) { e.onclick = function () { const table = e.closest("table"); const allTr = table.querySelectorAll("tr"); allTr.forEach(function (tr) { if (tr === e) return; tr.style.display = tr.style.display === "none" || tr.style.display === "" ? "table-row" : "none"; }); }; } //#endregion const clickButton = (localStor, userid, ip) => { const button = document.createElement("button"); button.type = "button"; button.className = "usertitle"; button.setAttribute("isshow", "false"); button.style.borderWidth = "0px"; button.style.padding = "2.5px 6px"; button.style.margin = "0px 5px"; // 未讀高亮 if (!localStor[userid].isRead || !localStor[ip].isRead) { notReadStyle(button); } button.onclick = function () { showMessage(this); // 已讀關閉高亮 userDataIsReal(localStor, userid, ip); isReadStyle(button); }; button.textContent = ip; return button; }; const mainDiv = (localStor, userid, ip) => { const listed = ipList(localStor, userid, ip); const buttoned = clickButton(localStor, userid, ip); const div = document.createElement("div"); div.className = "ip-list-main-div"; div.style.position = "relative"; div.style.display = "inline"; div.appendChild(buttoned); div.appendChild(listed); return div; }; //#endregion //#region 一些 DOM 模組化的效果 // type 0 = 發文 1 = 留言 function notReadStyle(element) { element.style.borderWidth = "1px"; element.style.borderColor = "#e66465 #9198e5 #9198e5 #e66465"; } function isReadStyle(element) { element.style.borderWidth = "0px"; } //#endregion //#region 資料 API function initDataStruct(userid, username, ip, time) { return { isRead: true, // 已讀 noteName: "", // 備用欄位 lastUpdated: new Date().toISOString().split("T")[0], // 最後更新時間 備用 data: [ { ip: ip, // IP time: time, // IP 紀錄時間 userid: userid, // 使用者id username: username, // 使用者名稱 }, ], }; } async function initUser(userid, username, ip, time, localStor) { const dataStruct = initDataStruct(userid, username, ip, time); localStor = { ...localStor, [userid]: dataStruct, }; await setItem(localStorageName, localStor); return localStor; } async function initIp(userid, username, ip, time, localStor) { const dataStruct = initDataStruct(userid, username, ip, time); localStor = { ...localStor, [ip]: dataStruct, }; await setItem(localStorageName, localStor); return localStor; } async function addUsername(userid, username, ip, time, localStor) { localStor[userid].lastUpdated = new Date().toISOString().split("T")[0]; // localStor[userid].isRead = true; localStor[userid].data.push({ ip: ip, // IP time: time, // IP 紀錄時間 userid: userid, // 使用者id username: username, // 使用者名稱 }); await setItem(localStorageName, localStor); return localStor; } async function addIp(userid, username, ip, time, localStor) { localStor[ip].lastUpdated = new Date().toISOString().split("T")[0]; localStor[ip].isRead = false; localStor[ip].data.push({ ip: ip, // IP time: time, // IP 紀錄時間 userid: userid, // 使用者id username: username, // 使用者名稱 }); await setItem(localStorageName, localStor); return localStor; } async function checkLocalStor(userid, username, ip, time, localStor) { let isUserUse = false; let isIpUse = false; localStor[userid].data.forEach((element) => { if (element.ip === ip) isUserUse = true; }); localStor[ip].data.forEach((element) => { if (element.userid === userid) isIpUse = true; }); if (!isUserUse) localStor = await addUsername(userid, username, ip, time, localStor); if (!isIpUse) localStor = await addIp(userid, username, ip, time, localStor); return localStor; } function userDataIsReal(localStor, userid, ip) { localStor[userid].isRead = true; localStor[ip].isRead = true; setItem(localStorageName, localStor); return localStor; } async function searchLocalStor(userid, username, ip, time) { let isOld = true; let localStor = await getItem(localStorageName); if (!localStor) { isOld = false; localStor = await initUser(userid, username, ip, time, localStor); localStor = await initIp(userid, username, ip, time, localStor); } if (localStor[userid] === undefined) { localStor = await initUser(userid, username, ip, time, localStor); } if (localStor[ip] === undefined) { localStor = await initIp(userid, username, ip, time, localStor); } if (isOld) localStor = await checkLocalStor(userid, username, ip, time, localStor); return localStor; } //#endregion //#region dom 渲染 async function render() { const postDom = document.querySelectorAll(".c-post__header"); await renderPostHeader(postDom); } async function renderPostHeader(dom) { for (let d of dom) { // 0.05 投票防呆 const isFloor = d.querySelector(".c-post__header__author"); if (!isFloor) continue; const userid = d.querySelector(".userid").textContent.trim(); const username = d.querySelector(".username").textContent.trim(); const ip = d.querySelector(".edittime").getAttribute("data-hideip"); const time = d.querySelector(".edittime").getAttribute("data-mtime"); const localStor = await searchLocalStor(userid, username, ip, time); d.querySelector(".c-post__header__author").appendChild( mainDiv(localStor, userid, ip) ); } } //#endregion // ip 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(); })();