futaba ID+IP popup

同じIDやIPのレスをポップアップしちゃう

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           futaba ID+IP popup
// @namespace      https://github.com/himuro-majika
// @description    同じIDやIPのレスをポップアップしちゃう
// @author         himuro_majika
// @include        http://*.2chan.net/*/res/*.htm
// @include        https://*.2chan.net/*/res/*.htm
// @version        1.3.1
// @license        MIT
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAPUExURYv4i2PQYy2aLUe0R////zorx9oAAAAFdFJOU/////8A+7YOUwAAAElJREFUeNqUj1EOwDAIQoHn/c88bX+2fq0kRsAoUXVAfwzCttWsDWzw0kNVWd2tZ5K9gqmMZB8libt4pSg6YlO3RnTzyxePAAMAzqMDgTX8hYYAAAAASUVORK5CYII=
// ==/UserScript==
(function() {
	'use strict';

	const USE_COUNTER = true;	// IDカウンターを表示する
	const USE_COUNTER_CURRENT = true;	// IDカウンターに現在の出現数を表示
	const scriptName = "GM_FIP";

	let Start = new Date().getTime();//count parsing time
	let saba = location.host.replace(".2chan.net","") + location.pathname.replace("futaba.htm","");
	let timer_show, timer_hide;
	let isIDIPThread = false;
	let isImg = false;

	init();
	console.log(scriptName + " Parsing " + saba + ": " + ((new Date()).getTime() - Start) + "msec");//log parsing time

	function init() {
		checkIsImg();
		checkThreadMail();
		setClassAndNameThread();
		createCounter();
		observeInserted();
	}

	//img鯖
	function checkIsImg() {
		isImg = document.querySelector(".cnm") === null;
	}

	// ID表示・IP表示スレかどうか
	function checkThreadMail() {
		let mail = !isImg ? document.querySelector(".cnm a") : document.querySelector(".thre .cnw a");
		isIDIPThread = mail !== null && mail.href.match(/^mailto:i[dp]/i) !== null;
	}

	// ID/IPにclass,nameを設定する
	// 本文
	function setClassAndNameThread() {
		let cnw = document.querySelectorAll(".thre .cnw");
		if (!cnw) return;
		setClassAndName(cnw);
	}
	// レス
	function setClassAndNameRes(node) {
		let resIdNode = arguments.length ? node.querySelectorAll(".rtd .cnw") : document.querySelectorAll(".rtd .cnw");
		if (!resIdNode) return;
		setClassAndName(resIdNode);
	}

	function setClassAndName(node) {
		if(!node.length) return;

		node.forEach((item) => {
			if (item.querySelector(".GM_fip_name")) return;
			let matchText = item.textContent.match(/(.+)(I[DP]:\S+)/);
			if (!matchText) return;
			let dateEle = document.createTextNode(matchText[1]);
			let mailEle = item.querySelector("a");
			let idText = matchText[2];
			let idEle = document.createElement("a");
			idEle.textContent = idText;
			idEle.classList.add("GM_fip_name");
			idEle.setAttribute("name", idText);
			idEle.style.color = "#F00";
			idEle.addEventListener("mouseover", openPopup, true);
			idEle.addEventListener("mouseout", closePopup, true);
			item.textContent = "";
			if (mailEle) {
				item.appendChild(mailEle);	//imgメ欄
			} else {
				item.appendChild(dateEle);
			}
			item.appendChild(idEle);
		})
	}
	// 出現数の表示
	function createCounter() {
		if (!USE_COUNTER) return;
		let a = document.querySelectorAll(".thre .GM_fip_name");
		let ids = {};
		for (let i = 0; i < a.length; i++) {
			let node = a[i];
			let id = node.name;
			if (USE_COUNTER_CURRENT) {
				if (ids[id]) {
					ids[id]++;
				} else {
					ids[id] = 1;
				}
			}
			let name = document.querySelectorAll(".thre [name='" + id + "']");
			let span;
			if (node.childNodes[1]) {
				span = node.childNodes[1];
			} else {
				span = document.createElement("span");
				span.classList.add("GM_fip_counter");
				span.style.margin = "0 5px";
				node.appendChild(span);
			}
			if (USE_COUNTER_CURRENT) {
				span.textContent = "[" + ids[id] + "/" + name.length + "]";
			} else {
				span.textContent = "[" + name.length + "]";
			}
		}
	}
	// ポップアップを表示する
	function openPopup(event) {
		clearTimeout(timer_show);
		delPopup();
		timer_show = setTimeout(() => {
			let name = this.name;
			let popup = makePopupContainer();
			let divThread = document.createElement("div");
			let table = document.createElement("table");
			let tbody = document.createElement("tbody");
			let tda = document.querySelectorAll(".thre [name='" + name + "']");
			for (let i = 0; i < tda.length; i++) {
				if (tda[i].parentNode.parentNode.className === "thre") {
					// スレ
					let form;
					if (document.querySelector(".cnw")) {
						form = tda[i].parentNode.parentNode.cloneNode(true);
					} else {
						form = tda[i].parentNode.cloneNode(true);
					}
					for (let j = 0; j < form.childNodes.length; j++) {
						// 広告
						if (form.childNodes[j].className !== "tue") {
							divThread.appendChild(form.childNodes[j].cloneNode(true));
						}
						if (form.childNodes[j].tagName == "BLOCKQUOTE") {
							break;
						}
					}
				} else {
					// レス
					let tr;
					if (document.querySelector(".cnw")) {
						tr = tda[i].parentNode.parentNode.parentNode.cloneNode(true);
					} else {
						tr = tda[i].parentNode.parentNode.cloneNode(true);
					}
					setQtJump(tr);
					tbody.appendChild(tr);
				}
			}
			table.appendChild(tbody);
			popup.appendChild(divThread);
			popup.appendChild(table);
			if (checkAkahukuEnabled()) {
				let bq = popup.querySelectorAll("blockquote");
				bq.forEach(q => {
					let td = q.parentNode;
					let gtdiv = document.createElement("div");
					gtdiv.style.maxWidth = "800px";
					gtdiv.classList.add("akahuku_popup_content_blockquote");
					gtdiv.innerHTML = q.innerHTML;
					q.remove();
					td.appendChild(gtdiv);
				})
			}
			// this.parentNode.appendChild(popup);
			document.querySelector("html body").appendChild(popup);
			document.querySelectorAll("#GM_fip_pop .GM_fip_name").forEach((item) => {
				item.className = ("GM_fip_name_pop");
			})
			let wX = this.getBoundingClientRect().left + window.scrollX;	//ポップアップ表示位置X
			let wY = this.getBoundingClientRect().top + window.scrollY - popup.clientHeight;	//ポップアップ表示位置Y
			if ( wY < 0 ) {	//ポップアップが上に見きれる時は下に表示
				wY = this.getBoundingClientRect().bottom + window.scrollY
			}
			popup.style.top = wY + "px";
			popup.style.left = wX + "px";
		}, 100);
		//ポップアップ内レス番号クリックでジャンプ
		function setQtJump(qt) {
			let rsc = qt.querySelector(".rsc");
			rsc.classList.add("qtjmp");
			let jumpid = rsc.id;
			rsc.removeAttribute("id");
			rsc.addEventListener("click", () => {
				let jumptarget = document.getElementById(jumpid).parentNode;
				window.scroll(0, jumptarget.getBoundingClientRect().top + window.pageYOffset);
				delPopup();
			});
		}
	}

	function makePopupContainer() {
		let container = document.createElement("div");
		container.id = "GM_fip_pop";
		container.classList.add("GM_fip_pop");
		container.style.position = "absolute";
		container.style.zIndex = 350;
		container.style.backgroundColor = "#eeaa88";
		container.style.fontSize = "0.85em";
		container.addEventListener("mouseover",() => {
			clearTimeout(timer_hide);
		}, true);
		container.addEventListener("mouseout",closePopup,true);

		return container;
	}
	// ポップアップを消す
	function closePopup() {
		clearTimeout(timer_show);
		clearTimeout(timer_hide);
		timer_hide = setTimeout(delPopup, 300);
	}

	function delPopup() {
		clearTimeout(timer_hide);
		let doc_pop = document.getElementsByClassName("GM_fip_pop");
		if ( doc_pop ) {
			for (let i = 0; i < doc_pop.length; i++) {
				doc_pop[i].remove();
			}
		}
	}
	// 続きを読むで追加されるレスを監視
	function observeInserted() {
		let target = document.querySelector(".thre");
		let timer_reload;
		let observer = new MutationObserver(function(mutations) {
			mutations.forEach((mutation) => {
				if (!mutation.addedNodes.length) return;
				let nodes = mutation.addedNodes[0];
				if (nodes.tagName == "TABLE" && nodes.id !== "akahuku_bottom_container") {
					setClassAndNameRes(nodes);
					clearTimeout(timer_reload);
					timer_reload = setTimeout(rel, 50);
				}
			});
		});
		observer.observe(target, { childList: true });
		function rel() {
			if (!isIDIPThread) {
				setClassAndNameThread();
				setClassAndNameRes();
			}
			createCounter();
		}
	}

	function checkAkahukuEnabled() {
		return document.getElementById("akahuku_postform") != null
	}

})();