您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Extention for NovelAI Image Generator. Fast Suggestion, Chant, Save in JPEG, Get aspect ratio.
当前为
// ==UserScript== // @name NovelAI Mod // @namespace https://www6.notion.site/dc99953d5f04405c893fba95dace0722 // @version 14 // @description Extention for NovelAI Image Generator. Fast Suggestion, Chant, Save in JPEG, Get aspect ratio. // @author SenY // @match https://novelai.net/image // @icon https://www.google.com/s2/favicons?sz=64&domain=novelai.net // @grant none // @license MIT // ==/UserScript== (function () { const gcd = function (a, b) { if (b == 0) return a; return gcd(b, a % b); } const getAspect = function (whArray) { return whArray.map(x => x / gcd(whArray[0], whArray[1])); } const aspectList = function (wa, ha, maxpixel) { if (!maxpixel) { maxpixel = 1024 * 1024; } let aspect = wa / ha; let limit = 16384; let steps = 64; let ws = [] for (let i = steps; i <= limit; i += steps) { ws.push(i); } let hs = ws.slice(0, ws.length + 1); let results = []; ws.forEach(w => { hs.forEach(h => { if (w / h == aspect && w * h <= maxpixel) { results.push([w, h]); } }) }); return results; } let suggested_at = new Date(); const colors = { "-1": ["red", "maroon"], "0": ["lightblue", "dodgerblue"], "1": ["gold", "goldenrod"], "3": ["violet", "darkorchid"], "4": ["lightgreen", "darkgreen"], "5": ["tomato", "darksalmon"], "6": ["red", "maroon"], "7": ["whitesmoke", "black"], "8": ["seagreen", "darkseagreen"] } const getChantURL = function (force) { if (force === true) { localStorage.removeItem("chantURL"); } let chantURL = localStorage.getItem("chantURL") || prompt("Input your chants json url.\nThe URL must be a Cors-enabled server (e.g., gist.github.com).", "https://gist.githubusercontent.com/vkff5833/989808aadebf8648831955cdf2a7b3e3/raw/yuuri.json"); if (chantURL) { localStorage.setItem("chantURL", chantURL); } return chantURL; } let chantURL = getChantURL(null); let seedButton; const saveJpeg = function () { let seed = Array.from(seedButton.querySelectorAll("span")).map(x => x.textContent).find(x => x.search(/^[0-9]*$/) == 0); let filename = seed + ".jpg"; let imgs = Array.from(document.querySelectorAll('img[src]')).filter(x => x.offsetParent); let url = imgs.find(x => x.height == Math.max.apply(null, imgs.map(x => x.height))).getAttribute("src"); if (!url) { url = document.querySelector('img[src]').getAttribute("src"); } let blob = fetch(url).then(r => r.blob()).then(blob => { let fileReader = new FileReader(); fileReader.onload = function () { let dataURI = this.result; let canvas = document.createElement("canvas"); let context = canvas.getContext("2d"); let image = new Image(); image.src = dataURI; image.onload = function () { canvas.width = image.width; canvas.height = image.height; context.drawImage(image, 0, 0); let JPEG = canvas.toDataURL("image/jpeg", document.getElementById("jpegQuality").value - 0); let link = document.createElement('a'); link.href = JPEG; link.download = filename; link.click(); }; } fileReader.readAsDataURL(blob); }); } let jpegButton = document.createElement("div"); let jpegQuality = document.createElement("div"); jpegButton.textContent = "JPEG"; jpegButton.addEventListener("click", () => { saveJpeg(); }); jpegQuality.textContent = ""; let input = document.createElement("input"); input.value = 0.85; input.setAttribute("id", "jpegQuality"); input.setAttribute("type", "number"); input.setAttribute("step", "0.01"); input.setAttribute("max", "1"); input.setAttribute("min", "0"); input.style.maxWidth = "3rem"; jpegQuality.appendChild(input); const changePixel = function (w, h) { let wh = [w, h]; let i = 0; document.querySelectorAll('input[type="number"][step="64"]').forEach(x => { x.value = wh[i]; x._valueTracker = ''; x.dispatchEvent(new Event('input', { bubbles: true })); i++; }); } const putUI = function (ui) { document.querySelectorAll("textarea[placeholder]").forEach(textarea => { if (textarea.offsetParent) { textarea.parentElement.appendChild(ui); } }); seedButton = Array.from(document.querySelectorAll('span[style]')).find(x => x.textContent.trim().search(/^[0-9]*(seed|シード)/i) == 0); if (seedButton) { seedButton = seedButton.closest("button"); seedButton.classList.forEach(x => { jpegButton.classList.add(x); jpegQuality.classList.add(x); }); seedButton.parentNode.insertBefore(jpegButton, seedButton); seedButton.parentNode.insertBefore(jpegQuality, seedButton); } let widthInput = document.querySelector('input[type="number"][step="64"]'); if (widthInput) { let heightInput = document.querySelectorAll('input[type="number"][step="64"]')[1]; let displayAspect = document.querySelector("#displayAspect"); if (!displayAspect) { displayAspect = document.createElement("div"); displayAspect.setAttribute("id", "displayAspect"); let container = document.createElement("div"); container.style.maxWidth = "130px"; container.style.display = "flex"; let widthAspect = document.createElement("input"); let heightAspect = document.createElement("input"); let largeCheck = document.createElement("input"); let submitButton = document.createElement("input"); widthAspect.setAttribute("type", "number"); heightAspect.setAttribute("type", "number"); largeCheck.setAttribute("type", "checkbox"); largeCheck.setAttribute("title", "Check to large size."); submitButton.setAttribute("type", "submit"); submitButton.value = "Aspect => Pixel"; widthInput.classList.forEach(x => { widthAspect.classList.add(x); heightAspect.classList.add(x); }); container.appendChild(widthAspect); container.appendChild(heightAspect); container.appendChild(largeCheck); displayAspect.appendChild(container); displayAspect.appendChild(submitButton); widthInput.parentNode.appendChild(displayAspect); const changePixelByAspect = function () { let wa = widthAspect.value; let ha = heightAspect.value; let maxpixel = 1024 * 1024; if (largeCheck.checked) { maxpixel = 1664 * 1664; } let as = aspectList(wa, ha, maxpixel); if (as.length) { let wh = as[as.length - 1]; changePixel(wh[0], wh[1]); } } submitButton.addEventListener("click", function () { changePixelByAspect(); }); } else { let widthAspect = displayAspect.querySelectorAll("input")[0]; let heightAspect = displayAspect.querySelectorAll("input")[1]; let Aspect = getAspect(Array.from(document.querySelectorAll('input[type="number"][step="64"]')).map(x => x.value)); widthAspect.value = Aspect[0]; heightAspect.value = Aspect[1]; } } } let allTags = []; fetch("https://gist.githubusercontent.com/vkff5833/275ccf8fa51c2c4ba767e2fb9c653f9a/raw/danbooru.json", { method: "GET", }).then((response) => response.json()) .then((data) => { allTags = data; fetch("https://gist.githubusercontent.com/vkff5833/275ccf8fa51c2c4ba767e2fb9c653f9a/raw/danbooru_wiki.slim.json", { method: "GET", }).then((response) => response.json()) .then((wikiPages) => { allTags = allTags.map(x => { let wikiPage = wikiPages.find(y => y.name == x.name); if (wikiPage) { x.terms = x.terms.concat(wikiPage.otherNames); } return x; }); }); }); let chants = []; const getTargetTag = function () { let textarea = document.querySelector("textarea[placeholder]"); if (textarea) { let oldTags = textarea.value.split(",").map(x => x.trim()); let beforeTags = textarea.value.slice(0, textarea.selectionEnd).split(",").map(x => x.trim()); let targetTag = beforeTags[beforeTags.length - 2]; targetTag = oldTags[oldTags.indexOf(targetTag) + 1]; return targetTag.trim(); } else { return null; } }; const Crease = function (value) { const getTagPower = function (tag) { let tagPower = 0; tagPower += tag.split("").filter(x => x == "{").length; tagPower -= tag.split("").filter(x => x == "[").length; return tagPower; } let textarea = document.querySelector("textarea[placeholder]"); let selectionEnd = textarea.selectionEnd; let oldTags = textarea.value.split(",").map(x => x.trim()); let targetTag = getTargetTag(); let tags = []; if (targetTag) { oldTags.forEach(tag => { if (tag == targetTag) { let tagPower = getTagPower(targetTag) + value; tag = tag.replace(/[\{\}\[\]]/g, "", tag); for (let i = 0; i < Math.abs(tagPower); i++) { if (tagPower > 0) { tag = '{' + tag + '}'; } if (tagPower < 0) { tag = '[' + tag + ']'; } } } tags.push(tag); }); tags = tags.filter(x => x); textarea.textContent = tags.join(", ").replace(/^, /g, ""); textarea.value = tags.join(", ").replace(/^, /g, "") + ", "; textarea._valueTracker = ''; textarea.dispatchEvent(new Event('input', { bubbles: true })); textarea.focus(); textarea.selectionEnd = selectionEnd; } } const Append = function (key, value) { let targetTag = getTargetTag(); let text; let newTags; let textarea = document.querySelector("textarea[placeholder]"); let selectionEnd = textarea.selectionEnd; let oldTags = textarea.value.split(",").map(x => x.trim()); if (key) { let chant = chants.find(x => x.name == key.trim()); if (chant) { newTags = chant.content.split(",").map(x => x.trim()); } } if (value) { newTags = [value.trim()]; } if (newTags.length) { //let tags = oldTags.concat(newTags).filter(x => x); let tags = []; oldTags.forEach(tag => { if (tag == targetTag) { if (key) { tags.push(tag); } newTags.forEach(newTag => { tags.push(newTag); }); } else { tags.push(tag); } }); if (!targetTag) { tags = tags.concat(newTags); } tags = Array.from(new Set(tags.filter(x => x))); textarea.textContent = tags.join(", ").replace(/^, /g, ""); textarea.value = tags.join(", ").replace(/^, /g, "") + ", "; // Special Thanks to https://fate.5ch.net/test/read.cgi/liveuranus/1702763225/730 textarea._valueTracker = ''; textarea.dispatchEvent(new Event('input', { bubbles: true })); textarea.focus(); textarea.selectionEnd = selectionEnd; } } const Suggest = function () { let textarea = document.querySelector("textarea[placeholder]"); if (textarea) { let tags = textarea.value.split(",").map(x => x.trim()); if (tags.length) { let targetTag = getTargetTag().replace(/[\\^$.*+?()[\]{}|]/g, '\\$&'); document.getElementById("suggestionField").textContent = ""; let suggestions = []; if (targetTag) { suggestions = allTags.filter(x => x.name.search(targetTag.replace(/_/g, " ").replace(/[\{\}\[\]\\]/g, "")) >= 0).slice(0, 10); suggestions = suggestions.concat(allTags.filter(x => { return x.terms.map(y => y.search(targetTag.replace(/_/g, " ").replace(/[\{\}\[\]\\]/g, "")) >= 0).includes(true); }).slice(0, 20)); } let done = new Set(); suggestions.forEach(tag => { if (!done.has(tag.name)) { let button = document.createElement("button"); let count = tag.coumt; if (count > 1000) { count /= 1000; count = Math.round(count * 10) / 10; count += "k"; } button.textContent = tag.name + " (" + count + ")"; button.style.color = colors[tag.category][1]; button.setAttribute("title", tag.terms.map(x => x.trim()).filter(x => x).join(", ")); button.addEventListener("click", function () { Append(null, tag.name); }); document.getElementById("suggestionField").appendChild(button); if (textarea.value.split(",").map(y => y.trim().replace(/_/g, " ").replace(/[\{\}\[\]\\]/g, "")).includes(tag.name.replace(/_/g, " "))) { button.style.opacity = 0.5; } } done.add(tag.name); }); } } } const Build = function () { let ui = document.getElementById("NAIModUi"); if (!ui) { ui = document.createElement("div"); ui.setAttribute("id", "NAIModUi"); ui.style.padding = "0.5rem"; } ui.textContent = ""; let hr = document.createElement("hr"); hr.style.color = "grey"; hr.style.borderWidth = "2px"; let optionField = document.createElement("div"); let chantsField = document.createElement("div"); let suggestionField = document.createElement("div"); suggestionField.setAttribute("id", "suggestionField"); ui.appendChild(optionField); ui.appendChild(hr.cloneNode()); ui.appendChild(chantsField); ui.appendChild(hr.cloneNode()); ui.appendChild(suggestionField); let resetButton = document.createElement("button"); resetButton.textContent = "Reset Chants URL"; resetButton.style.color = "red"; resetButton.addEventListener("click", function () { chantURL = getChantURL(true); Build(); }); optionField.appendChild(resetButton); let clearButton = document.createElement("button"); clearButton.textContent = "Clear Suggestion"; clearButton.style.color = "red"; clearButton.addEventListener("click", function () { document.getElementById("suggestionField").textContent = ""; }); optionField.appendChild(clearButton); fetch(chantURL, { method: "GET", }).then((response) => response.json()) .then((data) => { chants = data; chants.forEach(chant => { let button = document.createElement("button"); button.textContent = chant.name; button.style.color = colors[chant.color][1]; button.addEventListener("click", function () { Append(chant.name, null); }); chantsField.appendChild(button); }); putUI(ui); }); } document.addEventListener("blur", function (event) { if (event.target.tagName.toLowerCase() === 'button') { Build(); } }, true); let init = setInterval(() => { document.querySelectorAll("textarea[placeholder]").forEach(textarea => { if (textarea.offsetParent) { clearInterval(init); Build(); } }); }); document.addEventListener("keydown", function (e) { if (e.ctrlKey == true && e.code == "ArrowUp") { e.preventDefault(); Crease(1); } if (e.ctrlKey == true && e.code == "ArrowDown") { e.preventDefault(); Crease(-1); } }); const SUGGESTION_LIMIT = 300; document.addEventListener("keyup", function (e) { let typed = new Date(); if (typed - suggested_at > SUGGESTION_LIMIT && e.target.tagName == "TEXTAREA") { Suggest(); suggested_at = typed; } }); document.addEventListener("click", function (e) { if (e.target.tagName == "TEXTAREA") { Suggest(); } }); })();