您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
General Favoriates Adder for pixiv.net or other websites
// ==UserScript== // @name General Favoriates Adder // @namespace https://greasyfork.org/zh-CN/scripts/424020-general-favoriates-adder // @version 0.21.4.27 // @description General Favoriates Adder for pixiv.net or other websites // @author MangoPomelo // @include /^https?://safebooru\.org/index\.php.*id=.*$/ // @include /^https?://www\.pixiv\.net/artworks/.*$/ // @include /^https?://hitomi\.la/(doujinshi|gamecg|cg|manga)/.*?\.html$/ // @include /^https?://nozomi\.la/post/.*?\.html$/ // @grant none // ==/UserScript== (function() { 'use strict'; if (window != window.top){return undefined;} // iframe is not allowed let MODE = "PRODUCTION"; // "TUNNING" if want to tune the threshold and create new pattern, else use "PRODUCTION" let TEMPLATE = "{author}\t{URL}\t{character}\t{full_color}"; // placeholders must corresponding to the subjects in CONFIG let BUTTON = ` <div class="btn-circle-extract"> <svg id="extract" viewBox="0 0 1024 1024" width="24px" height="24px"> <path d="M160.41 522.24l90.47-90.47 45.21 45.26-90.42 90.42a192 192 0 0 0 271.36 271.82l90.47-90.47 45.21 45.21-90.47 90.47a256 256 0 0 1-361.83-362.24z m678.86-45.21l-90.47 90.42 45.21 45.26 90.47-90.47a256 256 0 0 0-362.24-361.83l-90.47 90.47 45.26 45.21 90.42-90.42a192 192 0 0 1 271.82 271.36z" fill="#ffffff"></path><path d="M341.35 642.97a42.7 42.7 0 1 0 60.16 60.62l302.08-302.08a42.65 42.65 0 1 0-60.62-60.16L341.35 642.97z" fill="#ffffff"></path> </svg> <svg id="check" width="21px" height="15px" viewBox="13 17 21 15"> <polyline points="32.5 18.5 20 31 14.5 25.5"></polyline> </svg> </div> `; let [MAIN_H, MAIN_S, MAIN_L] = [197, 85, 63]; // use HSL, not RGB let STYLE = ` <style> /* Based on: https://dribbble.com/shots/4525196-Jelly-Download */ .btn-circle-extract { position: fixed; bottom: 28px; left: 20px; height: 48px; width: 48px; margin: auto; border-radius: 100%; background: #FFF; opacity: 0.97; cursor: pointer; overflow: hidden; box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.16), 0px 1px 2px hsla(${MAIN_H}deg, ${MAIN_S}%, ${MAIN_L}%, 0.5); transition: all 0.5s ease; transition: bottom 1s ease; z-index: 999; } .btn-circle-extract::before { content: ""; position: absolute; top: 2px; left: 2px; right: 2px; bottom: 2px; border-radius: 50%; background: radial-gradient(circle, hsl(${MAIN_H}deg ,${MAIN_S * .75}%, ${MAIN_L}%) 0%, hsl(${MAIN_H}deg, ${MAIN_S}%, ${MAIN_L}%) 60%, hsl(${MAIN_H-5}deg, ${MAIN_S * .5}%, ${MAIN_L-5}%) 63%, hsl(0deg, 0%, 100%) 65%); } .btn-circle-extract svg { stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill: none; } .btn-circle-extract svg#extract { position: absolute; top: 12px; left: 12px; } .btn-circle-extract svg#check { position: absolute; top: 17px; left: 13px; stroke: white; transform: scale(0); } .btn-circle-extract.done { animation: rubberBand 0.8s; } .btn-circle-extract.done:after { transform: translateX(50%); transition: transform 0.4s ease; transition-delay: 0.7s; } .btn-circle-extract.done #extract { transform: scale(0); transition: all 0.2s ease; } .btn-circle-extract.done #check { transform: scale(1); transition: all 0.2s ease; transition-delay: 0.2s; } @keyframes rubberBand { from { transform: scale(1, 1, 1); } 30% { transform: scale3d(1.15, 0.75, 1); } 40% { transform: scale3d(0.75, 1.15, 1); } 50% { transform: scale3d(1.1, 0.85, 1); } 65% { transform: scale3d(0.95, 1.05, 1); } 75% { transform: scale3d(1.05, 0.95, 1); } to { transform: scale3d(1, 1, 1); } } </style> `; let CALLBACK = () => { document.querySelector(".btn-circle-extract").onclick = function() { document.querySelector(".btn-circle-extract").classList.add("done"); }; }; let CONFIG = { "author": { "type": "is", "resultMap": res => res? res.replace(/@.+/, ""): "", "evaluations": [ "#root > div:nth-child(2) > div.sc-1nr368f-0.kCKAFN > div > div.sc-1nr368f-3.iHKGIi > aside > section.sc-171jvz-1.sc-171jvz-3.sc-10r3j8-0.f30yhg-3.dfhJPe > h2 > div > div > a > div:nth-child(1)", // pixiv.net "#tag-sidebar > li.tag-type-artist.tag > a", // safebooru.org "body > div.container > div.content > div.gallery > h2", "body > div.container > div.content > div > div > table > tbody > tr:nth-child(1) > td:nth-child(2) > ul > li > a", // hitomi.la "body > div > div.sidebar > ul > li > a.artist", // nozomi.la ] }, "URL": { "type": "is", "resultMap": x => window.location.href.replace(location.hash, "") }, "character": { "type": "is", "ranker": { "threshold": 0, "patterns": [ {"reg": /^.*_.*$/, "weight": 5}, {"reg": /^.* .*$/, "weight": 3}, {"reg": /^.*\(.*\)$/, "weight": 15}, {"reg": /^.*・.*$/, "weight": 15}, {"reg": /^[ァ-ヴー]{4}$/u, "weight": 1}, {"reg": /^[ァ-ヴー]{2,3}$/u, "weight": 5}, {"reg": /^[ァ-ヴー]+$/u, "weight": 2}, {"reg": /^[一-龠ァ-ヴー]{2,5}$/u, "weight": 3}, {"reg": /^[一-龠]{3,4}$/u, "weight": 6}, {"reg": /^[一-龠]{1,3}[ァ-ヴーぁ-ゔ]{2,3}$/u, "weight": 5}, ] }, "evaluations": [ "#root > div:nth-child(2) > div.sc-1nr368f-0.kCKAFN > div > div.sc-1nr368f-3.iHKGIi > main > section > div.sc-171jvz-0.ketmXG > div > figcaption > div.sc-1u8nu73-13.KzfRK > div > footer > ul > li > span > span:nth-child(1)", // pixiv.net "#tag-sidebar > li.tag-type-character.tag > a", // safebooru.org "body > div > div.content > div > div > table > tbody > tr:nth-child(5) > td:nth-child(2) > ul > li", // hitomi.la "body > div > div.sidebar > ul > li > a.character", // nozomi.la ] }, "full_color": { "type": "has", "featureTags": ["Full Color"], "resultMap": res => (res || /.*(cg|post|artworks).*/.test(window.location.href))? "True": "False", "evaluations": [ "#root > div:nth-child(2) > div.sc-1nr368f-0.kCKAFN > div > div.sc-1nr368f-3.iHKGIi > main > section > div.sc-171jvz-0.ketmXG > div > figcaption > div.sc-1u8nu73-13.KzfRK > div > footer > ul > li > span > span:nth-child(1)", // pixiv.net "#tag-sidebar > li.tag-type-general > a", // safebooru.org "body > div > div.content > div > div > table > tbody > tr > td:nth-child(2) > ul > li", // hitomi.la "body > div > div.sidebar > ul > li > a.general", // nozomi.la ] } }; // Codes below class HasVerifier { constructor(subject) { this.dataLoaded = false; if (subject !== undefined) { // if subject exists this.setData(subject); } } setData(subject) { this.subject = subject; this.type = CONFIG[subject].type.toLowerCase(); this.featureTags = CONFIG[subject].featureTags; this.resultMap = CONFIG[subject].resultMap? CONFIG[subject].resultMap: res => res? "true": "false"; this.evaluations = CONFIG[subject].evaluations; if (this.type != 'has') { throw Error("<Class HasVerifier>: Only the type 'has' is accepted in HasVerifier, do you mean to use <Class IsVerifier>?") } this.dataLoaded = true; return this; } verify() { if (this.dataLoaded == false) { throw Error("<Class HasVerifier>: The configuration hasn't been loaded, use .setData(subj) to load the data") } let result = false; for (let selector of this.evaluations) { const candidates = document.querySelectorAll(selector); const tags = [...candidates].map(elem => elem.innerText); const tagsToCheck = this.featureTags; for (let tag of tags) { for (let tagToCheck of tagsToCheck) { if (tag.includes(tagToCheck)) { result = true; return this.resultMap(result); } } } } return this.resultMap(result); } } class IsVerifier { constructor(subject) { this.dataLoaded = false; if (subject !== undefined) { // if subject exists this.setData(subject); } } setData(subject) { // TODO: use destruction this.subject = subject; this.type = CONFIG[subject].type.toLowerCase(); this.resultMap = CONFIG[subject].resultMap? CONFIG[subject].resultMap: res => res? res: ""; this.evaluations = CONFIG[subject].evaluations? CONFIG[subject].evaluations: []; this.patterns = CONFIG[subject].ranker? CONFIG[subject].ranker.patterns? CONFIG[subject].ranker.patterns: [{"reg": /.*/, "weight": 1}]: [{"reg": /.*/, "weight": 1}]; this.threshold = CONFIG[subject].ranker? CONFIG[subject].ranker.threshold? CONFIG[subject].ranker.threshold: 0: 0; if (this.type != 'is') { throw Error("<Class IsVerifier>: Only the type 'is' is accepted in IsVerifier, do you mean to use <Class HasVerifier>?") } this.dataLoaded = true; return this; } verify() { if (this.dataLoaded == false) { throw Error("<Class IsVerifier>: The configuration hasn't been loaded, use .setData(subj) to load the data") } let globalCandidates = []; for (let selector of this.evaluations) { const candidates = document.querySelectorAll(selector); const tags = [...candidates].map(elem => elem.innerText); globalCandidates = [...globalCandidates, ...tags]; } let highestScore = -1; let correspondingCandidate = ""; for (let candidate of globalCandidates) { if (MODE == "TUNNING") { console.log(`${candidate}:`); } let score = 0; for (let pattern of this.patterns) { let reg = pattern.reg; if (reg.test(candidate)) { score += pattern.weight; if (MODE == "TUNNING") { console.log(` ${reg} +${pattern.weight}`); } } } if (score > highestScore) { highestScore = score; correspondingCandidate = candidate; } if (MODE == "TUNNING") { console.log(` (${score}/${this.threshold})`); } } return this.resultMap(highestScore >= this.threshold? correspondingCandidate: null); } } class Verifier { constructor(subject) { this.innerHasVerifier = new HasVerifier(); this.innerIsVerifier = new IsVerifier(); this.currentVerifier = null; this.dataLoaded = false; if (subject !== undefined) { // if subject exists this.setData(subject); } } setData(subject) { const type = CONFIG[subject].type.toLowerCase(); if (type == 'is') { this.currentVerifier = this.innerIsVerifier; } else if (type == 'has') { this.currentVerifier = this.innerHasVerifier; } else { throw Error('<Class Verifier>: Type must be either "is" or "has"'); } this.dataLoaded = true; this.currentVerifier.setData(subject); return this; } verify() { if (this.dataLoaded == false) { throw Error("<Class Verifier>: The configuration hasn't been loaded, use .setData(subj) to load the data") } return this.currentVerifier.verify(); } } class Formatter { fill(pairs) { let newString = `${TEMPLATE}`; for (let subject in pairs) { let key = subject; let value = pairs[subject]; newString = newString.replace("{" + key + "}", value); } return newString; } } const copyToClipboard = str => { const el = document.createElement('textarea'); el.value = str; el.setAttribute('readonly', ''); el.style.position = 'absolute'; el.style.left = '-9999px'; document.body.appendChild(el); el.select(); document.execCommand('copy'); document.body.removeChild(el); }; const extract = () => { let elem = document.querySelector(".btn-circle-extract"); let v = new Verifier(); let f = new Formatter(); let pairs = {}; for (let subject in CONFIG) { let value = v.setData(subject).verify(); pairs[subject] = value; } let result = f.fill(pairs); copyToClipboard(result); setTimeout(()=>{ elem.style.bottom = "-200px"; }, 3000); setTimeout(()=>{ elem.remove(); }, 5000); }; (()=>{ document.querySelector('head').insertAdjacentHTML('afterbegin', STYLE); document.querySelector("body").insertAdjacentHTML('afterbegin', BUTTON); document.querySelector(".btn-circle-extract").addEventListener("click", extract); if (CALLBACK !== undefined) { CALLBACK(); } })(); })();