您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
dアニメストア ニコニコ支店の引用コメント関連のツール
当前为
// ==UserScript== // @name ニコニコ動画 引用コメントツール // @namespace https://midra.me // @version 1.7.4 // @description dアニメストア ニコニコ支店の引用コメント関連のツール // @author Midra // @license MIT // @match https://www.nicovideo.jp/* // @icon https://www.google.com/s2/favicons?sz=64&domain=nicovideo.jp // @run-at document-start // @noframes // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @connect nicovideo.jp // @require https://gf.qytechs.cn/scripts/7212-gm-config-eight-s-version/code/GM_config%20(eight's%20version).js?version=156587 // ==/UserScript== (() => { // src/util.js var generateDocumentByHTML = (html) => { const elem = document.createElement("html"); elem.insertAdjacentHTML("beforeend", html); return elem; }; var generateElementByHTML = (html) => { const elem = document.createElement("div"); elem.insertAdjacentHTML("beforeend", html); return elem.firstElementChild; }; var filterObject = (obj) => { if (obj != null && typeof obj === "object" && !Array.isArray(obj)) { Object.keys(obj).forEach((key) => { if (obj[key] == null) { delete obj[key]; } else { filterObject(obj[key]); } }); } }; var numberToKansuji = (num) => { let result = ""; if (typeof num === "number" && 0 < num && num < 1e5) { const kansujiA = ["", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"]; const kansujiB = ["", "\u5341", "\u767E", "\u5343", "\u4E07"]; const numAry = Array.from(num.toString()).map((v) => Number(v)); numAry.reverse().forEach((n, idx, ary) => { if (n === 0) { return; } else if (idx === 0) { result = `${kansujiA[n]}${result}`; } else if (n === 1) { if (3 <= idx && 5 <= ary.length) { result = `${kansujiA[n]}${kansujiB[idx]}${result}`; } else { result = `${kansujiB[idx]}${result}`; } } else { result = `${kansujiA[n]}${kansujiB[idx]}${result}`; } }); } return result !== "" ? result : null; }; var kansujiToNumber = (kansuji) => { let result = 0; if (typeof kansuji === "string") { const kansujiA = ["", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"]; const kansujiB = ["", "\u5341", "\u767E", "\u5343", "\u4E07"]; const numberB = [1, 10, 100, 1e3, 1e4]; let isKansuji; const kansujiAry = Array.from(kansuji); kansujiAry.forEach((char) => { isKansuji || (isKansuji = Boolean([...kansujiA, ...kansujiB].find((v) => v === char))); }); if (isKansuji) { kansujiAry.forEach((char, idx, ary) => { if (char === null) { return; } const idxA = kansujiA.findIndex((v) => v === char); const idxB = kansujiB.findIndex((v) => v === char); if (idxA !== -1) { if (idx + 1 < kansujiAry.length) { const nextIdxB = kansujiB.findIndex((v) => v === ary[idx + 1]); if (nextIdxB !== -1) { result += idxA * numberB[nextIdxB]; ary[idx + 1] = null; } } else { result += idxA; } } if (idxB !== -1) { if (idx + 1 < kansujiAry.length) { const nextIdxB = kansujiB.findIndex((v) => v === ary[idx + 1]); if (nextIdxB !== -1) { result += numberB[idxB] * numberB[nextIdxB]; ary[idx + 1] = null; } else { result += numberB[idxB]; } } else { result += numberB[idxB]; } } }); } } return 0 < result ? result : null; }; var convertToRoman = (num) => { const decimal = [1e3, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]; const romanNumeral = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]; let result = ""; decimal.forEach((val, idx) => { while (val <= num) { result += romanNumeral[idx]; num -= val; } }); return result || num.toString(); }; var fixRomanNum = (str) => { if (typeof str === "string") { const ronamNum = [null, "\u2170", "\u2171", "\u2172", "\u2173", "\u2174", "\u2175", "\u2176", "\u2177", "\u2178", "\u2179", "\u217A", "\u217B"]; return str.replace(/[ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻ]/, (char) => { const idx = ronamNum.indexOf(char); return idx !== -1 ? convertToRoman(Number(idx)).toLowerCase() : char; }); } }; var normalizeText = (text) => { if (typeof text === "string") { text = text.toLowerCase(); text = fixRomanNum(text); text = text.replace(/[-−\(\)()「」「」『』【】[]〈〉《》〔〕{}{}\[\]]/g, " "); text = text.replace(/[a-z0-9]/g, (s) => String.fromCharCode(s.charCodeAt(0) - 65248)); text = text.replace(/./g, (s) => ({ "\u301C": "~", "\uFF1F": "?", "\uFF01": "!", "\u201D": '"', "\u2019": "'", "\xB4": "'", "\uFF40": "`", "\uFF1A": ":", "\uFF0C": ",", "\uFF0E": ".", "\u30FB": "\uFF65", "\uFF0F": "/", "\uFF03": "#", "\uFF04": "$", "\uFF05": "%", "\uFF06": "&", "\uFF1D": "=", "\uFF20": "@" })[s] || s); return text; } else { return ""; } }; var normalizeEpisodeNumber = (str) => { return str.replace(/第?(\d+|[一二三四五六七八九十百千万]+)話|episode(\d+)|#(\d+)|\s(\d+)\s/g, (_, p1, p2, p3, p4) => { const num = Number(p1 || p2 || p3 || p4); if (Number.isFinite(num)) { return num.toString(); } else { return kansujiToNumber(p1).toString(); } }); }; var optimizeEpisodeNumberForSearch = (title) => { title = title.replace(/\s+/g, ' ').trim(); const splitedTitle = title.split(' '); if (splitedTitle.length === 3) { const matchedNumber = splitedTitle[1].match(/\d+|[一二三四五六七八九十百千万]+/); if (matchedNumber !== null) { let num = Number(matchedNumber[0]); if (Number.isNaN(num)) { num = kansujiToNumber(matchedNumber[0]); } splitedTitle[1] = `${num.toString()}話`; title = splitedTitle.join(' '); } } return title.replace(/第?(\d+|[一二三四五六七八九十百千万]+)話|episode(\d+)|#(\d+)/g, (_, p1, p2, p3) => { let num = Number(p1 || p2 || p3); if (Number.isNaN(num)) { num = kansujiToNumber(p1); } if (num < 10) { const zeroPadding = ('00' + num).slice(-2); return ` ${num} OR ${zeroPadding} OR ${numberToKansuji(num)} `; } else { return ` ${num} OR ${numberToKansuji(num)} `; } }) }; var optimizeTitleForSearch = (title = "") => { title = normalizeText(title); title = optimizeEpisodeNumberForSearch(title); title = title.replace(/\s+/g, " ").trim(); return title; }; var isEqualTitle = (titleA, titleB) => { let result = false; if (typeof titleA === 'string' && titleA !== '' && typeof titleB === 'string' && titleB !== '') { result = titleA === titleB; if (!result) { titleA = normalizeText(titleA); titleA = normalizeEpisodeNumber(titleA); titleA = titleA.replace(/\s+/g, '').trim(); titleB = normalizeText(titleB); titleB = normalizeEpisodeNumber(titleB); titleB = titleB.replace(/\s+/g, '').trim(); result = titleA === titleB; } } return result; }; // src/api.js var WATCH_V3 = "https://www.nicovideo.jp/api/watch/v3"; var THREADS = "https://nvcomment.nicovideo.jp/v1/threads"; var CHANNEL_VIDEO_DANIME_LINKS = "https://public-api.ch.nicovideo.jp/v1/user/channelVideoDAnimeLinks"; var SEARCH_V2 = "https://api.search.nicovideo.jp/api/v2/snapshot/video/contents/search"; var NICORU_KEYS = " https://nvapi.nicovideo.jp/v1/comment/keys/nicoru"; var getVideoData = async (videoId) => { if (videoId === void 0) { throw new Error("[ECT] ERROR: videoId is undefined"); } try { const requestQuery = { actionTrackId: `${Array.from(Array(10)).map(() => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[Math.random() * 62 | 0]).join("")}_${Date.now()}` }; const res2 = await fetch(`${WATCH_V3}/${videoId}?${new URLSearchParams(requestQuery)}`, { method: "GET", headers: { "x-client-os-type": "others", "x-frontend-id": 6, "x-frontend-version": 0 } }); const json = await res2.json(); if (json.data !== void 0) { return json.data; } else { throw new Error({ message: "[ECT] ERROR: getVideoData", object: res2 }); } } catch (e) { throw new Error(e); } }; var getThreads = async (videoData) => { if (videoData === void 0) { throw new Error("[ECT] ERROR: videoData is undefined"); } try { const res2 = await fetch(THREADS, { method: "POST", headers: { "x-client-os-type": "others", "x-frontend-id": 6, "x-frontend-version": 0 }, body: JSON.stringify({ additionals: {}, params: videoData.comment?.nvComment?.params || {}, threadKey: videoData.comment?.nvComment?.threadKey }) }); const json = await res2.json(); if (json.data !== void 0) { return json.data.threads; } else { throw new Error({ message: "[ECT] ERROR: getThreads", object: res2 }); } } catch (e) { throw new Error(e); } }; var getLinkedVideoId = async (videoId) => { if (videoId === void 0) { throw new Error("[ECT] ERROR: videoId is undefined"); } try { const requestQuery = { videoId, _frontendId: 6 }; const res2 = await fetch(`${CHANNEL_VIDEO_DANIME_LINKS}?${new URLSearchParams(requestQuery)}`); const json = await res2.json(); if (json.data !== void 0) { return json.data.items[0].linkedVideoId; } else { throw new Error({ message: "[ECT] ERROR: getLinkedVideoId", object: res2 }); } } catch (e) { throw new Error(e); } }; var search = async (query) => { return new Promise((resolve, reject) => { if (query === void 0) { reject("query is undefined"); } try { const requestQuery = { q: query.q, targets: query.targets.join(), fields: query.fields?.join() || ["contentId", "title", "channelId", "lengthSeconds", "tags"].join(), _sort: query._sort || "+startTime", _offset: query._offset, _limit: query._limit || 5, _context: "ect" }; if (query.filters !== void 0) { Object.entries(query.filters).forEach((val) => { if (Array.isArray(val[1]) && val[1].length === 2) { requestQuery[`filters[${val[0]}][gte]`] = val[1][0]; requestQuery[`filters[${val[0]}][lte]`] = val[1][1]; } else { requestQuery[`filters[${val[0]}][0]`] = val[1]; } }); } filterObject(requestQuery); GM_xmlhttpRequest({ method: "GET", url: `${SEARCH_V2}?${new URLSearchParams(requestQuery)}`, headers: { "User-Agent": "ECT/1.0" }, responseType: "json", onload: (e) => { if (e.response !== void 0) { resolve(e.response.data); } else { reject({ message: "[ECT] ERROR: search", object: res }); } }, onerror: (e) => reject(e) }); } catch (e) { reject(e); } }); }; var getNicoruKey = async (threadId, fork) => { if (threadId === void 0 || fork === void 0) { return new Error("[ECT] ERROR: threadId or fork is undefined"); } try { const requestQuery = { threadId, fork }; const res2 = await fetch(`${NICORU_KEYS}?${new URLSearchParams(requestQuery)}`, { method: "GET", headers: { "x-frontend-id": 6, "x-frontend-version": 0 }, credentials: "include" }); const json = await res2.json(); if (json.data !== void 0) { return json.data.nicoruKey; } else { throw new Error({ message: "[ECT] ERROR: getNicoruKey", object: res2 }); } } catch (e) { throw new Error(e); } }; // src/index.js (() => { "use strict"; const configInitData = { extraMain: { label: "\u300C\u5F15\u7528\u30B3\u30E1\u30F3\u30C8\u300D\u3092\u300C\u30C1\u30E3\u30F3\u30CD\u30EB\u30B3\u30E1\u30F3\u30C8\u300D\u306B\u7D71\u5408\u3059\u308B", type: "checkbox", default: true }, extraEasy: { label: "\u300C\u5F15\u7528\u304B\u3093\u305F\u3093\u30B3\u30E1\u30F3\u30C8\u300D\u3092\u300C\u304B\u3093\u305F\u3093\u30B3\u30E1\u30F3\u30C8\u300D\u306B\u7D71\u5408\u3059\u308B", type: "checkbox", default: true }, extraMainFromDanime: { label: "\u300Cd\u30A2\u30CB\u30E1\u30B9\u30C8\u30A2 \u30CB\u30B3\u30CB\u30B3\u652F\u5E97\u300D\u306E\u30B3\u30E1\u30F3\u30C8\u3092\u5F15\u7528\u30FB\u7D71\u5408\u3059\u308B", type: "checkbox", default: false }, forcedExtra: { label: "\u30B3\u30E1\u30F3\u30C8\u3092\u5F37\u5236\u7684\u306B\u5F15\u7528\u30FB\u7D71\u5408\u3059\u308B", type: "checkbox", default: false }, showAddedCommentCount: { label: "\u7D71\u5408\u3057\u305F\u30B3\u30E1\u30F3\u30C8\u306E\u6570\u3092\u30B3\u30E1\u30F3\u30C8\u6570\u6A2A\u306B\u8868\u793A\u3059\u308B", type: "checkbox", default: true }, showExtraViewCount: { label: "\u5F15\u7528\u3057\u305F\u52D5\u753B\u306E\u518D\u751F\u6570\u3092\u518D\u751F\u6570\u6A2A\u306B\u8868\u793A\u3059\u308B", type: "checkbox", default: false }, deleteExtra: { label: "\u300C\u5F15\u7528\u30B3\u30E1\u30F3\u30C8\u300D\u3068\u300C\u5F15\u7528\u304B\u3093\u305F\u3093\u30B3\u30E1\u30F3\u30C8\u300D\u3092\u975E\u8868\u793A\u306B\u3059\u308B", type: "checkbox", default: false }, deleteEasy: { label: "\u300C\u304B\u3093\u305F\u3093\u30B3\u30E1\u30F3\u30C8\u300D\u3068\u300C\u5F15\u7528\u304B\u3093\u305F\u3093\u30B3\u30E1\u30F3\u30C8\u300D\u3092\u975E\u8868\u793A\u306B\u3059\u308B", type: "checkbox", default: false }, kawaiiPct: { label: "\u30B3\u30E1\u30F3\u30C8\u306E\u300C\u304B\u308F\u3044\u3044\u300D\u7387\u3092\u8868\u793A\u3059\u308B", type: "checkbox", default: false } }; GM_config.init("\u30CB\u30B3\u30CB\u30B3\u52D5\u753B \u5F15\u7528\u30B3\u30E1\u30F3\u30C8\u30C4\u30FC\u30EB \u8A2D\u5B9A", configInitData); GM_config.onload = () => { setTimeout(() => { alert("\u8A2D\u5B9A\u3092\u53CD\u6620\u3055\u305B\u308B\u306B\u306F\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002"); }, 200); }; GM_registerMenuCommand("\u8A2D\u5B9A", GM_config.open); const config = {}; Object.keys(configInitData).forEach((v) => { config[v] = GM_config.get(v); }); console.log("[ECT] config:", config); if (!location.pathname.startsWith("/watch/")) return; const ECT = { DANIME_CHANNEL_ID: "ch2632720", get videoId() { return location.pathname.split("/")[2]; }, getChannnelThreadsData(videoData) { return videoData?.comment?.threads?.filter((thread) => { return { "main": "community", "easy": "easy" }[thread.forkLabel] === thread.label; }) || []; }, getExtraThreadsData(videoData) { return videoData?.comment?.threads?.filter((thread) => { return { "main": "extra-community", "easy": "extra-easy" }[thread.forkLabel] === thread.label; }) || []; }, async getLinkedVideo(videoId) { try { const linkedVideoId = await getLinkedVideoId(videoId); const linkedVideoData = await getVideoData(linkedVideoId); const linkedVideoThreads = await getThreads(linkedVideoData); return { videoData: linkedVideoData, threads: linkedVideoThreads }; } catch (e) { console.error(e); } }, async getIdenticalVideo(videoData) { if (videoData === void 0 || videoData.video === void 0) { throw new Error("[ECT] ERROR: videoData is undefined"); } try { const searchTitle = optimizeTitleForSearch(videoData.video.title); const duration = videoData.video.duration; console.log("[ECT] search title:", searchTitle); let searchResult; let filteredResult; try { const query = { targets: ["title"], filters: { "categoryTags": "\u30A2\u30CB\u30E1", "genre.keyword": "\u30A2\u30CB\u30E1", "lengthSeconds": Number.isFinite(duration) ? [duration - 2, duration + 2] : void 0 } }; searchResult = await search({ q: searchTitle, ...query }); console.log("[ECT] search result 1:", searchResult); if (Array.isArray(searchResult) && searchResult.length <= 1) { searchResult = await search({ q: videoData.video.title, ...query }); console.log("[ECT] search result 2:", searchResult); } if (Array.isArray(searchResult) && 0 < searchResult.length) { filteredResult = searchResult.filter((val) => isEqualTitle(val.title, videoData.video.title) && val.contentId !== videoData.video.id); } } catch (e) { console.error(e); } if (!Array.isArray(filteredResult) || filteredResult[0] === void 0) { try { const res2 = await fetch(`https://www.nicovideo.jp/search/${encodeURIComponent(searchTitle)}?genre=anime&sort=f&order=a`); const elem = generateDocumentByHTML(await res2.text()); searchResult = Array.from(elem.querySelectorAll('.item[data-video-item][data-video-id^="so"]')).filter((item) => { const videoLength = item.getElementsByClassName("videoLength")[0].textContent.trim().split(":"); const videoDuration = videoLength.length === 2 ? Number(videoLength[0]) * 60 + Number(videoLength[1]) : 0; return Math.abs(videoDuration - duration) <= 2; }).map((item) => ({ contentId: item.dataset.videoId, title: item.querySelector(".itemTitle > a")?.title?.trim() })); console.log("[ECT] search result 3:", searchResult); if (Array.isArray(searchResult) && 0 < searchResult.length) { filteredResult = searchResult.filter((val) => isEqualTitle(val.title, videoData.video.title) && val.contentId !== videoData.video.id); } } catch (e) { console.error(e); } } console.log("[ECT] search result (filtered):", filteredResult); if (filteredResult[0] !== void 0) { const identicalVideoData = await getVideoData(filteredResult[0].contentId); if (identicalVideoData?.channel?.isOfficialAnime) { const identicalVideoThreads = await getThreads(identicalVideoData); return { videoData: identicalVideoData, threads: identicalVideoThreads }; } } } catch (e) { console.error(e); } } }; unsafeWindow.ECT = ECT; unsafeWindow.fetch = new Proxy(unsafeWindow.fetch, { apply: async function(target, thisArg, argumentsList) { if (argumentsList[0].startsWith(THREADS) && argumentsList[0].endsWith("/nicorus") && unsafeWindow["origExtraThreadsOldNums"] !== null && unsafeWindow["origExtraThreads"] !== null) { try { const splitedUrl = argumentsList[0].split("/"); const body = JSON.parse(argumentsList[1].body); const oldNums = unsafeWindow["origExtraThreadsOldNums"][`${splitedUrl[5]}-${body.fork}`]; const oldNo = oldNums[body.no]; body.no = oldNo || body.no; let isExtraComment = false; for (const thread of unsafeWindow["origExtraThreads"]) { if (!isExtraComment && thread.fork === body.fork) { if (oldNo !== void 0) { isExtraComment = Boolean(thread.comments?.find((cmt) => cmt.no === oldNo && cmt.body === body.content)); if (isExtraComment) { splitedUrl[5] = thread.id; body.videoId = thread.videoId; body.nicoruKey = await getNicoruKey(splitedUrl[5], body.fork); } } } } argumentsList[0] = splitedUrl.join("/"); argumentsList[1].body = JSON.stringify(body); } catch (e) { console.error(e); } return Reflect.apply(target, thisArg, argumentsList); } else if (argumentsList[0] !== THREADS) { return Reflect.apply(target, thisArg, argumentsList); } const promise = Reflect.apply(target, thisArg, argumentsList); let videoData; let extraVideoData; let linkedVideo; let identicalVideo; let extraThreadsData = []; let channelThreadsData = []; unsafeWindow["origExtraThreads"] = null; unsafeWindow["origExtraThreadsOldNums"] = null; try { setTimeout(() => { document.querySelector(".FormattedNumber-addedComment")?.remove(); document.querySelector(".FormattedNumber-extraView")?.remove(); }, 0); console.log("[ECT] %cfetch start%c", "color:white;background-color:blue;", ""); videoData = await getVideoData(ECT.videoId); if (!videoData?.channel?.isOfficialAnime) { return promise; } console.log("[ECT] videoData:", videoData); if (videoData.channel.id === ECT.DANIME_CHANNEL_ID) { extraThreadsData = ECT.getExtraThreadsData(videoData); if (extraThreadsData.length !== 0) { extraVideoData = await getVideoData(extraThreadsData[0].videoId); } } else if (!config["deleteExtra"] && config["extraMainFromDanime"]) { linkedVideo = await ECT.getLinkedVideo(ECT.videoId); if (linkedVideo !== void 0 && Math.abs(videoData.video.duration - linkedVideo.videoData.video.duration) <= 2) { extraThreadsData = ECT.getChannnelThreadsData(linkedVideo.videoData); if (extraThreadsData.length !== 0) { extraVideoData = linkedVideo.videoData; } } } console.log("[ECT] linkedVideo:", linkedVideo); if (!config["deleteExtra"] && config["forcedExtra"] && extraThreadsData.length === 0 && (videoData.channel.id === ECT.DANIME_CHANNEL_ID || config["extraMainFromDanime"])) { identicalVideo = await ECT.getIdenticalVideo(videoData); if (identicalVideo !== void 0) { extraThreadsData = ECT.getChannnelThreadsData(identicalVideo.videoData); if (extraThreadsData.length !== 0) { extraVideoData = identicalVideo.videoData; } } } console.log("[ECT] identicalVideo:", identicalVideo); console.log("[ECT] extraThreadsData:", extraThreadsData); channelThreadsData = ECT.getChannnelThreadsData(videoData); console.log("[ECT] channelThreadsData:", channelThreadsData); console.log("[ECT] extraVideoData:", extraVideoData); } catch (e) { console.error(e); } const response = await promise; const json = await response.json(); console.log("[ECT] json:", json); if (Boolean(json.data?.threads?.length)) { let addedCommentCount = 0; for (const extraThreadData of extraThreadsData) { try { const extraThreadIdx = json.data.threads.findIndex((thread) => extraThreadData.id.toString() === thread.id && extraThreadData.forkLabel === thread.fork); if (config["deleteExtra"] && extraThreadIdx !== -1) { delete json.data.threads[extraThreadIdx]; } else { const targetThreadData = channelThreadsData.find((channelThread) => extraThreadData.forkLabel === channelThread.forkLabel && extraThreadData.label.indexOf(channelThread.label) !== -1); if (targetThreadData === void 0) { continue; } const targetThreadIdx = json.data.threads.findIndex((thread) => targetThreadData.id.toString() === thread.id && targetThreadData.forkLabel === thread.fork); if (targetThreadIdx === -1) { continue; } if (config["extraMain"] && extraThreadData.forkLabel === "main" || config["extraEasy"] && extraThreadData.forkLabel === "easy" && !config["deleteEasy"] || config["forcedExtra"] && identicalVideo !== void 0) { let extraThread; if (extraThreadIdx !== -1) { extraThread = json.data.threads[extraThreadIdx]; delete json.data.threads[extraThreadIdx]; } else { extraThread = (linkedVideo || identicalVideo)?.threads?.find((thread) => extraThreadData.id.toString() === thread.id && extraThreadData.forkLabel === thread.fork); } if (extraThread == null) { continue; } unsafeWindow["origExtraThreads"] || (unsafeWindow["origExtraThreads"] = []); unsafeWindow["origExtraThreads"].push(JSON.parse(JSON.stringify({ videoId: extraThreadData.videoId, ...extraThread }))); addedCommentCount += extraThread.commentCount; json.data.threads[targetThreadIdx].comments.push(...extraThread.comments); json.data.threads[targetThreadIdx].comments.sort((a, b) => new Date(a.postedAt).getTime() - new Date(b.postedAt).getTime()); const oldNo = []; json.data.threads[targetThreadIdx].comments.forEach((v, i) => { oldNo[i + 1] = v.no; v.no = i + 1; }); unsafeWindow["origExtraThreadsOldNums"] || (unsafeWindow["origExtraThreadsOldNums"] = {}); unsafeWindow["origExtraThreadsOldNums"][`${targetThreadData.id}-${targetThreadData.forkLabel}`] = oldNo; json.data.threads[targetThreadIdx].commentCount += extraThread.commentCount; } } json.data.threads = json.data.threads.filter(Boolean); } catch (e) { console.error(e); } } if (config["deleteEasy"]) { json.data.threads = json.data.threads.filter((v) => v.fork !== "easy"); } if (config["showAddedCommentCount"] && addedCommentCount !== 0) { setTimeout((cnt) => { const counter = document.querySelector(".CommentCountMeta-counter > .FormattedNumber"); counter?.insertAdjacentHTML( "afterend", `<span class="FormattedNumber-addedComment"> (+${cnt.toLocaleString()})</span>` ); }, 0, addedCommentCount); } console.log(`[ECT] \u7D71\u5408\u3057\u305F\u5F15\u7528\u30B3\u30E1\u30F3\u30C8\u6570: ${addedCommentCount}`); } if (config["showExtraViewCount"] && Boolean(extraVideoData?.video?.count?.view)) { setTimeout((cnt) => { const counter = document.querySelector(".VideoViewCountMeta-counter > .FormattedNumber"); counter?.insertAdjacentHTML( "afterend", `<span class="FormattedNumber-extraView"> (+${cnt.toLocaleString()})</span>` ); }, 0, extraVideoData.video.count.view); } if (config["kawaiiPct"]) { let cmtCnt = 0; let kawaiiCnt = 0; for (const thread of json.data.threads) { for (const comment of thread.comments) { cmtCnt++; if (comment.body.indexOf("\u304B\u308F\u3044\u3044") !== -1) { kawaiiCnt++; } } } const kawaiiPct = Math.round(kawaiiCnt / cmtCnt * 10 * 100) / 10; if (0 < kawaiiPct) { setTimeout((pct) => { try { const kawaiiPctElem = document.querySelector(".KawaiiPctMeta") || document.querySelector(".CommentCountMeta")?.cloneNode(true); if (kawaiiPctElem instanceof HTMLElement) { kawaiiPctElem.classList.add("KawaiiPctMeta"); kawaiiPctElem.querySelector(".CommentCountMeta-title").textContent = "\u304B\u308F\u3044\u3044\u7387"; kawaiiPctElem.querySelector(".CommentCountMeta-counter").textContent = `${pct}%`; document.querySelector(".CommentCountMeta").insertAdjacentElement("afterend", kawaiiPctElem); } } catch (e) { console.error(e); } }, 0, kawaiiPct); } } return new Response(JSON.stringify(json), { status: response.status, statusText: response.statusText, headers: response.headers }); } }); const obs_opt = { childList: true, subtree: true }; const obs = new MutationObserver((mutationRecord) => { for (const { addedNodes } of mutationRecord) { for (const added of addedNodes) { if (added instanceof HTMLElement && added.classList.contains("ContextMenu-wrapper")) { obs.disconnect(); const menuContainer = added.getElementsByClassName("VideoContextMenuContainer")[0]; if (menuContainer instanceof HTMLElement) { const ectOptionBtn = generateElementByHTML( ` <div class="VideoContextMenu-group"> <div class="ContextMenuItem">\u5F15\u7528\u30B3\u30E1\u30F3\u30C8\u30C4\u30FC\u30EB \u8A2D\u5B9A</div> </div> ` ); ectOptionBtn.firstElementChild.addEventListener("click", GM_config.open); menuContainer.appendChild(ectOptionBtn); } obs.observe(document.body, obs_opt); } } } }); obs.observe(document.body, obs_opt); })(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址