您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
複数コメントしてるユーザーを簡単に見分けられます
// ==UserScript== // @name 静画5ch風コメント // @namespace http://tampermonkey.net/ // @version 0.12 // @description 複数コメントしてるユーザーを簡単に見分けられます // @author cbxm // @match https://seiga.nicovideo.jp/seiga/* // @grant GM.xmlHttpRequest // @run-at document-start // @license MIT // ==/UserScript== //汎用関数 class Util { static WaitDocumentElement() { return new Promise(r => { if (document.documentElement != null) { return r(); } window.addEventListener("DOMContentLoaded", () => { return r(); }); }); } //https://stackoverflow.com/questions/69368851/type-safe-way-of-narrowing-type-of-arrays-by-length-when-nouncheckedindexedacces //TypeScriptくんを納得させるための関数 static HasLengthAtLeast(arr, len) { return arr != null && arr.length >= len; } //TypeScriptくんを納得させるための関数 static IsLength(arr, len) { return arr != null && arr.length == len; } //xmlString=未探査部分 static XmlToObj(xmlString) { const F = (xmlString, obj) => { //タグを抜き出す let tagMatchs = null; while (true) { tagMatchs = xmlString.match(/<([^>]+)>/); //タグを探す //タグがないということはそれが値になる if (tagMatchs == null) { return xmlString; } if (tagMatchs[1]?.[tagMatchs[1].length - 1] == "/") { xmlString = xmlString.replace(/<[^>]+>([^]*)/, "$1"); } else { break; } } if (!Util.HasLengthAtLeast(tagMatchs, 2)) { return xmlString; } const tag = tagMatchs[1]; //タグの内側とその先を抜き出す const matchChildlen = []; while (true) { const matchs = xmlString.match(new RegExp(`^[^<]*<${tag}>([^]+?)<\/${tag}>([^]*)`)); if (matchs == null || !Util.HasLengthAtLeast(matchs, 3)) { break; } matchChildlen.push(matchs[1]); xmlString = matchs[2]; } //タグあったのにマッチしなかったおかしい if (matchChildlen.length == 0) { return obj; } //そのタグが一つしかないとき、オブジェクトになる if (Util.IsLength(matchChildlen, 1)) { //子を探す obj[tag] = F(matchChildlen[0], {}); } //そのタグが複数あるとき、配列になる if (matchChildlen.length > 1) { obj = []; for (let i = 0; i < matchChildlen.length; i++) { //子を探す obj[i] = F(matchChildlen[i], {}); } } //兄弟を探す F(xmlString, obj); return obj; }; //初期化で<xml>を取り除く xmlString = xmlString.replace(/\s*<[^>]+>([^]+)/, "$1"); return F(xmlString, {}); } static HtmlToDocument(str) { const parser = new DOMParser(); return parser.parseFromString(str, "text/html"); } static HtmlToChildNodes(str) { return this.HtmlToDocument(str).body.childNodes; } static HtmlToElement(str) { return this.HtmlToDocument(str).body.firstElementChild; } static HtmlToSVG(s) { var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); div.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg">' + s + '</svg>'; var frag = document.createDocumentFragment(); while (div.firstChild?.firstChild) frag.appendChild(div.firstChild.firstChild); return frag; } static Wait(ms) { return new Promise(r => setTimeout(() => r(null), ms)); } static WithTimeOut(p, ms) { return Promise.race([p, this.Wait(ms)]); } static async Retry(p, retryCount, wait, predicate) { for (let i = 0; i < retryCount; i++) { const result = await p(); if (predicate(result)) { return result; } //console.log("wait..."); await Util.Wait(wait); } return null; } static async Download(url, name) { const link = document.createElement("a"); document.body.appendChild(link); link.download = name; link.href = url; link.click(); //すぐに消すと反応しないとか await this.Wait(100); document.body.removeChild(link); } static GMFetchText(url, optopns) { //console.log(url); return new Promise(r => { GM.xmlHttpRequest({ url: url, ...optopns, onload: (response) => { r(response.responseText); }, onerror: (e) => { console.error(e); r(""); } }); }); } static Unique(array) { return Array.from(new Set(array)); } static IsElementInViewport(el) { var rect = el.getBoundingClientRect(); return (rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth)); } static SetCookie(key, value, option = "") { window.document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(value) + "; " + option; } static SetCookieKeyAndValue(keyAndValue) { window.document.cookie = keyAndValue; } static GetAllCookie() { const cookies = window.document.cookie.split(";").map(c => decodeURI(c).replace(/^\s/, "")); let keyVal = []; for (let i = 0; i < cookies.length; i++) { //cookies[i]=cookies[i].replace(/^\s/,""); ??? const s = cookies[i].match(/^(.*?)=(.*?)$/); if (Util.HasLengthAtLeast(s, 3)) { keyVal.push({ key: s[1], val: s[2] }); } } return keyVal; } static GetCookie(key) { const cookies = window.document.cookie.split(";").map(c => decodeURI(c).replace(/^\s/, "")); for (var c of cookies) { if (RegExp(key).test(c)) { return c; } } return null; } static GetCookieVal(key) { const cookies = window.document.cookie.split(";").map(c => decodeURI(c).replace(/^\s/, "")); for (var c of cookies) { const matched = c.match(`${key}=([^]*)`); if (Util.HasLengthAtLeast(matched, 2)) { return matched[1]; } } return null; } static DeleteCookie(key) { window.document.cookie = encodeURI(key) + "=; max-age=0"; } } ; class MicroDOMTask { constructor() { this.isRun = false; this.observer = null; this.tasks = []; this.element = null; } AddTask(task) { this.tasks.push(task); } Observe(element, callback) { this.element = element; if (this.observer == null) { this.observer = new IntersectionObserver((entrys, o) => { for (const e of entrys) { if (e.isIntersecting) { callback(); o.disconnect(); return; } } }); } this.observer.observe(element); } Start() { if (this.element instanceof HTMLElement && this.element.hidden) { return false; } if (!this.isRun) { this.isRun = true; this.observer?.disconnect(); for (const t of this.tasks) { t(); } return true; } return false; } } class BigDOMTask { constructor() { this.taskList = []; } AddMicroTask(task, observeElement) { if (observeElement != undefined) { //task.Observe(observeElement, () => this.taskList.unshift(task)); //配列の先頭に追加 task.Observe(observeElement, () => { this.taskList.unshift(null); requestAnimationFrame(() => task.Start()); }); //次のでやる //task.Observe(observeElement, () => task.Start()); //すぐやる //それぞれ挙動が違って面白い・・・いや面白くはない! } this.taskList.push(task); } Start() { if (this.taskList.length > 0) { requestAnimationFrame(n => this.ProcessTask.call(this, n)); } } ProcessTask(taskStartTime) { while (true) { const t = this.taskList.shift(); if (t == undefined) { break; } if (t.Start()) { if (performance.now() - taskStartTime > 1) { break; } } } if (this.taskList.length > 0) { requestAnimationFrame(n => this.ProcessTask.call(this, n)); } } } //2分探索して検索、挿入ができる class SortedArray extends Array { constructor(array) { super(...(array == undefined) ? [] : Array.isArray(array) ? array : [array]); this.bsNearestCache = { cacheId: -1, nearest: -1 }; if (Array.isArray(array) && !this.isSorted()) { this.sortOrder(); } } isSorted() { for (let i = 0; i < this.length - 1; i++) { if (this[i].id > this[i + 1].id) { return false; } } return true; } sortOrder() { Array.prototype.sort.call(this, (a, b) => a.id - b.id); //昇順 } //破壊的ソートさせない sort(compareFn) { if (compareFn != undefined) { console.warn("ソートさせないぞ"); } this.sortOrder(); return this; } //非破壊のこっちでやってね! sortNonDestructive(compareFn) { return Array.from(this).sort(compareFn); } binarySearchNearest(id) { if (this.bsNearestCache.cacheId == id && this.bsNearestCache.nearest != -1) { return this.bsNearestCache.nearest; } let lower = 0; let upper = this.length; while (lower < upper) { const mid = Math.floor(lower + (upper - lower) / 2); if (this[mid].id < id) { lower = mid + 1; } else { upper = mid; } } this.bsNearestCache.cacheId = id; this.bsNearestCache.nearest = lower; return lower; } binarySearchIndex(id) { const nearest = this.binarySearchNearest(id); if (nearest in this && this[nearest].id == id) { return nearest; } else { return -1; } } binarySearch(id) { const index = this.binarySearchIndex(id); if (index == -1) { return undefined; } else { return this[index]; } } // 上書き・挿入 insert(value) { const nearest = this.binarySearchNearest(value.id); if (nearest in this && this[nearest].id == value.id) { this[nearest] = value; } else { this.splice(nearest, 0, value); } return this[nearest]; } toJSON() { return [...this]; } //最初突合せてダメなら検索して見つかるかを返す。1つでも見つからないとfalse IsMatingOk(ids) { let i = 0; //正引きで突合せていく for (; i < ids.length; i++) { if (ids[i] != this[i]?.id) { break; } } for (; i < ids.length; i++) { if (this.binarySearch(ids[i]) == undefined) { break; } } return i == ids.length; } //最初突合せてダメなら検索した結果の配列を返す。1つでも見つからないとnull GetMatingList(ids) { const list = []; let i = 0; //正引きで突合せていく for (; i < ids.length; i++) { if (ids[i] != this[i]?.id) { break; } list.push(this[i]); } for (; i < ids.length; i++) { const t = this.binarySearch(ids[i]); if (t == undefined) { break; } list.push(t); } return i == ids.length ? list : null; } } ; ; //MutationObserver使った便利関数 class Observer { static Wait(predicate, parent = document, option = null) { return new Promise(r => { if (option == null) { option = {}; } if (option.childList == undefined && option.attributes == undefined && option.characterData == undefined) { option.childList = true; option.subtree = true; } const mutationObserver = new MutationObserver((mrs) => { if (predicate(mrs)) { mutationObserver.disconnect(); r(mrs); return; } }); mutationObserver.observe(parent, option); }); } ; static WaitAddedNodes(predicate, parent, option = null) { return new Promise(r => { if (option == null) { option = {}; } if (option.childList == undefined && option.attributes == undefined && option.characterData == undefined) { option.childList = true; option.subtree = true; } const mutationObserver = new MutationObserver(async (mrs) => { //console.log(document.head.innerHTML); //console.log(document.body.innerHTML); const result = []; for (let node of mrs) { //console.log(added); for (let i = 0; i < node.addedNodes.length; i++) { result.push(...await predicate(node.addedNodes[i])); } } if (result.length != 0) { mutationObserver.disconnect(); r(result); return; } }); mutationObserver.observe(parent, option); }); } ; static WaitAddedNode(predicate, parent, option = null) { return new Promise(r => { if (option == null) { option = {}; } if (option.childList == undefined && option.attributes == undefined && option.characterData == undefined) { option.childList = true; option.subtree = true; } if (option.childList == undefined && option.attributes == undefined && option.characterData == undefined) { option.childList = true; option.subtree = true; } const mutationObserver = new MutationObserver(async (mrs) => { //console.log(document.head.innerHTML); //console.log(document.body.innerHTML); for (let node of mrs) { //console.log(added); for (let i = 0; i < node.addedNodes.length; i++) { const ret = await predicate(node.addedNodes[i]); if (ret != null) { mutationObserver.disconnect(); r(ret); return; } } } }); mutationObserver.observe(parent, option); }); } ; static async DefinitelyGetElementById(id, parent = document.documentElement, option = null) { if (!(option?.doNotNormalCheck ?? false)) { const e = document.getElementById(id); if (e != null) { return e; } } return this.WaitAddedNode(e => (e instanceof Element && e.id == id) ? e : null, parent, option); } //getElementsByClassNameをつかうけど単体 static async DefinitelyGetElementByClassName(className, parent = document.documentElement, option = null) { if (!(option?.doNotNormalCheck ?? false)) { const e = parent.getElementsByClassName(className)[0]; if (e != null) { return e; } } return this.WaitAddedNode(e => { if (e instanceof Element) { if (e.classList.contains(className)) { return e; } if (option?.isDeepSearch ?? false) { const c = e.getElementsByClassName(className); if (c.length != 0) { return c[0]; } } } return null; }, parent, option); } //getElementsByTagNameをつかうけど単体 static async DefinitelyGetElementByTagName(tagName, parent = document.documentElement, option = null) { tagName = tagName.toUpperCase(); if (!(option?.doNotNormalCheck ?? false)) { const e = parent.getElementsByTagName(tagName)[0]; if (e != null) { return e; } } return this.WaitAddedNode(e => { if (e instanceof Element) { if (e.tagName == tagName) { return e; } if (option?.isDeepSearch ?? false) { const c = e.getElementsByTagName(tagName); if (c.length != 0) { return c[0]; } } } return null; }, parent, option); } static async DefinitelyGetElementsByClassName(className, parent = document.documentElement, option = null) { if (!(option?.doNotNormalCheck ?? false)) { const e = parent.getElementsByClassName(className); if (e.length != 0) { return Array.from(e); } } return this.WaitAddedNodes(e => { const ret = []; if (e instanceof Element) { if (e.classList.contains(className)) { ret.push(e); } if (option?.isDeepSearch ?? false) { ret.push(...Array.from(e.getElementsByClassName(className))); } } return ret; }, parent, option); } static async DefinitelyGetElementsByTagName(tagName, parent = document.documentElement, option = null) { tagName = tagName.toUpperCase(); if (!(option?.doNotNormalCheck ?? false)) { const e = parent.getElementsByTagName(tagName); if (e.length != 0) { return Array.from(e); } } return this.WaitAddedNodes(e => { const ret = []; if (e instanceof Element) { if (e.tagName == tagName) { ret.push(e); } if (option?.isDeepSearch ?? false) { ret.push(...Array.from(e.getElementsByTagName(tagName))); } } return ret; }, parent, option); } } ; class CommentList { constructor(illustId) { this.illustId = illustId; } async FetchCommentList() { const endPoint = `https://seiga.nicovideo.jp/ajax/illust/comment/list`; const method = "GET"; //console.log("FetchNicorare"); const res = await Util.GMFetchText(`${endPoint}?id=${this.illustId.toString()}&mode=all`, { method }); //console.log(res); if (res == "") { return false; } const result = JSON.parse(res); if (result?.result != "true" || result.comment_list == null) { return false; } result.comment_list.forEach(c => { if (typeof c.id == "string") { c.id = parseInt(c.id); } }); this.commentList = new SortedArray(result.comment_list); this.CountComment(); return true; } GetList() { return this.commentList; } CountComment() { for (const comment of this.commentList) { if (comment.thisUserComments != undefined) { continue; } const trueList = this.commentList.filter(c => c.user == comment.user); for (const trued of trueList) { trued.thisUserComments = trueList; } } } } class PopUp { constructor(createdElement, parent) { this.createdElement = createdElement; this.parent = parent; this.children = []; } SetElement(parentParentElement, comes) { const commentListElement = parentParentElement.getElementsByClassName("comment_list")[0]; if (!(commentListElement instanceof HTMLElement)) { return; } this.blackFilterElement = document.createElement("div"); this.blackFilterElement.style.backgroundColor = "rgba(0,0,0,0.3)"; this.blackFilterElement.style.position = "absolute"; if (this.parent == null) { this.blackFilterElement.style.top = commentListElement.offsetTop + "px"; this.blackFilterElement.style.height = commentListElement.offsetHeight + "px"; } else { this.blackFilterElement.style.top = this.parent.popupElement?.offsetTop + "px"; this.blackFilterElement.style.height = this.parent.popupElement?.offsetHeight + "px"; } this.blackFilterElement.style.width = "300px"; parentParentElement.insertAdjacentElement("beforeend", this.blackFilterElement); //クリックしたところに合わせて消す this.blackFilterElement.addEventListener("click", (e) => { if (popup != null) { e.stopPropagation(); const all = popup.GetAll(); for (const p of all) { if (p.createdElement == this.createdElement) { p.RemoveAll(); if (popup == p) { popup = null; } return; } } } }); this.popupElement = document.createElement("ul"); this.popupElement.className = "comment_list"; this.popupElement.style.position = "absolute"; this.popupElement.style.overflowY = "auto"; this.popupElement.style.width = "300px"; this.popupElement.style.backgroundColor = "white"; parentParentElement.insertAdjacentElement("beforeend", this.popupElement); this.popupElement.addEventListener("click", (e) => { if (popup != null) { e.stopPropagation(); } }); this.borderElement = document.createElement("div"); this.borderElement.style.position = "absolute"; this.borderElement.style.width = "299px"; this.borderElement.style.border = "1px solid"; this.borderElement.style.pointerEvents = "none"; parentParentElement.insertAdjacentElement("beforeend", this.borderElement); const triangleSvgParent = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); triangleSvgParent.style.position = "relative"; triangleSvgParent.setAttribute("width", "20"); triangleSvgParent.setAttribute("height", "5"); this.borderElement.appendChild(triangleSvgParent); const triangleSvg = document.createElementNS('http://www.w3.org/2000/svg', 'path'); triangleSvg.setAttribute('d', 'M0 0 L10 5 L20 0 Z'); triangleSvg.setAttribute('fill', 'white'); triangleSvg.setAttribute('stroke', 'white'); triangleSvg.setAttribute('stroke-width', "0"); triangleSvgParent.appendChild(triangleSvg); const userTempIdInnserElement = document.createElement("span"); userTempIdInnserElement.textContent = comes[0].user; userTempIdInnserElement.style.userSelect = "all"; const userTempIdElement = document.createElement("li"); userTempIdElement.textContent = `仮ID: `; userTempIdElement.style.margin = "3px"; userTempIdElement.style.fontSize = "83%"; userTempIdElement.style.textAlign = "center"; userTempIdElement.appendChild(userTempIdInnserElement); this.popupElement.appendChild(userTempIdElement); for (const c of comes) { const e = Util.HtmlToElement(` <li class="comment_list_item PopupComment" data-bind="css:{unpublic: !is_visible(), user: is_owner, unpublic : is_filtered()}"> <ul class="comment_info" data-display_flag=""> <li class="count_new" data-bind="visible: is_new"${c.is_new ? "" : 'style="display: none;"'}> NEW</li> <li class="date"><span data-bind="text: date">${c.date}</span></li> <li class="id">No.<span data-bind="text: id">${c.id}</span></li> <li class="user" data-bind="visible: is_owner" ${c.is_owner ? "" : 'style="display: none;"'}>投稿者</li> <li class="text" data-bind="text: is_filtered()? '###このコメントは表示されません###' : text">${c.text}</li> </ul> </li>`); //, event:{contextmenu: $parent.showNgMenu} //` <li class="ng_menu"> // <ul class="ng_menu_list"> // <li class="add_ng_comment" data-bind="click: $parent.addNgComment">NGコメントに追加</li> // <li class="add_ng_user" data-bind="click: $parent.addNgUser">NGユーザーに追加</li> // <li class="open_ng_comment">NG設定一覧を見る</li> // </ul> // </li>` if (e == null || !(e instanceof HTMLElement)) { continue; } this.popupElement.appendChild(e); } const lastE = this.popupElement.lastElementChild; lastE.firstElementChild.style.marginBottom = "0"; //位置大きさ計算 let padBottomBefore = parseFloat(window.getComputedStyle(this.popupElement, null).getPropertyValue('padding-bottom')); if (isNaN(padBottomBefore)) { padBottomBefore = 0; } const maxTop = commentListElement.offsetTop + parentParentElement.scrollTop; const maxHeight = commentListElement.offsetHeight; let popupTop = this.createdElement.getBoundingClientRect().y - parentParentElement.getBoundingClientRect().y + parentParentElement.scrollTop - this.popupElement.offsetHeight + padBottomBefore - 3; let popupHeight = this.popupElement.offsetHeight; let padBottomNext = 0; if (popupTop < maxTop) { const popupTopOther = popupTop + this.popupElement.offsetHeight + this.createdElement.offsetHeight - padBottomBefore + 6; // popupHeight -= maxTop - popupTop; // popupTop = maxTop; popupTop = popupTopOther; if (maxHeight < popupHeight + popupTop && padBottomBefore != 0) { padBottomNext = padBottomBefore; } triangleSvgParent.style.top = -13 + "px"; triangleSvgParent.setAttribute("transform", "rotate(180)"); } else { triangleSvgParent.style.top = popupHeight - padBottomBefore - 10 + 1 + "px"; } triangleSvgParent.style.left = this.createdElement.offsetLeft + "px"; this.popupElement.style.paddingBottom = padBottomNext + "px"; // this.popupElement.style.height = popupHeight - padBottomBefore + "px"; this.popupElement.style.top = popupTop + "px"; this.borderElement.style.height = popupHeight - padBottomBefore - 1 + "px"; this.borderElement.style.top = popupTop + "px"; let p = this.parent; let zIndex = 2001; while (p != null) { zIndex++; p = p.parent; } const zIndexStr = zIndex.toString(); this.blackFilterElement.style.zIndex = zIndexStr; this.popupElement.style.zIndex = zIndexStr; this.borderElement.style.zIndex = zIndexStr; } GetAncestor() { let p = this; while (true) { if (p.parent == null) { return p; } p = p.parent; } } GetAll() { const cs = [this]; for (const c of this.children) { cs.push(...c.GetAll()); } return cs; } Remove() { this.blackFilterElement?.remove(); this.popupElement?.remove(); this.borderElement?.remove(); } RemoveAll() { this.GetAll().forEach(e => e.Remove()); if (this.parent != null) { this.parent.children = this.parent.children.filter(p => p != this); } } } let popup = null; const SetCommentCountElement = async (commentListElements, commentList) => { const bigTask = new BigDOMTask(); for (const commentListElement of commentListElements) { const idElements = Array.from(commentListElement.getElementsByClassName("id")).filter(e => e.firstElementChild?.textContent?.length != 0); const ids = idElements.map(e => parseInt(e.firstElementChild?.textContent ?? "")); let matingList = commentList.GetList().GetMatingList(ids); if (matingList == null) { await commentList.FetchCommentList(); matingList = commentList.GetList().GetMatingList(ids) ?? null; if (matingList == null) { console.error("見つからない謎エラー"); continue; } } const comeCounts = Array.from(commentListElement.getElementsByClassName("ComeCount")); for (let i = 0; i < idElements.length; i++) { const idElement = idElements[i]; const come = matingList[i]; const id = ids[i]; if (come?.thisUserComments == null || id == undefined) { continue; } const commentCountText = `[${come.thisUserComments.findIndex(c => c.id == come.id) + 1}/${come.thisUserComments.length}]`; const beforeElement = comeCounts[i]; if (beforeElement != undefined) { //前のと同じならやめる、違ったら前の消しとく if (beforeElement.textContent == commentCountText) { continue; } else { beforeElement.remove(); } } //コメント全体の要素 const commentItemElement = idElement.parentElement?.parentElement; if (commentItemElement == null) { continue; } const countElement = document.createElement("li"); countElement.textContent = commentCountText; countElement.className = "ComeCount"; const task = new MicroDOMTask(); task.AddTask(() => idElement.insertAdjacentElement("beforebegin", countElement)); if (idElements.length < 15) { requestAnimationFrame(() => task.Start()); } else { bigTask.AddMicroTask(task, commentItemElement); } if (come.thisUserComments.length >= 2) { countElement.style.color = "rgb(35, 148, 216)"; } if (come.thisUserComments.length >= 5) { countElement.style.color = "rgb(216, 35, 35)"; } countElement.style.cursor = "pointer"; countElement.addEventListener("click", (e) => { e.stopPropagation(); let parentPopup = null; if (popup != null) { const all = popup.GetAll(); for (const p of all) { //生成元をクリックしてる場合 その子供含めて消す if (p.createdElement == countElement) { p.RemoveAll(); if (popup == p) { popup = null; } return; } for (const element of Array.from(p.popupElement?.children ?? [])) { //このポップアップ要素の子供をクリックしてる場合 親にする if (element == commentItemElement) { parentPopup = p; p.children.forEach(p => p.RemoveAll()); } } } } const newPopup = new PopUp(countElement, parentPopup); if (parentPopup == null) { popup?.RemoveAll(); popup = newPopup; } else { parentPopup.children.push(newPopup); } newPopup.SetElement(commentListElement.parentElement, come.thisUserComments); }); } } bigTask.Start(); }; (async () => { "use strict"; console.log("start"); //イラストページのとき const illustIdMatchs = location.href.match(/im(\d+)/); if (Util.HasLengthAtLeast(illustIdMatchs, 2)) { const illustId = parseInt(illustIdMatchs[1]); const commentList = new CommentList(illustId); await commentList.FetchCommentList(); await Util.WaitDocumentElement(); const commentListElements = await Util.Retry(() => Observer.DefinitelyGetElementsByClassName("comment_list"), 5, 1000, r => r.length >= 2) ?? []; if (commentListElements.length == 0) { console.error("commentListElementsが2つない"); return; } document.head.insertAdjacentHTML("beforeend", `<style> .illust_main .illust_side .illust_comment .comment_list .comment_list_item.PopupComment .comment_info::after { background: none; background-image: none; content: none; } .illust_main .illust_side .illust_comment .comment_list .comment_list_item .comment_info > li{ margin: 0 6px 0 0; } </style>`); document.getElementById("wrapper")?.addEventListener("click", () => { popup?.RemoveAll(); popup = null; }); SetCommentCountElement(commentListElements, commentList); //通常のリストにコメントが追加されたときのオブザーバー const mutationObserver = new MutationObserver(async (mrs) => { const nicorus = new Set(); for (let mr of mrs) { for (let i = 0; i < mr.addedNodes.length; i++) { const element = mr.addedNodes[i]; if (!(element instanceof HTMLElement)) { continue; } if (element.classList.contains("comment_list_item")) { nicorus.add(element.parentElement); break; } } } SetCommentCountElement([...nicorus], commentList); }); for (let e of commentListElements) { mutationObserver.observe(e, { childList: true, }); } const uniqueParents = Util.Unique(commentListElements.map(e => e.parentElement)); //リスト自体が追加されたときのオブザーバー const mutationObserverParent = new MutationObserver(async (mrs) => { const nicorus = new Set(); for (let mr of mrs) { for (let i = 0; i < mr.addedNodes.length; i++) { const element = mr.addedNodes[i]; if (!(element instanceof HTMLElement)) { continue; } if (element.classList.contains("comment_list")) { nicorus.add(element); break; } } } SetCommentCountElement([...nicorus], commentList); }); for (let e of uniqueParents) { mutationObserverParent.observe(e, { childList: true, }); } } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址