Pixiv Utils

Utilities for Pixiv

当前为 2024-09-29 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name		Pixiv Utils
// @namespace	https://greasyfork.org/en/scripts/510779-pixiv-utils/
// @version		2024-09-29
// @description	Utilities for Pixiv
// @author		V.H.
// @copyright	V.H.
// @match		*://*.pixiv.net/*
// @icon		https://icons.duckduckgo.com/ip2/pixiv.net.ico
// @grant		unsafeWindow
// @grant		GM_log
// @grant		GM_registerMenuCommand
// @grant		GM_unregisterMenuCommand
// @grant		GM_addStyle
// @grant		GM_getValue
// @grant		GM_setValue
// @run-at		document-start
// @tag			utilities
// @connect		self
// @webRequest	[{"selector":"ads-pixiv.net","action":"cancel"}]
// @license		MIT
// @supportURL	https://greasyfork.org/en/scripts/510779-pixiv-utils/feedback
// ==/UserScript==

"use strict";

const	sep			= /[\s\n]*[,\n][\s\n]*/gmis,
		idsep		= /\//gi,
		idmatch		= /^\d+/i,
		showc		= /show all|reading works?/i,
		prems		= [
			[ /(?<=['"]_setCustomVar['"]\s*?,\s*?\d+?\s*?,\s*?['"]plan['"]\s*?,\s*?['"])(normal)(?=['"])/gmis, "premium" ],
			[ /(?<=['"]?premium['"]?\s*?:\s*?['"])(no)(?=['"])/gmis, "yes" ],
			[ /(?<=['"]?plan['"]?\s*?:\s*?['"])(normal)(?=['"])/gmis, "premium" ],
			[ /(?<=['"]?premium['"]?\s*?:\s*?['"]?)(false|0|no)(?=['"]?)/gmis, "true" ],
		];
let		cfg			= {
	name:		"Pixiv Utils",
	prefix:		"gm_vh_pxvutl_",
	base:		"https://www.pixiv.net/en/artworks/",
	intr:		1000,
	baseopts:	{
		
	},
	panel:		{
		name:					"Panel",
		title:					"Panel",
		accessKey:				"P",
		autoClose:				true,
		id:						null,
	},
	observe:	{
		subtree:				true,
		childList:				true,
		characterData:			false,
		characterDataOldValue:	false,
		attributes:				true,
		attributeOldValue:		false,
		attributeFilter:		[ "src", "alt", "title" ],
	},
	style:		`
		.gm_vh_pxvutl_ {
			user-select: contain;
			text-shadow: 1px 1px 1px black;
		}
		.gm_vh_pxvutl_.row {
			display: flex;
			flex-flow: row wrap;
			justify-content: center;
			align-items: stretch;
			align-content: stretch;
			gap: 5px;
		}
		div:has(> div[width][height] .gm_vh_pxvutl_violet), div[type][size]:has(.gm_vh_pxvutl_violet) {
			border: medium dashed violet !important;
		}
		div:has(> div[width][height] .gm_vh_pxvutl_high), div[type][size]:has(.gm_vh_pxvutl_high) {
			border: medium dashed yellow !important;
		}
		div:has(> div[width][height] .gm_vh_pxvutl_green), div[type][size]:has(.gm_vh_pxvutl_green) {
			border: medium dashed green !important;
		}
		.gm_vh_pxvutl_block:not(.gm_vh_pxvutl_blue) {
			visibility: collapse !important;
			cursor: not-allowed;
		}
		div:has(> div[width][height] .gm_vh_pxvutl_block), div[type][size]:has(.gm_vh_pxvutl_block) {
			border: thick double red !important;
		}
		div:has(> div[width][height] .gm_vh_pxvutl_blue), div[type][size]:has(.gm_vh_pxvutl_blue) {
			visibility: visible !important;
			border-width: thick !important;
			border-style: dashed;
			border-color: blue !important;
		}
		.gm_vh_pxvutl_hide, div:has(> div[width][height] .gm_vh_pxvutl_hide), div[type][size]:has(.gm_vh_pxvutl_hide), div:has(> div > div[width][height] .gm_vh_pxvutl_hide, > div[type][size] .gm_vh_pxvutl_hide), li:has(> div > div > div[width][height] .gm_vh_pxvutl_hide, > div > div[type][size] .gm_vh_pxvutl_hide) {
			display: none !important;
			visibility: hidden !important;
			opacity: 0 !important;
			pointer-events: none !important;
			cursor: not-allowed !important;
		}
		
		:where(h1, h2, h3, h4).gm_vh_pxvutl_ {
			text-decoration: underline;
			margin: 5px;
		}
		label.gm_vh_pxvutl_ {
			user-select: none;
		}
		label.gm_vh_pxvutl_::after {
			content: ": ";
		}
		
		textarea.gm_vh_pxvutl_ {
			resize: both;
			border-radius: 5px;
			padding: 5px;
			margin: 5px;
			background: revert;
		}
		
		.gm_vh_pxvutl_:is(button, input, select) {
			cursor: pointer !important;
			opacity: .8;
			padding: 5px;
			margin: 5px;
			box-shadow: 1px 1px 1px 0 black;
			border-radius: 5px;
			background: revert;
			transition: all 200ms;
		}
		.gm_vh_pxvutl_:is(button, input, select):hover {
			box-shadow: 2px 2px 1px 1px black;
			opacity: .9;
			padding: 7px;
			border-radius: 7px;
		}
		.gm_vh_pxvutl_:is(button, input, select):active {
			box-shadow: 2px 2px 2px 2px black;
			opacity: 1;
			border-radius: 10px;
			padding: 8px;
		}
		
		#gm_vh_pxvutl_panel {
			display: flex;
			flex-flow: column nowrap;
			position: fixed !important;
			justify-content: space-between;
			align-items: stretch;
			align-content: stretch;
			resize: both !important;
			gap: 5px;
			bottom: 1vh !important;
			left: 1vw !important;
			min-width: 10vw;
			width: 30vw;
			max-width: 40vw;
			min-height: 10vh;
			height: 95vh;
			max-height: 100vh;
			padding: 5px;
			margin: 5px;
			border-radius: 5px;
			z-index: 999 !important;
			background: radial-gradient(circle farthest-side at center, rgba(230, 230, 230, 1) 0%, rgba(150, 150, 150, .7) 90%);
			overflow: auto;
			user-select: contain;
			pointer-events: none;
		}
		#gm_vh_pxvutl_panel * {
			pointer-events: auto;
		}
		#gm_vh_pxvutl_panel_close {
			position: sticky;
			top: 5px;
			right: 5px;
		}
	`,
},		data		= {
	enabled:	false,
	exposed:	true,
	show:		true,
	violet:		"",
	high:		"",
	green:		"",
	block:		"",
	blue:		"",
	hide:		"",
},		css			= GM_addStyle(cfg.style),
		observer	= new MutationObserver(see),
		tags		= {
			violet:	[ ],
			high:	[ ],
			green:	[ ],
			block:	[ ],
			blue:	[ ],
			hide:	[ ],
		},
		intr		= null;

document.addEventListener("DOMContentLoaded", premiumUnlock, true);

function start() {
	cfg.panel.id	= GM_registerMenuCommand(cfg.panel.name, panel, cfg.panel);
	
	data			= Object.assign(data, GM_getValue(getPrefixed("settings"), data));
	
	update();
	
	GM_log(`--- '${cfg.name}' has started.`);
} //start
function stop() {
	GM_unregisterMenuCommand(cfg.panel.id);
	css.remove();
	clearInterval(intr);
} //stop

function panel(e) {
	const	root		= document.createElement("dialog"),
			title		= document.createElement("h3"),
			close		= document.createElement("button"),
			statesp		= document.createElement("span"),
			statelb		= document.createElement("label"),
			state		= document.createElement("input"),
			expsp		= document.createElement("span"),
			explb		= document.createElement("label"),
			exp			= document.createElement("input"),
			showsp		= document.createElement("span"),
			showlb		= document.createElement("label"),
			show		= document.createElement("input"),
			violetsp	= document.createElement("span"),
			violetlb	= document.createElement("label"),
			violet		= document.createElement("textarea"),
			highsp		= document.createElement("span"),
			highlb		= document.createElement("label"),
			high		= document.createElement("textarea"),
			greensp		= document.createElement("span"),
			greenlb		= document.createElement("label"),
			green		= document.createElement("textarea"),
			blocksp		= document.createElement("span"),
			blocklb		= document.createElement("label"),
			block		= document.createElement("textarea"),
			bluesp		= document.createElement("span"),
			bluelb		= document.createElement("label"),
			blue		= document.createElement("textarea"),
			hidesp		= document.createElement("span"),
			hidelb		= document.createElement("label"),
			hide		= document.createElement("textarea");
	
	root.id				= getPrefixed("panel");
	root.classList.add(getPrefixed());
	root.setAttribute("open", "");
	
	{
		const	e	= document.getElementById(root.id);
		
		if (e)	e.remove();
	}
	
	title.id			= getPrefixed("panel_title");
	title.classList.add(getPrefixed());
	title.innerHTML		= "Pixiv Utils Control Panel";
	
	close.id			= getPrefixed("panel_close");
	close.classList.add(getPrefixed());
	close.innerHTML		= "Close";
	close.onclick		= () => {
		const	root	= document.getElementById(getPrefixed("panel"));
		
		root.close();
		root.remove();
	};
	
	state.id			= getPrefixed("panel_enable");
	state.type			= "checkbox";
	state.classList.add(getPrefixed());
	state.onchange		= () => {
		data.enabled	= state.checked;
		update();
	};
	if (data.enabled)	state.setAttribute("checked", "");
	
	statelb.classList.add(getPrefixed());
	statelb.htmlFor		= state.id;
	statelb.innerHTML	= "Enable";
	
	exp.id			= getPrefixed("panel_expose");
	exp.type			= "checkbox";
	exp.classList.add(getPrefixed());
	exp.onchange		= () => {
		data.exposed	= exp.checked;
		update();
	};
	if (data.exposed)	exp.setAttribute("checked", "");
	
	explb.classList.add(getPrefixed());
	explb.htmlFor		= exp.id;
	explb.title			= "The Alt field of images contains the tags";
	explb.innerHTML		= "Expose Alt";
	
	show.id				= getPrefixed("panel_show");
	show.type			= "checkbox";
	show.classList.add(getPrefixed());
	show.onchange		= () => {
		data.show		= show.checked;
		update();
	};
	if (data.show)		show.setAttribute("checked", "");
	
	showlb.classList.add(getPrefixed());
	showlb.htmlFor		= show.id;
	showlb.innerHTML	= "Auto Show-All";
	
	violet.id			= getPrefixed("panel_violet");
	violet.classList.add(getPrefixed());
	violet.value		= data.violet;
	violet.cols			= 50;
	violet.rows			= 3;
	violet.placeholder	= "tag1, tag2, ...";
	violet.setAttribute("spellcheck", "false");
	violet.onchange		= () => {
		data.violet		= violet.value;
		update();
	};
	
	violetlb.classList.add(getPrefixed());
	violetlb.htmlFor	= violet.id;
	violetlb.innerHTML	= "Violet Tags";
	
	high.id				= getPrefixed("panel_high");
	high.classList.add(getPrefixed());
	high.value			= data.high;
	high.cols			= 50;
	high.rows			= 3;
	high.placeholder	= "tag1, tag2, ...";
	high.setAttribute("spellcheck", "false");
	high.onchange		= () => {
		data.high		= high.value;
		update();
	};
	
	highlb.classList.add(getPrefixed());
	highlb.htmlFor		= high.id;
	highlb.innerHTML	= "Highlighted Tags";
	
	green.id			= getPrefixed("panel_green");
	green.classList.add(getPrefixed());
	green.value			= data.green;
	green.cols			= 50;
	green.rows			= 3;
	green.placeholder	= "tag1, tag2, ...";
	green.setAttribute("spellcheck", "false");
	green.onchange		= () => {
		data.green		= green.value;
		update();
	};
	
	greenlb.classList.add(getPrefixed());
	greenlb.htmlFor		= green.id;
	greenlb.innerHTML	= "Green Tags";
	
	block.id			= getPrefixed("panel_block");
	block.classList.add(getPrefixed());
	block.value			= data.block;
	block.cols			= 50;
	block.rows			= 3;
	block.placeholder	= "tag1, tag2, ...";
	block.setAttribute("spellcheck", "false");
	block.onchange		= () => {
		data.block		= block.value;
		update();
	};
	
	blocklb.classList.add(getPrefixed());
	blocklb.htmlFor		= block.id;
	blocklb.innerHTML	= "Blocked Tags";
	
	blue.id			= getPrefixed("panel_blue");
	blue.classList.add(getPrefixed());
	blue.value			= data.blue;
	blue.cols			= 50;
	blue.rows			= 3;
	blue.placeholder	= "tag1, tag2, ...";
	blue.setAttribute("spellcheck", "false");
	blue.onchange		= () => {
		data.blue		= blue.value;
		update();
	};
	
	bluelb.classList.add(getPrefixed());
	bluelb.htmlFor		= blue.id;
	bluelb.innerHTML	= "Blue Tags";
	
	hide.id				= getPrefixed("panel_hide");
	hide.classList.add(getPrefixed());
	hide.value			= data.hide;
	hide.cols			= 50;
	hide.rows			= 3;
	hide.placeholder	= "tag1, tag2, ...";
	hide.setAttribute("spellcheck", "false");
	hide.onchange		= () => {
		data.hide		= hide.value;
		update();
	};
	
	hidelb.classList.add(getPrefixed());
	hidelb.htmlFor		= hide.id;
	hidelb.innerHTML	= "Hide Tags";
	
	title.classList.add(getPrefixed(), "row");
	statesp.classList.add(getPrefixed(), "row");
	expsp.classList.add(getPrefixed(), "row");
	showsp.classList.add(getPrefixed(), "row");
	blocksp.classList.add(getPrefixed(), "row");
	highsp.classList.add(getPrefixed(), "row");
	greensp.classList.add(getPrefixed(), "row");
	bluesp.classList.add(getPrefixed(), "row");
	violetsp.classList.add(getPrefixed(), "row");
	hidesp.classList.add(getPrefixed(), "row");
	
	statesp.append(statelb, state);
	expsp.append(explb, exp);
	showsp.append(showlb, show);
	violetsp.append(violetlb, violet);
	highsp.append(highlb, high);
	greensp.append(greenlb, green);
	blocksp.append(blocklb, block);
	bluesp.append(bluelb, blue);
	hidesp.append(hidelb, hide);
	root.append(title, close, violetsp, highsp, greensp, blocksp, bluesp, hidesp, expsp, showsp, statesp);
	document.body.appendChild(root);
	
	root.show();
	
	GM_log(`--- '${cfg.name}' Panel opened.`);
} //panel

function update() {
	GM_setValue(getPrefixed("settings"), data);
	
	tags.violet	= data.violet.trim().split(sep).map(t => t.trim()).filter(t => t).map(r => new RegExp(wrap(r, "\\b"), "i"));
	tags.high	= data.high.trim().split(sep).map(t => t.trim()).filter(t => t).map(r => new RegExp(wrap(r, "\\b"), "i"));
	tags.green	= data.green.trim().split(sep).map(t => t.trim()).filter(t => t).map(r => new RegExp(wrap(r, "\\b"), "i"));
	tags.block	= data.block.trim().split(sep).map(t => t.trim()).filter(t => t).map(r => new RegExp(wrap(r, "\\b"), "i"));
	tags.blue	= data.blue.trim().split(sep).map(t => t.trim()).filter(t => t).map(r => new RegExp(wrap(r, "\\b"), "i"));
	tags.hide	= data.hide.trim().split(sep).map(t => t.trim()).filter(t => t).map(r => new RegExp(wrap(r, "\\b"), "i"));
	
	if (data.enabled)	observer.observe(document, cfg.observe);
	else				observer.disconnect();
	if (data.show)		intr	= setInterval(timed, cfg.intr);
	else				clearInterval(intr);
	
	GM_log("Update.");
} //update

function timed() {
	const	show	= Array.from(document.querySelectorAll("button:not(.hidden)")).find(b => showc.test(b.innerText.trim().toLowerCase()));
	
	if (show) {
		show.click();
		show.classList.add("hidden");
	}
} //timed

start();

function premiumUnlock() {
	const	scripts	= Array.from(document.scripts).filter(s => s.innerText.trim()),
			metas	= Array.from(document.querySelectorAll("meta[content]")).filter(m => m.content.trim());
	
	GM_log(`PREMS:\t${prems.length}\t${scripts.length}\t${metas.length}`);
	
	for (let script of scripts) {
		prems.forEach(p => {
			script.innerText	= script.innerText.replaceAll(p[0], p[1]);
		});
	}
	for (let meta of metas) {
		prems.forEach(p => {
			meta.content	= meta.content.replaceAll(p[0], p[1]);
		});
	}
	
	GM_log(`--- '${cfg.name}' has premium-unlocked.`);
} //premiumUnlock

function see(e, o) {
	for (let mut of e) {
		mut.addedNodes && mut.addedNodes.forEach(nd => {
			if (nd.tagName && nd.tagName.toLowerCase() == "img" && nd.alt)	process(nd);
		});
	}
} //see
function process(img, skip = false) {
	GM_log(`IMG:\t${img.src}\t${img.alt}`);
	
	const	alt	= img.alt.trim().toLowerCase().split(sep).map(t => t.trim()).filter(r => r);
	
	if (rule(tags.violet, alt)) {
		img.classList.add(getPrefixed("violet"));
		GM_log(`VIOLET:\t${img.src}\t${img.alt}`);
	}
	if (rule(tags.high, alt)) {
		img.classList.add(getPrefixed("high"));
		GM_log(`HIGH:\t${img.src}\t${img.alt}`);
	}
	if (rule(tags.green, alt)) {
		img.classList.add(getPrefixed("green"));
		GM_log(`GREEN:\t${img.src}\t${img.alt}`);
	}
	if (rule(tags.block, alt)) {
		img.classList.add(getPrefixed("block"));
		GM_log(`BLOCK:\t${img.src}\t${img.alt}`);
	}
	if (rule(tags.blue, alt)) {
		img.classList.add(getPrefixed("blue"));
		GM_log(`BLUE:\t${img.src}\t${img.alt}`);
	}
	if (rule(tags.hide, alt)) {
		img.classList.add(getPrefixed("hide"));
		GM_log(`HIDE:\t${img.src}\t${img.alt}`);
	}
	
	if (data.exposed)	img.parentNode.title	= img.title	= img.alt.trim();
	if (skip)	return;
	
	getData(img).then(tags => {
		img.alt	+= ", " + tags.join(", ");
		
		process(img, true);
	});
} //process

async function getData(img) {
	try {
		const	id		= getID(img.src),
				res		= await fetch(cfg.base + id, cfg.baseopts),
				dat		= await res.text(),
				tree	= Document.parseHTMLUnsafe(dat).querySelector("meta[name='preload-data']"),
				json	= JSON.parse(tree.content)["illust"][id]["tags"]["tags"],
				tags	= [ ];
		
		for (let tag of json) {
			if (tag["tag"])								tags.push(tag["tag"]);
			if (tag["romaji"])							tags.push(tag["romaji"]);
			if (tag["translation"])
				for (let trans in tag["translation"])	tags.push(tag["translation"][trans]);
		}
		
		return tags;
	} catch(err) {
		return [ ];
	}
} //getData

function rule(r = [], t = []) {
	return r.reduce((acc, curr) => acc + t.reduce((acc, curt) => acc + curr.test(curt), 0), 0);
} //rule

function getID(src) {
	return src.trim().split(idsep).map(i => i.trim()).filter(i => i).pop().match(idmatch)[0];
} //getID

function getPrefixed(thing = "") {
	return cfg.prefix.trim() + thing.trim();
} //getPrefixed
function wrap(thing = "", w = "") {
	return w + thing + w;
} //wrap