futaba ID+IP popup

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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
	}

})();