您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ニコニコ動画のかんたんコメントをカスタマイズします。
// ==UserScript== // @name Niconico Easy Kusa // @version 1.1.1 // @description ニコニコ動画のかんたんコメントをカスタマイズします。 // @author 蝙蝠の目 // @match https://www.nicovideo.jp/watch/* // @namespace https://greasyfork.org/ja/users/808813 // ==/UserScript== (() => { "use strict"; const SCRIPT_NAME = "NiconicoEasyKusa"; function init() { addCSS(` .EasyCommentButton { min-width: 0; margin-bottom: 4px; } .EasyCommentContainer { height: auto; min-height: 46px; } .EasyCommentContainer-inner { height: auto; } .EasyCommentContainer-prevButtonBox, .EasyCommentContainer-nextButtonBox { display: none !important; } .EasyCommentContainer-easyComments { white-space: normal; } .${SCRIPT_NAME}-pre { display: inline; font: inherit; margin: 0; padding: 0; } .${SCRIPT_NAME}-editButtonContainer { margin-left: 1.5em; } .${SCRIPT_NAME}-editButtonContainer *:nth-of-type(n+2) { margin-left: 0.8em; } .CommentPostContainer-commentInput.${SCRIPT_NAME}-adding { background-color: #a8ffa8; } .CommentPostContainer-commentInput.${SCRIPT_NAME}-warning { background-color: #f4b0b0; } .${SCRIPT_NAME}-EasyCommentButton.${SCRIPT_NAME}-deleting { background-color: #ffb8b8; } .${SCRIPT_NAME}-EasyCommentButton.${SCRIPT_NAME}-deleting:hover { background-color: #ff7878; } `); addEditPanel(); for (const comment of storedData.getAllComments()) { addCommentButton(comment, false); } setMode(0); } function addCSS(cssText) { const styleElement = document.createElement("style"); styleElement.textContent = cssText; document.head.appendChild(styleElement); } function addEditPanel() { function createButton(text, onClick) { const button = document.createElement("a"); button.href = "javascript:void(0);"; button.textContent = `[${text}]`; if (onClick) { button.addEventListener("click", onClick); } return button; } const captionElement = document.querySelector(".EasyCommentContainer-caption"); const span0 = document.createElement("span"); span0.id = `${SCRIPT_NAME}-editButtonContainer-0`; span0.classList.add(`${SCRIPT_NAME}-editButtonContainer`); let lastWarningTime = 0; const addButton = createButton("追加", () => { const commentInput = document.querySelector(".CommentInput-textarea"); const text = commentInput.value; const succeed = addCommentButton(text); if (succeed) { commentInput.value = ""; getReactHandler(commentInput, "onChange")({ target: commentInput }); } else { const currentTime = Date.now(); if (currentTime - lastWarningTime >= 750) { const element = document.querySelector(".CommentPostContainer-commentInput"); const className = `${SCRIPT_NAME}-warning`; element.classList.add(className); window.setTimeout(() => element.classList.remove(className), 150); window.setTimeout(() => element.classList.add(className), 300); window.setTimeout(() => element.classList.remove(className), 450); window.setTimeout(() => element.classList.add(className), 600); window.setTimeout(() => element.classList.remove(className), 750); lastWarningTime = currentTime; } } }); addButton.addEventListener("mouseover", () => { document.querySelector(".CommentPostContainer-commentInput") .classList.add(`${SCRIPT_NAME}-adding`); }); addButton.addEventListener("mouseout", () => { document.querySelector(".CommentPostContainer-commentInput") .classList.remove(`${SCRIPT_NAME}-adding`); }); span0.appendChild(addButton); const deleteButton = createButton("削除", () => setMode(2)); deleteButton.id = `${SCRIPT_NAME}-deleteButton`; span0.appendChild(deleteButton); captionElement.appendChild(span0); const span1 = document.createElement("span"); span1.id = `${SCRIPT_NAME}-editButtonContainer-not0`; span1.classList.add(`${SCRIPT_NAME}-editButtonContainer`); span1.appendChild(createButton("削除完了", () => setMode(0))); captionElement.appendChild(span1); } function addCommentButton(text, saving = true) { if (saving) { if (/^\s*$/.test(text)) { return false; } if (storedData.has(text)) { return false; } storedData.add(text); } const container = document.querySelector(".EasyCommentContainer-easyComments"); const button = createCommentButton(text); container.appendChild(button); setMode(0); return true; } function removeCommentButton(text) { const container = document.querySelector(".EasyCommentContainer-easyComments"); for (const child of container.children) { if (child instanceof Element && child.classList.contains(`${SCRIPT_NAME}-EasyCommentButton`) && child.textContent === text ) { container.removeChild(child); break; } } storedData.delete(text); if (storedData.numberOfComments() === 0) { setMode(0); } } function createCommentButton(text) { const button = document.createElement("button"); button.classList.add("ActionButton"); button.classList.add("EasyCommentButton"); button.classList.add(`${SCRIPT_NAME}-EasyCommentButton`); const caption = document.createElement("div"); caption.classList.add("EasyCommentButton-caption"); caption.appendChild(createTextDisplay(text)); button.appendChild(caption); button.addEventListener("click", () => { if (button.classList.contains(`${SCRIPT_NAME}-deleting`)) { removeCommentButton(text); } else { postComment(text); } }); return button; } function createTextDisplay(text) { const element = document.createElement("pre"); element.classList.add(SCRIPT_NAME + "-pre"); element.textContent = text; return element; } async function postComment(text) { const commandInput = document.querySelector(".CommentCommandInput"); const commentInput = document.querySelector(".CommentInput-textarea"); const command0 = commandInput.value; const comment0 = commentInput.value; commentInput.value = text; getReactHandler(commentInput, "onChange")({ target: commentInput }); if (command0) { commandInput.value = ""; getReactHandler(commandInput, "onChange")({ target: commandInput }); await wait(15); } document.querySelector(".CommentPostButton").click(); await wait(1); commandInput.value = command0; commentInput.value = comment0; getReactHandler(commandInput, "onChange")({ target: commandInput }); getReactHandler(commentInput, "onChange")({ target: commentInput }); } function getReactHandler(element, handlerName) { for (const x in element) { if (typeof x === "string" && x.indexOf("reactEventHandlers") >= 0) { return element[x][handlerName]; } } } function animationFramePromise() { return new Promise(resolve => window.requestAnimationFrame(resolve)); } async function wait(frames) { for (let i = 0; i < frames; ++i) await animationFramePromise(); } function setMode(mode) { document.getElementById(`${SCRIPT_NAME}-editButtonContainer-0`).style.display = mode === 0 ? "" : "none"; document.getElementById(`${SCRIPT_NAME}-editButtonContainer-not0`).style.display = mode !== 0 ? "" : "none"; document.getElementById(`${SCRIPT_NAME}-deleteButton`).style.display = storedData.numberOfComments() > 0 ? "" : "none"; for (const button of document.querySelectorAll(`.${SCRIPT_NAME}-EasyCommentButton`)) { if (mode === 2) { button.classList.add(`${SCRIPT_NAME}-deleting`); } else { button.classList.remove(`${SCRIPT_NAME}-deleting`); } } } class StoredData { constructor(localStorageKey) { this.localStorageKey = localStorageKey; this.comments = new Set(["草"]); this._load(); } _encodeToString() { return JSON.stringify({ version: 1, comments: [...this.comments] }); } _decodeFromString(str) { const data = JSON.parse(str); this.comments = new Set(data.comments); } _load() { const str = localStorage.getItem(this.localStorageKey); if (str === null) { this._save(); return true; } try { this._decodeFromString(str); return true; } catch { return false; } } _save() { localStorage.setItem(this.localStorageKey, this._encodeToString()); } getAllComments() { return [...this.comments]; } has(text) { return this.comments.has(text); } add(text) { this.comments.add(text); this._save(); } delete(text) { const res = this.comments.delete(text); if (res) this._save(); return res; } numberOfComments() { return this.comments.size; } } const storedData = new StoredData(`${SCRIPT_NAME}-data`); init(); })();