// ==UserScript== // @name ニコニコ静画、簡単NGスクリプト // @namespace http://tampermonkey.net/ // @version 1.072 // @description 申し訳ないが見たくないイラストはNG // @author cbxm // @match https://seiga.nicovideo.jp/tag/* // @match https://seiga.nicovideo.jp/seiga/* // @grant GM.xmlHttpRequest // @grant GM.getValue // @grant GM.setValue // @run-at document-start // @downloadURL none // ==/UserScript== (async () => { "use strict"; ; ; class Util { //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; } } const tag = tagMatchs[1]; //タグの内側とその先を抜き出す const matchChildlen = []; while (true) { const matchs = xmlString.match(new RegExp(`^[^<]*<${tag}>([^]+?)<\/${tag}>([^]*)`)); if (matchs == null) { break; } matchChildlen.push(matchs[1]); xmlString = matchs[2]; } //タグあったのにマッチしなかったおかしい if (matchChildlen.length == 0) { return obj; } //そのタグが一つしかないとき、オブジェクトになる if (matchChildlen.length == 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; }; //初期化でを取り除く 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 Wait(ms) { return new Promise(r => setTimeout(r, ms)); } 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); } } ; class Fetcher { static GMFetchText(url) { return new Promise(r => { GM.xmlHttpRequest({ url: url, method: "GET", onload: (response) => { r(response.responseText); } }); }); } static async FetchIllustDatas(ids) { if (ids.length == 0) { return { illusts: [], userIds: [] }; } const url = `http:\/\/seiga.nicovideo.jp/api/illust/info?id_list=${ids.join()}`; const res = await this.GMFetchText(url); const obj = Util.XmlToObj(res); const list = Array.isArray(obj.response.image_list) ? obj.response.image_list : [obj.response.image_list.image]; const illusts = []; for (let i = 0; i < list.length; i++) { illusts[i] = { id: list[i].id, created: new Date(list[i].created) }; } return { illusts: illusts, userIds: list.map(l => l.user_id) }; } static async FetchUserName(userId) { const url = "http://seiga.nicovideo.jp/api/user/info?id=" + userId; const json = Util.XmlToObj(await this.GMFetchText(url)); return json.response.user.nickname; } static async FetchUserId(illustId) { const url = "https://seiga.nicovideo.jp/api/illust/info?id=im" + illustId; const resultText = await this.GMFetchText(url); const json = Util.XmlToObj(resultText); return json.response.image.user_id; } } ; class Storage { constructor(storageName) { this.storageName = ""; this.storageName = storageName; } async GetStorageData(defaultValue = null) { const text = await GM.getValue(this.storageName, null); return text != null ? JSON.parse(decodeURIComponent(text)) : defaultValue; } async SetStorageData(data) { await GM.setValue(this.storageName, encodeURIComponent(JSON.stringify(data))); } } ; class Observer { static Wait(predicate, parent = document, option = null) { return new Promise(r => { if (option == null) { option = { childList: true, subtree: true }; } const mutationObserver = new MutationObserver((mrs) => { if (predicate(mrs)) { mutationObserver.disconnect(); r(mrs); return; } }); mutationObserver.observe(parent, option); }); } ; static WaitAddedNode(predicate, parent, option = null) { return new Promise(r => { if (option == null) { option = { childList: true, subtree: true }; } const mutationObserver = new MutationObserver((mrs) => { //console.log(document.head.innerHTML); //console.log(document.body.innerHTML); for (let node of mrs) { node.addedNodes.forEach(added => { //console.log(added); if (predicate(added)) { mutationObserver.disconnect(); r(added); return; } }); } }); mutationObserver.observe(parent, option); }); } ; static async DefinitelyGetElementById(id, parent = document, option = null) { const e = document.getElementById(id); if (e != null) { return e; } return this.WaitAddedNode(e => e.id != null && e.id == id, parent, option); } //getElementsByClassNameをつかうけど単体なので注意 static async DefinitelyGetElementByClassName(className, parent = document, option = null) { const e = document.getElementsByClassName(className)[0]; if (e != null) { return e; } return this.WaitAddedNode(e => e.className != null && e.className == className, parent, option); } //getElementsByTagNameをつかうけど単体なので注意 static async DefinitelyGetElementByTagName(tagName, parent = document, option = null) { tagName = tagName.toUpperCase(); const e = document.getElementsByTagName(tagName)[0]; if (e != null) { return e; } return this.WaitAddedNode(e => e.tagName != null && e.tagName == tagName, parent, option); } } ; //暫定OK、暫定荒らし、確定OK、確定荒らし //type Status = "OK" | "NG" | "LOK" | "LNG" let PageType; (function (PageType) { PageType[PageType["None"] = -1] = "None"; PageType[PageType["TAG_SEARCH"] = 0] = "TAG_SEARCH"; PageType[PageType["ILLUST"] = 1] = "ILLUST"; PageType[PageType["MAX"] = 2] = "MAX"; })(PageType || (PageType = {})); ; ; const pageInfos = [ { regex: /https:\/\/seiga.nicovideo.jp\/tag\/.*/, name: "タグ検索", }, { regex: /https:\/\/seiga.nicovideo.jp\/seiga\/.*/, name: "イラストページ", } ]; let Status; (function (Status) { Status[Status["NONE"] = 0] = "NONE"; Status[Status["OK"] = 1] = "OK"; Status[Status["NG"] = 2] = "NG"; Status[Status["WHITE"] = 3] = "WHITE"; Status[Status["BLACK"] = 4] = "BLACK"; Status[Status["MAX"] = 5] = "MAX"; })(Status || (Status = {})); let IllustSize; (function (IllustSize) { IllustSize[IllustSize["NOMAL"] = 0] = "NOMAL"; IllustSize[IllustSize["CUTOUT"] = 1] = "CUTOUT"; })(IllustSize || (IllustSize = {})); ; class Main { constructor() { this.cache = []; this.illustInfos = []; this.selectedList = []; this.cacheStorage = new Storage("NICONICO_RENTO_ARASI_NG_DATA_CACHE"); this.optionStorage = new Storage("NICONICO_RENTO_ARASI_NG_OPTION_CACHE"); this.imgIntersectionObserver = new IntersectionObserver(entries => { for (let e of entries) { if (e.intersectionRatio > 0) { const img = e.target; if (img.src != null && img.dataset != null && img.dataset.src != null) { img.src = img.dataset.src; } this.imgIntersectionObserver.unobserve(img); } } }); } async GetStorageData() { this.cache = (await this.cacheStorage.GetStorageData([])); //console.log(this.cache); const defaultOption = { usePages: [true, false], judge: { isJudge: false, time: 1 * 60 * 60 * 1000, postCount: 3 }, okCacheMax: 1000 //どのくらいがいいのかわからない }; this.option = await this.optionStorage.GetStorageData(defaultOption); if (this.option.usePages == undefined) { this.option.usePages = defaultOption.usePages; } for (let i = 0; i < PageType.MAX; i++) { if (this.option.usePages[i] == undefined) { this.option.usePages[i] = defaultOption.usePages[i]; } } if (this.option.judge == undefined) { this.option.judge = defaultOption.judge; } if (this.option.judge.time == undefined) { this.option.judge.time = defaultOption.judge.time; } if (this.option.judge.postCount == undefined) { this.option.judge.postCount = defaultOption.judge.postCount; } if (this.option.judge.isJudge == undefined) { this.option.judge.isJudge = defaultOption.judge.isJudge; } if (this.option.okCacheMax == undefined) { this.option.okCacheMax = defaultOption.okCacheMax; } //console.log(this.option); } GetInfo(illustId) { for (let c of this.cache) { for (let illust of c.illusts) { if (illust.id == illustId) { return { user: c, illust: illust }; } } } return undefined; } CheckArasi(user) { if (user.illusts.length == 0 || user.status == Status.BLACK || user.status == Status.WHITE || user.status == Status.NG) { return; } for (let illust of user.illusts) { if (typeof illust.created == "string") { illust.created = new Date(illust.created); } } //新しい順 const sorted = user.illusts.sort((a, b) => b.created.getTime() - a.created.getTime()); for (let i = 0; i < sorted.length; i++) { const currentDate = sorted[i].created; let j = i + 1; let postCount = 1; while (true) { if (j >= sorted.length || currentDate.getTime() - sorted[j].created.getTime() > this.option.judge.time) { break; } j++; postCount++; } if (postCount >= this.option.judge.postCount) { user.status = Status.NG; return; } } } ; GetIllustIds(itemListElement) { var _a; const illustIdElements = itemListElement.getElementsByTagName("a"); const illustIds = []; for (let i = 0; i < illustIdElements.length; i++) { if (illustIdElements[i].parentElement != null && ((_a = illustIdElements[i].parentElement) === null || _a === void 0 ? void 0 : _a.classList.contains("list_more_link"))) { continue; } const idMatchs = illustIdElements[i].href.match(/im(\d+)/); if (idMatchs == null) { continue; } const id = idMatchs[1]; illustIds.push(id); } return illustIds; } DrawList() { const list = this.optionDialog.getElementsByClassName("scrollUL")[0]; const onlyCurrentPageCheckbox = this.optionDialog.getElementsByClassName("onlyCurrentPageCheckbox")[0]; const listStatusSelect = this.optionDialog.getElementsByClassName("listStatusSelect")[0]; if (list == undefined || onlyCurrentPageCheckbox == undefined || listStatusSelect == undefined) { return; } const status = listStatusSelect.value == "ALL" ? "" : Status[listStatusSelect.value]; list.innerHTML = ""; for (let user of this.cache) { if (status != "" && user.status != status) { continue; } const info = this.illustInfos.find(info => info.user.userId == user.userId); let sampleIllustId = info != undefined ? info.illustId : undefined; if (onlyCurrentPageCheckbox.checked && sampleIllustId == undefined) { continue; } if (sampleIllustId == undefined) { sampleIllustId = user.illusts[0].id; } const div = document.createElement("div"); div.style.height = "70px"; div.style.display = "flex"; div.style.flexDirection = "column"; div.className = "userInfoItem"; list.appendChild(div); div.addEventListener("mouseup", e => this.ClickList(div)); { const nameIdDiv = document.createElement("div"); nameIdDiv.style.top = "relative"; nameIdDiv.style.position = "4px"; div.appendChild(nameIdDiv); { const nameSpan = document.createElement("span"); nameSpan.className = "userName"; nameSpan.textContent = info == undefined ? "" : info.name; nameSpan.style.fontSize = "130%"; nameSpan.style.color = "black"; nameSpan.style.width = "66px"; nameSpan.style.height = "24px"; nameSpan.style.padding = "3px"; nameIdDiv.appendChild(nameSpan); const idSpan = document.createElement("span"); idSpan.className = "userId"; idSpan.textContent = user.userId; idSpan.style.fontSize = "130%"; idSpan.style.color = "black"; idSpan.style.width = "66px"; idSpan.style.padding = "3px"; nameIdDiv.appendChild(idSpan); } const userAndSampleImgDiv = document.createElement("div"); div.appendChild(userAndSampleImgDiv); { const aUser = document.createElement("a"); aUser.href = `https:\/\/seiga.nicovideo.jp/user/illust/${user.userId}`; userAndSampleImgDiv.appendChild(aUser); { const imgUser = document.createElement("img"); imgUser.dataset.src = `https:\/\/secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/${Math.floor(parseInt(user.userId) / 10000)}/${user.userId}.jpg`; imgUser.style.height = "40px"; imgUser.style.position = "relative"; imgUser.style.padding = "0 20px 0 10px"; imgUser.style.top = "-5px"; this.imgIntersectionObserver.observe(imgUser); aUser.appendChild(imgUser); imgUser.addEventListener("error", () => { imgUser.src = "https:\/\/secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg"; }); } const aSample = document.createElement("a"); aSample.href = `https:/\/seiga.nicovideo.jp/seiga/im${sampleIllustId}`; userAndSampleImgDiv.appendChild(aSample); { const imgSample = document.createElement("img"); imgSample.dataset.src = `https:\/\/lohas.nicoseiga.jp\/\/thumb/${sampleIllustId}c`; imgSample.style.height = "30px"; this.imgIntersectionObserver.observe(imgSample); imgSample.style.position = "relative"; imgSample.style.top = "-5px"; aSample.appendChild(imgSample); const bigSample = document.createElement("img"); bigSample.dataset.src = `https:\/\/lohas.nicoseiga.jp\/\/thumb/${sampleIllustId}c`; bigSample.style.height = "100px"; this.imgIntersectionObserver.observe(bigSample); bigSample.style.pointerEvents = "none"; bigSample.style.position = "absolute"; bigSample.style.zIndex = "110"; imgSample.addEventListener("mouseover", () => { const clientRect = imgSample.getBoundingClientRect(); const x = window.pageXOffset + clientRect.left + imgSample.width / 2 - 50; const y = window.pageYOffset + clientRect.top + imgSample.height / 2 - 50; bigSample.style.top = y + "px"; bigSample.style.left = x + "px"; document.body.appendChild(bigSample); }); imgSample.addEventListener("mouseleave", () => { bigSample.remove(); }); } } } } } ClickList(target) { if (target != null) { if (this.selectedList.includes(target)) { target.style.backgroundColor = ""; this.selectedList = this.selectedList.filter(s => s != target); } else { target.style.backgroundColor = "rgba(0, 140, 255, 0.5)"; this.selectedList.push(target); } } } async SetOptionButton() { const parent = await Observer.DefinitelyGetElementByClassName("sg_pankuzu"); if (document.getElementById("optionSpan") != null) { return; } const optionSpan = document.createElement("span"); optionSpan.id = "optionSpan"; optionSpan.style.margin = "0 10px"; parent.appendChild(optionSpan); { const optionButton = document.createElement("input"); optionButton.type = "button"; optionButton.value = "簡単NGスクリプト"; optionButton.style.backgroundColor = "yellow"; optionButton.style.padding = "1px 10px"; optionButton.style.fontSize = "110%"; optionButton.style.cssText += "color: black !important;"; optionButton.addEventListener("click", () => { if (this.optionDialog.parentElement == null) { optionSpan.appendChild(this.optionDialog); return; } this.optionDialog.style.display = (this.optionDialog.style.display == "none") ? "block" : "none"; }); optionSpan.appendChild(optionButton); this.optionDialog = document.createElement("div"); this.optionDialog.style.backgroundColor = "white"; this.optionDialog.style.position = "absolute"; this.optionDialog.style.padding = "5px"; this.optionDialog.style.marginLeft = "10px"; this.optionDialog.style.zIndex = "100"; this.optionDialog.style.border = "2px solid"; { const list1 = document.createElement("div"); list1.style.display = "flex"; list1.style.paddingTop = "5px"; list1.style.paddingBottom = "10px"; this.optionDialog.appendChild(list1); { const listStatusSelect = document.createElement("select"); listStatusSelect.className = "listStatusSelect"; listStatusSelect.style.margin = "5px"; list1.appendChild(listStatusSelect); for (let i = 1; i <= Status.MAX; i++) { const option = document.createElement("option"); const text = i == Status.MAX ? "ALL" : Status[i]; option.value = text; option.textContent = text; listStatusSelect.appendChild(option); } listStatusSelect.addEventListener("change", () => { while (this.selectedList.length != 0) { const element = this.selectedList.pop(); if (element != undefined) { element.style.backgroundColor = ""; } } this.DrawList(); }); const onlyCurrentPageLabel = document.createElement("label"); onlyCurrentPageLabel.style.color = "black"; onlyCurrentPageLabel.style.padding = "3px"; onlyCurrentPageLabel.style.display = "flex"; list1.appendChild(onlyCurrentPageLabel); { const onlyCurrentPageCheckbox = document.createElement("input"); onlyCurrentPageCheckbox.type = "checkbox"; onlyCurrentPageCheckbox.className = "onlyCurrentPageCheckbox"; onlyCurrentPageCheckbox.checked = true; onlyCurrentPageCheckbox.style.padding = "3px"; onlyCurrentPageCheckbox.style.margin = "10px"; onlyCurrentPageCheckbox.style.marginRight = "3px"; onlyCurrentPageCheckbox.style.marginLeft = "0px"; onlyCurrentPageLabel.appendChild(onlyCurrentPageCheckbox); onlyCurrentPageCheckbox.addEventListener("change", () => this.DrawList()); const onlyCurrentPageText = document.createElement("div"); onlyCurrentPageText.textContent = "このページだけ"; onlyCurrentPageText.style.color = "black"; onlyCurrentPageLabel.appendChild(onlyCurrentPageText); } const allSelect = document.createElement("input"); allSelect.type = "button"; allSelect.value = "全選択"; allSelect.style.color = "black"; allSelect.style.fontSize = "120%"; allSelect.style.padding = "0 5px"; allSelect.style.margin = "3px"; list1.appendChild(allSelect); allSelect.addEventListener("click", () => { const infos = Array.from(document.getElementsByClassName("userInfoItem")); for (let info of infos) { this.ClickList(info); } }); const detailButton = document.createElement("input"); detailButton.type = "button"; detailButton.value = "設定"; detailButton.style.color = "black"; detailButton.style.fontSize = "120%"; detailButton.style.margin = "3px"; detailButton.style.marginLeft = "45px"; detailButton.style.padding = "0 10px"; list1.appendChild(detailButton); detailButton.addEventListener("click", () => detailDialog.style.display = (detailDialog.style.display == "none") ? "block" : "none"); const detailDialog = document.createElement("div"); detailDialog.style.backgroundColor = "white"; detailDialog.style.display = "none"; detailDialog.style.position = "absolute"; detailDialog.style.padding = "0 10px"; detailDialog.style.zIndex = "100"; detailDialog.style.border = "2px solid"; detailDialog.style.left = "360px"; detailDialog.style.top = "10px"; list1.appendChild(detailDialog); const useSettingH3 = document.createElement("h1"); useSettingH3.textContent = "使うところ"; useSettingH3.style.fontSize = "140%"; useSettingH3.style.marginTop = "10px"; useSettingH3.style.color = "black"; detailDialog.appendChild(useSettingH3); const setUseListDiv = document.createElement("div"); setUseListDiv.style.marginBottom = "10px"; detailDialog.appendChild(setUseListDiv); { for (let i = 0; i < PageType.MAX; i++) { const setUseLabel = document.createElement("label"); setUseLabel.style.padding = "3px"; setUseLabel.style.display = "flex"; setUseListDiv.appendChild(setUseLabel); { const setUsePageCheckbox = document.createElement("input"); setUsePageCheckbox.type = "checkbox"; setUsePageCheckbox.checked = this.option.usePages[i]; setUsePageCheckbox.style.padding = "3px"; setUsePageCheckbox.style.margin = "10px"; setUsePageCheckbox.style.marginRight = "3px"; setUseLabel.appendChild(setUsePageCheckbox); setUsePageCheckbox.addEventListener("change", async () => { this.option.usePages[i] = setUsePageCheckbox.checked; await this.optionStorage.SetStorageData(this.option); }); const setUsePageText = document.createElement("div"); setUsePageText.textContent = pageInfos[i].name; setUsePageText.style.padding = "3px"; setUsePageText.style.fontSize = "120%"; setUsePageText.style.color = "black"; setUseLabel.appendChild(setUsePageText); } } } const otherSettingH3 = document.createElement("h1"); otherSettingH3.textContent = "その他"; otherSettingH3.style.fontSize = "140%"; otherSettingH3.style.marginTop = "10px"; otherSettingH3.style.color = "black"; detailDialog.appendChild(otherSettingH3); const judgeRigorCover = document.createElement("div"); const setIsJudgeDiv = document.createElement("div"); setIsJudgeDiv.style.padding = "5px"; setIsJudgeDiv.style.paddingBottom = "0"; setIsJudgeDiv.style.display = "flex"; detailDialog.appendChild(setIsJudgeDiv); { const isJudgeCheckbox = document.createElement("input"); isJudgeCheckbox.type = "checkbox"; isJudgeCheckbox.id = "isJudgeCheckbox"; isJudgeCheckbox.checked = this.option.judge.isJudge; isJudgeCheckbox.style.padding = "3px"; isJudgeCheckbox.style.margin = "10px"; isJudgeCheckbox.style.marginRight = "3px"; setIsJudgeDiv.appendChild(isJudgeCheckbox); isJudgeCheckbox.addEventListener("change", async () => { this.option.judge.isJudge = isJudgeCheckbox.checked; if (this.option.judge.isJudge) { judgeRigorCover.style.visibility = "hidden"; } else { judgeRigorCover.style.visibility = "visible"; } await this.optionStorage.SetStorageData(this.option); }); const isJudgeLabel = document.createElement("label"); isJudgeLabel.htmlFor = "isJudgeCheckbox"; isJudgeLabel.textContent = "連投自動NG"; isJudgeLabel.style.color = "black"; isJudgeLabel.style.padding = "3px"; isJudgeLabel.style.fontSize = "120%"; setIsJudgeDiv.appendChild(isJudgeLabel); } const setJudgeRigorFlex = document.createElement("div"); setJudgeRigorFlex.style.padding = "0px 10px 5px 10px"; setJudgeRigorFlex.style.position = "relative"; detailDialog.appendChild(setJudgeRigorFlex); { const setJudgeTime = document.createElement("input"); setJudgeTime.type = "time"; setJudgeTime.style.height = "20px"; setJudgeTime.style.fontSize = "120%"; const hour = ('00' + Math.floor(this.option.judge.time / 60 / 1000 / 60).toString()).slice(-2); const minutes = ('00' + (this.option.judge.time / 60 / 1000 % 60).toString()).slice(-2); setJudgeTime.value = `${hour}:${minutes}`; setJudgeTime.addEventListener("change", async () => { const [h, m] = setJudgeTime.value.split(":").map(s => parseInt(s)); const ms = ((h * 60) + m) * 60 * 1000; if (ms >= 1) { this.option.judge.time = ms; await this.optionStorage.SetStorageData(this.option); } else { const hour = ('00' + Math.floor(this.option.judge.time / 60 / 1000 / 60).toString()).slice(-2); const minutes = ('00' + (this.option.judge.time / 60 / 1000 % 60).toString()).slice(-2); setJudgeTime.value = `${hour}:${minutes}`; } }); setJudgeRigorFlex.appendChild(setJudgeTime); const setJudgeText1 = document.createElement("span"); setJudgeText1.textContent = "以内に"; setJudgeText1.style.color = "black"; setJudgeText1.style.fontSize = "15px"; setJudgeRigorFlex.appendChild(setJudgeText1); const setJudgePostCount = document.createElement("input"); setJudgePostCount.type = "number"; setJudgePostCount.value = this.option.judge.postCount.toString(); setJudgePostCount.style.width = "40px"; setJudgePostCount.min = "2"; setJudgePostCount.style.height = "20px"; setJudgePostCount.style.fontSize = "120%"; setJudgePostCount.addEventListener("change", async () => { const num = parseInt(setJudgePostCount.value); if (num >= 2) { this.option.judge.postCount = num; await this.optionStorage.SetStorageData(this.option); } else { this.option.judge.postCount = 2; setJudgePostCount.value = this.option.judge.postCount.toString(); } }); setJudgeRigorFlex.appendChild(setJudgePostCount); const setJudgeText2 = document.createElement("span"); setJudgeText2.textContent = "回投稿で仮荒らし認定"; setJudgeText2.style.color = "black"; setJudgeText2.style.fontSize = "15px"; setJudgeRigorFlex.appendChild(setJudgeText2); judgeRigorCover.style.backgroundColor = "gray"; judgeRigorCover.style.width = "310px"; judgeRigorCover.style.height = "30px"; judgeRigorCover.style.zIndex = "1000"; judgeRigorCover.style.opacity = "0.5"; judgeRigorCover.style.position = "absolute"; judgeRigorCover.style.top = "0"; setJudgeRigorFlex.appendChild(judgeRigorCover); if (this.option.judge.isJudge) { judgeRigorCover.style.visibility = "hidden"; } else { judgeRigorCover.style.visibility = "visible"; } } const categoryBlock = document.createElement("div"); categoryBlock.style.margin = "10px 0"; detailDialog.appendChild(categoryBlock); const setOKCacheMaxFlex = document.createElement("div"); setOKCacheMaxFlex.style.padding = "5px"; detailDialog.appendChild(setOKCacheMaxFlex); { const setOKCacheMaxText1 = document.createElement("span"); setOKCacheMaxText1.textContent = "OKユーザーのキャッシュ最大数:"; setOKCacheMaxText1.style.color = "black"; setOKCacheMaxText1.style.fontSize = "15px"; setOKCacheMaxFlex.appendChild(setOKCacheMaxText1); const setOKCacheMax = document.createElement("input"); setOKCacheMax.type = "number"; setOKCacheMax.value = this.option.okCacheMax.toString(); setOKCacheMax.style.width = "80px"; setOKCacheMax.min = "100"; setOKCacheMax.style.height = "20px"; setOKCacheMax.style.fontSize = "120%"; setOKCacheMax.addEventListener("change", async () => { const num = parseInt(setOKCacheMax.value); if (num >= 100) { this.option.okCacheMax = num; await this.optionStorage.SetStorageData(this.option); } else { this.option.okCacheMax = 100; setOKCacheMax.value = this.option.okCacheMax.toString(); } }); setOKCacheMaxFlex.appendChild(setOKCacheMax); } } const list2 = document.createElement("div"); list2.style.position = "relative"; list2.style.display = "flex"; this.optionDialog.appendChild(list2); { const userInfoList = document.createElement("ul"); userInfoList.className = "scrollUL"; userInfoList.style.overflowY = "scroll"; userInfoList.style.overflowX = "hidden"; userInfoList.style.height = "380px"; userInfoList.style.width = "250px"; list2.appendChild(userInfoList); const buttonList = document.createElement("ul"); buttonList.style.width = "90px"; list2.appendChild(buttonList); { const moveButtonList = document.createElement("div"); moveButtonList.style.marginTop = "20px"; moveButtonList.style.marginBottom = "10px"; buttonList.appendChild(moveButtonList); { for (let i = 1; i < Status.MAX; i++) { const div = document.createElement("div"); moveButtonList.appendChild(div); { const toButton = document.createElement("input"); toButton.type = "button"; toButton.style.padding = "3px"; toButton.style.fontSize = "130%"; toButton.style.margin = "3px"; toButton.value = "→ " + Status[i]; toButton.name = Status[i]; div.appendChild(toButton); toButton.addEventListener("click", async () => { while (this.selectedList.length != 0) { const element = this.selectedList.pop(); if (element == undefined) { continue; } element.style.backgroundColor = ""; const userId = element.getElementsByClassName("userId")[0].textContent; const user = this.cache.find(c => c.userId == userId); if (user != undefined) { user.status = Status[toButton.name]; } } for (let info of this.illustInfos) { this.UpdateIllust(info); this.DrawBlackWhiteButton(info); } this.DrawList(); await this.cacheStorage.SetStorageData(this.cache); }); } } } const DeleteSelectedUser = () => { while (this.selectedList.length != 0) { const element = this.selectedList.pop(); if (element == undefined) { continue; } const userId = element.getElementsByClassName("userId")[0].textContent; this.cache = this.cache.filter(c => c.userId != userId); const infos = this.illustInfos.filter(c => c.user.userId == userId); for (let info of infos) { info.user.status = Status.WHITE; this.UpdateIllust(info); this.DrawBlackWhiteButton(info); } this.illustInfos = this.illustInfos.filter(c => c.user.userId != userId); } }; const div = document.createElement("div"); buttonList.appendChild(div); { const selectedCacheClearButton = document.createElement("input"); selectedCacheClearButton.type = "button"; selectedCacheClearButton.style.padding = "3px"; selectedCacheClearButton.style.fontSize = "120%"; selectedCacheClearButton.style.margin = "3px"; selectedCacheClearButton.style.marginTop = "5px"; selectedCacheClearButton.style.backgroundColor = "yellow"; selectedCacheClearButton.style.cssText += "color: black !important"; selectedCacheClearButton.value = "→DELETE"; div.appendChild(selectedCacheClearButton); selectedCacheClearButton.addEventListener("click", async () => { if (!window.confirm("選択したアイテムのキャッシュクリアしていいですか?\nホワイト・ブラックリストも削除されます。")) { return; } DeleteSelectedUser(); this.DrawList(); await this.cacheStorage.SetStorageData(this.cache); }); } const div2 = document.createElement("div"); buttonList.appendChild(div2); { const allCacheClearButton = document.createElement("input"); allCacheClearButton.type = "button"; allCacheClearButton.style.padding = "3px"; allCacheClearButton.style.fontSize = "120%"; allCacheClearButton.style.margin = "3px"; allCacheClearButton.style.backgroundColor = "red"; allCacheClearButton.value = "ALL\nDELETE"; div2.appendChild(allCacheClearButton); allCacheClearButton.addEventListener("click", async () => { if (!window.confirm("全キャッシュクリアしていいですか?\nホワイト・ブラックリストも削除されます。")) { return; } for (let info of this.illustInfos) { info.user.status = Status.WHITE; this.UpdateIllust(info); this.DrawBlackWhiteButton(info); } this.illustInfos = []; this.cache = []; this.DrawList(); await this.cacheStorage.SetStorageData(this.cache); }); } const div3 = document.createElement("div"); buttonList.appendChild(div3); { const reStartButton = document.createElement("input"); reStartButton.type = "button"; reStartButton.style.padding = "3px"; reStartButton.style.fontSize = "120%"; reStartButton.style.margin = "3px"; reStartButton.style.marginTop = "10px"; reStartButton.style.backgroundColor = "green"; reStartButton.style.cssText += "color: white !important"; reStartButton.value = "RE START"; div3.appendChild(reStartButton); reStartButton.addEventListener("click", async () => { await this.Run(); }); } const div4 = document.createElement("div"); div4.style.marginTop = "10px"; div4.style.marginBottom = "10px"; buttonList.appendChild(div4); { const importDiv = document.createElement("div"); importDiv.style.position = "relative"; div4.appendChild(importDiv); { const importButton = document.createElement("input"); importButton.type = "button"; importButton.style.padding = "3px"; importButton.style.fontSize = "120%"; importButton.style.margin = "3px"; importButton.style.marginTop = "10px"; importButton.value = "← IMPORT"; importDiv.appendChild(importButton); const importFile = document.createElement("input"); importFile.type = "file"; importFile.style.position = "absolute"; importFile.style.opacity = "0"; importFile.style.width = "80px"; importFile.style.top = "8px"; importFile.style.left = "0"; importFile.accept = "text/plain"; importFile.style.padding = "0"; importDiv.appendChild(importFile); importFile.addEventListener("change", async (e) => { if (e.target == null) { return; } const files = e.target.files; if (files == null) { return; } const file = files[0]; if (file.type != "text/plain") { alert("テキストファイルを入れてください。"); return; } if (!window.confirm("インポートしていいですか?\nインポートする前に、今選択しているユーザーは削除されます。")) { return; } DeleteSelectedUser(); this.DrawList(); const reader = new FileReader(); reader.readAsText(file); reader.onload = async () => { if (typeof reader.result != "string") { return; } const importUsers = JSON.parse(reader.result); for (let imUser of importUsers) { for (let illust of imUser.illusts) { illust.created = new Date(illust.created); } } for (let imUser of importUsers) { const cachedUser = this.cache.find(c => c.userId == imUser.userId); if (cachedUser == undefined) { this.cache.push(imUser); } else { cachedUser.status = imUser.status; for (let illust of cachedUser.illusts) { if (cachedUser.illusts.some(c => c.id == illust.id)) { continue; } cachedUser.illusts.push(illust); } } } await this.cacheStorage.SetStorageData(this.cache); this.Run(); }; }); } const exportEiv = document.createElement("div"); div4.appendChild(exportEiv); { const reStartButton = document.createElement("input"); reStartButton.type = "button"; reStartButton.style.padding = "3px"; reStartButton.style.fontSize = "120%"; reStartButton.style.margin = "3px"; reStartButton.style.marginTop = "5px"; reStartButton.value = "→ EXPORT"; exportEiv.appendChild(reStartButton); reStartButton.addEventListener("click", async () => { const selectedUsers = []; for (let element of this.selectedList) { if (element == undefined) { continue; } const userId = element.getElementsByClassName("userId")[0].textContent; const user = this.cache.find(c => c.userId == userId); if (user != undefined) { selectedUsers.push(user); } } if (selectedUsers.length == 0) { alert("出力するユーザーを選択してください"); return; } const listStatusSelect = document.getElementById("listStatusSelect"); const status = listStatusSelect.value; const blob = new Blob([JSON.stringify(selectedUsers)], { type: "text/plain" }); const dlUrl = URL.createObjectURL(blob); await Util.Download(dlUrl, `niconicoNG_${status}.txt`); }); } } } } } } } UpdateIllust(info) { if (info.user.status == Status.OK || info.user.status == Status.WHITE) { info.element.getElementsByTagName("img")[0].style.filter = "brightness(1)"; if (info.element.parentElement == null) { info.parent.appendChild(info.element); } } if (info.user.status == Status.NG) { info.element.getElementsByTagName("img")[0].style.filter = "brightness(0.3)"; info.parent.appendChild(info.element); } if (info.user.status == Status.BLACK) { info.element.remove(); } } AddUserLink(illustInfo) { if (illustInfo.element.getElementsByClassName("userLink").length > 0) { return; } const userElement = illustInfo.element.getElementsByClassName("user")[0]; const userA = document.createElement("a"); userA.className = "userLink"; userA.href = "https://seiga.nicovideo.jp/user/illust/" + illustInfo.user.userId; userA.style.left = "0"; userA.style.zIndex = "10"; userA.style.right = "10px"; userA.style.position = "absolute"; userA.style.border = "0"; userA.style.opacity = "0"; userA.addEventListener("mouseover", () => { userA.style.border = "solid 1px silver"; userA.style.opacity = "0.3"; }); userA.addEventListener("mouseleave", () => { userA.style.border = "0"; userA.style.opacity = "0"; }); if (illustInfo.size == IllustSize.NOMAL) { userA.style.height = "10px"; userA.style.top = "34px"; userA.style.backgroundColor = "silver"; } if (illustInfo.size == IllustSize.CUTOUT) { userA.style.height = "20px"; userA.style.top = "20px"; userA.style.backgroundColor = "black"; } userElement.style.position = "relative"; userElement.style.zIndex = "20"; userElement.style.pointerEvents = "none"; userElement.insertAdjacentElement("beforebegin", userA); } DrawBlackWhiteButton(illustInfo) { if (illustInfo.user.status == Status.BLACK || illustInfo.user.status == Status.WHITE) { if (illustInfo.user.status == Status.WHITE) { const list = Array.from(illustInfo.element.getElementsByClassName("toListButton")); for (let l of list) { l.remove(); } } return; } if (illustInfo.element.getElementsByClassName("toListButton").length > 0) { return; } const whiteButton = document.createElement("input"); const blackButton = document.createElement("input"); whiteButton.style.position = "relative"; whiteButton.style.zIndex = "20"; whiteButton.style.visibility = "hidden"; if (illustInfo.size == IllustSize.NOMAL) { whiteButton.style.left = "117px"; whiteButton.style.top = "-30px"; whiteButton.style.width = "40px"; whiteButton.style.height = "25px"; } if (illustInfo.size == IllustSize.CUTOUT) { whiteButton.style.left = "54px"; whiteButton.style.top = "-19px"; whiteButton.style.width = "30px"; whiteButton.style.height = "19px"; } //上記のスタイルを両方に適用 blackButton.style.cssText = whiteButton.style.cssText; whiteButton.type = "button"; blackButton.type = "button"; whiteButton.className = "toListButton"; blackButton.className = "toListButton"; whiteButton.name = "white"; blackButton.name = "black"; whiteButton.style.cssText += `background-color : white !important;`; blackButton.style.cssText += `background-color : black !important;`; whiteButton.addEventListener("contextmenu", async (e) => { e.preventDefault(); illustInfo.user.status = Status.OK; for (let info of this.illustInfos) { this.UpdateIllust(info); } this.DrawList(); await this.cacheStorage.SetStorageData(this.cache); }); whiteButton.addEventListener("click", async () => { illustInfo.user.status = Status.WHITE; for (let info of this.illustInfos) { this.UpdateIllust(info); if (info.user.userId != illustInfo.user.userId) { continue; } const buttons = info.element.getElementsByClassName("toListButton"); while (buttons.length != 0) { buttons[0].remove(); } } this.DrawList(); await this.cacheStorage.SetStorageData(this.cache); }); blackButton.addEventListener("contextmenu", async (e) => { e.preventDefault(); illustInfo.user.status = Status.NG; for (let info of this.illustInfos) { this.UpdateIllust(info); } this.DrawList(); await this.cacheStorage.SetStorageData(this.cache); }); blackButton.addEventListener("click", async () => { illustInfo.user.status = Status.BLACK; for (let info of this.illustInfos) { this.UpdateIllust(info); } this.DrawList(); await this.cacheStorage.SetStorageData(this.cache); }); if (illustInfo.size == IllustSize.NOMAL) { const infoElement = illustInfo.element.getElementsByClassName("illust_count")[0]; blackButton.addEventListener("mouseover", () => { infoElement.style.opacity = "1"; }); blackButton.addEventListener("mouseleave", () => { infoElement.style.opacity = ""; }); whiteButton.addEventListener("mouseover", () => { infoElement.style.opacity = "1"; }); whiteButton.addEventListener("mouseleave", () => { infoElement.style.opacity = ""; }); } if (illustInfo.size == IllustSize.CUTOUT) { const infoElement = illustInfo.element.getElementsByClassName("illust_info")[0]; blackButton.addEventListener("mouseover", () => { infoElement.style.bottom = "0px"; }); blackButton.addEventListener("mouseleave", () => { infoElement.style.bottom = ""; }); whiteButton.addEventListener("mouseover", () => { infoElement.style.bottom = "0px"; }); whiteButton.addEventListener("mouseleave", () => { infoElement.style.bottom = ""; }); } illustInfo.element.addEventListener("mouseover", () => { blackButton.style.visibility = "visible"; whiteButton.style.visibility = "visible"; }); illustInfo.element.addEventListener("mouseleave", () => { blackButton.style.visibility = "hidden"; whiteButton.style.visibility = "hidden"; }); illustInfo.element.appendChild(whiteButton); illustInfo.element.appendChild(blackButton); } async AddInfos(illustListElement) { var _a; illustListElement.style.visibility = "hidden"; for (let moreLink of Array.from(illustListElement.getElementsByClassName("list_more_link"))) { if (moreLink.parentElement == null) { continue; } moreLink.parentElement.insertAdjacentElement("afterend", moreLink); } const illustElements = Array.from(illustListElement.getElementsByClassName("list_item")); const illustCutoutElements = Array.from(illustListElement.getElementsByClassName("list_item_cutout")); illustElements.push(...illustCutoutElements); const illustIds = this.GetIllustIds(illustListElement); const names = Array.from(illustListElement.getElementsByClassName("user")); //console.log(illustIds); //console.log(illustElements); //console.log(names); //キャッシュからの情報と合わせて追加(もうこれ分かんねぇこともある) for (let i = 0; i < illustIds.length; i++) { if (this.illustInfos.some(info => info.illustId == illustIds[i] && info.element == illustElements[i])) { continue; } const info = this.GetInfo(illustIds[i]); let size = IllustSize.NOMAL; if (illustElements[i].classList.contains("list_item_cutout")) { size = IllustSize.CUTOUT; } this.illustInfos.push({ name: (_a = names[i].textContent) !== null && _a !== void 0 ? _a : "", illustId: illustIds[i], illust: info == undefined ? { created: "", id: "" } : info.illust, user: info == undefined ? { illusts: [], status: Status.NONE, userId: "" } : info.user, element: illustElements[i], parent: illustListElement, size: size }); } } SetCurrentPage(url) { for (let i = 0; i < pageInfos.length; i++) { if (pageInfos[i].regex.test(url)) { this.currentPage = i; return this.option.usePages[i]; } } this.currentPage = PageType.None; return false; } //メインクラス、メイン関数の肥大化もう始まってる! async Run(illustListElements) { await Observer.DefinitelyGetElementByClassName("illust_list"); await Observer.DefinitelyGetElementById("footer"); illustListElements = illustListElements !== null && illustListElements !== void 0 ? illustListElements : Array.from(document.getElementsByClassName("item_list")); for (let illustListElement of illustListElements) { await this.AddInfos(illustListElement); } //console.log("infos", this.illustInfos, this.illustInfos.length); //これもう分かんねぇやつら const unkownInfos = this.illustInfos.filter(info => info.user.userId == ""); //console.log("unkownInfos", unkownInfos); //この戻り値なんかダサい・・・ダサくない? const result = await Fetcher.FetchIllustDatas(unkownInfos.map(info => info.illustId)); //これもう分かんねぇやつらとキャッシュまで!?の情報更新 for (let i = 0; i < unkownInfos.length; i++) { unkownInfos[i].illust = result.illusts[i]; let cachedUser = this.cache.find((c) => c.userId == result.userIds[i]); if (cachedUser == undefined) { unkownInfos[i].user.userId = result.userIds[i]; unkownInfos[i].user.status = Status.OK; this.cache.push(unkownInfos[i].user); } else { //キャッシュ使ったら後ろにしとく this.cache = this.cache.filter(c => c.userId != (cachedUser === null || cachedUser === void 0 ? void 0 : cachedUser.userId)); this.cache.push(cachedUser); unkownInfos[i].user = cachedUser; } unkownInfos[i].user.illusts.push(result.illusts[i]); } //増えすぎたキャッシュ削除 if (this.cache.length > 0) { let okCount = 0; for (let c of this.cache) { if (c.status == Status.OK) { okCount++; } } while (okCount > this.option.okCacheMax) { //OK以外消さない if (this.cache[0].status != Status.OK) { const c = this.cache.shift(); this.cache.push(c); continue; } //今使ってたら消さない if (this.illustInfos.some(info => info.user.userId == this.cache[0].userId)) { break; } this.cache.shift(); okCount--; } } //console.log(result); //ブラック,ホワイトリストにないイラストエレメントにボタン追加 for (let illustInfo of this.illustInfos) { this.DrawBlackWhiteButton(illustInfo); this.AddUserLink(illustInfo); } if (this.option.judge.isJudge) { //情報取ってきた投稿者の荒らし判定更新 ↓これは重複排除 for (let c of [...new Set(unkownInfos.map(u => u.user))]) { this.CheckArasi(c); } } await this.cacheStorage.SetStorageData(this.cache); for (let info of this.illustInfos) { this.UpdateIllust(info); } for (let illustListElement of illustListElements) { illustListElement.style.visibility = "visible"; } await this.SetOptionButton(); this.DrawList(); } } ; const main = new Main(); await main.GetStorageData(); await main.SetOptionButton(); const isUseNG = main.SetCurrentPage(location.href); if (!isUseNG) { return; } await main.Run(); const illustList = await Observer.DefinitelyGetElementByClassName("illust_list"); const mutationObserver = new MutationObserver(async (mrs) => { for (let mr of mrs) { for (let i = 0; i < mr.addedNodes.length; i++) { const element = mr.addedNodes[i]; if (element.classList != null && element.classList.contains("item_list")) { await main.Run([element]); } } } }); mutationObserver.observe(illustList, { childList: true, subtree: true }); })();