Pixiv Utils

Utilities for Pixiv (tag block/highlight, adblock, ease-of-access, automation, premium emulation)

Versão de: 29/09/2024. Veja: a última versão.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey to install this script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name		Pixiv Utils
// @namespace	https://greasyfork.org/en/scripts/510779-pixiv-utils/
// @version		2024-09-29
// @description	Utilities for Pixiv (tag block/highlight, adblock, ease-of-access, automation, premium emulation)
// @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