pixiv タグクラウドからピックアップ

Restores the tag cloud (illustration or novel tags column), and if there are tags attached to a work, this script brings those tags to the top of the tag cloud (illustration tags column).

目前為 2020-03-11 提交的版本,檢視 最新版本

// ==UserScript==
// @name        pixiv タグクラウドからピックアップ
// @name:ja     pixiv タグクラウドからピックアップ
// @name:en     pixiv Tag Cloud Prioritizer
// @description Restores the tag cloud (illustration or novel tags column), and if there are tags attached to a work, this script brings those tags to the top of the tag cloud (illustration tags column).
// @description:ja 作品ページへタグクラウド (作品タグ・小説タグ) を復活させ、閲覧中の作品についているタグと同じものをピックアップします。
// @namespace   https://userscripts.org/users/347021
// @version     2.14.0
// @match       https://www.pixiv.net/*
// @require     https://gitcdn.xyz/cdn/greasemonkey/gm4-polyfill/a834d46afcc7d6f6297829876423f58bb14a0d97/gm4-polyfill.js
// @require     https://gf.qytechs.cn/scripts/17895/code/polyfill.js?version=625392
// @require     https://gf.qytechs.cn/scripts/19616/code/utilities.js?version=752462
// @require     https://gf.qytechs.cn/scripts/17896/code/start-script.js?version=112958
// @license     MPL-2.0
// @contributionURL https://www.amazon.co.jp/registry/wishlist/E7PJ5C3K7AM2
// @compatible  Edge 最新安定版 / Latest stable (非推奨 / Deprecated)
// @compatible  Firefox
// @compatible  Opera
// @compatible  Chrome
// @grant       GM.setValue
// @grant       GM_setValue
// @grant       GM.getValue
// @grant       GM_getValue
// @grant       GM.deleteValue
// @grant       GM_deleteValue
// @grant       GM.listValues
// @grant       GM_listValues
// @noframes
// @run-at      document-start
// @icon        data:image/png;base64,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
// @author      100の人
// @homepageURL https://gf.qytechs.cn/scripts/262
// ==/UserScript==

// 当スクリプトはpixivが作成、配布しているアプリケーションではありません。
// <https://www.pixiv.net/terms/?page=brand>

'use strict';

/**
 * タグ一覧ページをキャッシュしておく期間 (秒数)。
 * @constant {number}
 */
const CACHE_LIFETIME = 24 * 60 * 60;

/**
 * @typedef {Object} TagsData
 * @property {HTMLDivElement} tagCloudSection - タグクラウド。
 * @property {Object.<number>} tagsAndCounts - タグをキー、タグの出現数を値に持つ連想配列。
 */

if (typeof content !== 'undefined') {
	// For Greasemonkey 4
	XMLHttpRequest = content.XMLHttpRequest.bind(content); //eslint-disable-line no-global-assign, no-native-reassign, no-undef, max-len
}

/**
 * 小説ページなら真。
 * @type {boolean}
 */
const novel = location.pathname.startsWith('/novel/');

/** @type {Promise.<TagsData>} */
let tagsDataPromise;

getUserId().then(async function (userId) {
	new MutationObserver(function (mutations) {
		for (const mutation of mutations) {
			const addedNode = mutation.addedNodes[0];
			if (!addedNode) {
				continue;
			}

			const target = mutation.target;
			switch (target.localName) {
				case 'div':
					if (novel && addedNode.id === 'chapter_0_0') {
						// 作品ページから作品ページへの移動 (小説)
						pickup();
						return;
					}
					if (addedNode.localName !== 'div') {
						continue;
					}
					// 作品ページ外から作品ページへの移動
					insertTagCloud();
					return;
				case 'section':
					if (addedNode.localName !== 'div' || addedNode !== target.firstElementChild) {
						continue;
					}
					// 作品ページ外から作品ページへの移動
					pickup();
					return;
				case 'a': {
					if (novel || !target.closest('figure') || addedNode.localName !== 'img') {
						continue;
					}
					// 作品ページから作品ページへの移動 (イラスト)
					const newUserId = document.querySelector('[href*="/users/"]').pathname.replace('/users/', '');
					if (newUserId !== userId) {
						// 別ユーザーの関連作品への移動
						userId = newUserId;
						tagsDataPromise = getTagsData(userId);
						insertTagCloud();
					}
					pickup();
					return;
				}
			}
		}
	}).observe(document, { childList: true, subtree: true });

	tagsDataPromise = async function () {
		const version = await GM.getValue('version'); // v2.13.0以降
		let nextCleaningDate = await GM.getValue('next-cleaning-date'); // v1.1.0以降
		if (version && nextCleaningDate) {
			if (new Date(nextCleaningDate).getTime() < Date.now()) {
				// 予定時刻を過ぎていれば、古いキャッシュを削除
				for (const name of await GM.listValues()) {
					if (/-(?:tags|expire)$/.test(name)) {
						// バージョン2.2.0以前で生成されたデータの削除
						await GM.deleteValue(name);
						continue;
					}
					if (!/^[0-9]+(?:-novel)?$/.test(name)) {
						continue;
					}
					const data = await GM.getValue(name);
					if (new Date(data.expire).getTime() < Date.now()) {
						// キャッシュの有効期限が切れていれば
						await GM.deleteValue(name);
					}
				}
				nextCleaningDate = null;
			}
		} else {
			// v2.12.0以前に生成されたデータの削除
			await Promise.all((await GM.listValues()).map(GM.deleteValue));
		}
		if (!version || version !== GM.info.script.version) {
			await GM.setValue('version', GM.info.script.version);
		}
		if (!nextCleaningDate) {
			await GM.setValue(
				'next-cleaning-date',
				new Date(Date.now() + CACHE_LIFETIME * DateUtils.MINUTES_TO_MILISECONDS).toISOString()
			);
		}

		return getTagsData(userId);
	}();

	addStyleSheet();
});

async function addStyleSheet()
{
	let tagCloudStyles = await GM.getValue('tag-cloud-styles');
	if (!tagCloudStyles) {
		tagCloudStyles = (await (await fetch('https://s.pximg.net/www/css/global.css')).text())
			.match(/^(?:\.area_(?:new|title|inside)|\.view_mypixiv|ul\.tagCloud) .+?$/umg).join('\n');
		GM.setValue('tag-cloud-styles', tagCloudStyles);
	}
	document.head.insertAdjacentHTML('beforeend', h`<style>
		${tagCloudStyles}
		.area_new {
			width: unset;
			margin: 16px;
		}
		.area_new + section {
			/* 小説ページにもともと含まれる「作者の作品タグ」 */
			display: none;
		}
		.tagCloud {
			padding: 0;
		}
		.tagCloud .last-current-tag::after {
			content: "";
			display: inline-block;
			height: 18px;
			border-right: solid 1px #999;
			width: 10px;
			margin-bottom: -3px;
			-webkit-transform: rotate(0.3rad);
			transform: rotate(0.3rad);
		}
	`);
}

async function insertTagCloud()
{
	const tagCloudSection = (await tagsDataPromise).tagCloudSection.cloneNode(true);
	const currentTagCloudSection = document.getElementsByClassName('area_new')[0];
	if (currentTagCloudSection) {
		if (currentTagCloudSection.getElementsByTagName('a')[0].getAttribute('href')
			!== tagCloudSection.getElementsByTagName('a')[0].getAttribute('href')) {
			currentTagCloudSection.replaceWith(tagCloudSection);
		}
		return;
	}
	if (novel) {
		const h2 = document.querySelector('main + aside > div ~ section header > h2');
		if (!h2) {
			return;
		}
		const sibling = h2.closest('section');
		if (!sibling) {
			return;
		}
		sibling.before(tagCloudSection);
	} else {
		const sibling = document.querySelector('main + aside > div');
		if (!sibling) {
			return;
		}
		sibling.after(tagCloudSection);
	}
}

async function pickup()
{
	/** @type {TagsData} */
	const tagsData = await tagsDataPromise;

	if (!document.getElementsByClassName('area_new')[0]) {
		await insertTagCloud();
	}

	/** @type {HTMLUListElement} */
	const tagCloud = tagsData.tagCloudSection.getElementsByClassName('tagCloud')[0].cloneNode(true);

	let tagCloudItemTemplate;
	let tagCloudItemTemplateAnchor;

	const currentTags = [];

	// 表示している作品のタグを取得する
	for (const tagItem
		of document.getElementsByClassName('gtm-new-work-tag-event-click')[0].closest('ul').getElementsByTagName('a')) {
		/**
		 * RFC 3986にもとづいてパーセント符号化されたタグ。
		 * @type {string}
		 */
		const urlencodedTag = tagItem.pathname.split('/')[2];

		let tagCloudItem;

		const anchor = tagCloud.querySelector('[href$="/' + urlencodedTag + '"]');
		if (anchor) {
			// タグクラウドに同じタグが存在すれば、抜き出す
			tagCloudItem = anchor.parentElement;
		} else {
			// 存在しなければ、もっとも出現度の低いタグとして追加
			if (!tagCloudItemTemplate) {
				tagCloudItemTemplate = tagCloud.firstElementChild.cloneNode(true);
				tagCloudItemTemplate.className = 'level6';
				tagCloudItemTemplateAnchor = tagCloudItemTemplate.firstElementChild;
			}
			
			tagCloudItemTemplateAnchor.pathname = tagCloudItemTemplateAnchor.pathname.replace(/[^/]+$/, urlencodedTag);
			const tag = tagItem.textContent;
			tagCloudItemTemplateAnchor.text = tag;
			if (tag in tagsData.tagsAndCounts) {
				// タグの数を表示
				tagCloudItemTemplateAnchor
					.insertAdjacentHTML('beforeend', `<span class="cnt">(${tagsData.tagsAndCounts[tag]})</span>`);
			}
			tagCloudItem = tagCloudItemTemplate.cloneNode(true);
		}

		currentTags.push(' ', tagCloudItem);
	}

	// 表示している作品のタグとそれ以外のタグとの区切りを示すクラスを設定
	currentTags[currentTags.length - 1].classList.add('last-current-tag');

	// タグクラウドの先頭に挿入
	tagCloud.prepend(...currentTags);
	
	// 更新
	document.getElementsByClassName('tagCloud')[0].replaceWith(tagCloud);
}

/**
 * 表示している作品の作者のユーザーIDを取得します。
 * @returns {Promise.<string>}
 */
function getUserId()
{
	return new Promise(function (resolve) {
		new MutationObserver(function (mutations, observer) {
			for (const mutation of mutations) {
				if (mutation.target.localName !== 'head') {
					continue;
				}
				for (const node of mutation.addedNodes) {
					if (node.id !== 'meta-preload-data') {
						continue;
					}
					observer.disconnect();
					resolve(Object.keys(JSON.parse(node.content).user)[0]);
					return;
				}
			}
		}).observe(document, {childList: true, subtree: true});
	});
}

/**
 * 指定したユーザーのタグクラウド、および出現数が2回以上のタグ一覧を取得します。
 * @param {string} userId
 * @returns {Promise.<TagsData>}
 */
async function getTagsData(userId)
{
	const serializedTagsData = await GM.getValue(userId);
	if (serializedTagsData && new Date(serializedTagsData.expire).getTime() > Date.now()) {
		const body = document.implementation.createHTMLDocument().body;
		body.innerHTML = serializedTagsData.tagCloudSection;
		return { tagCloudSection: body.firstElementChild, tagsAndCounts: serializedTagsData.tagsAndCounts };
	}
	return getTagsDataFromPage(userId);
}

/**
 * 指定したユーザーのタグクラウド、および出現数が2回以上のタグ一覧をページから取得し、キャッシュとして保存します。
 * @param {string} userId
 * @returns {Promise.<TagsData>}
 */
function getTagsDataFromPage(userId)
{
	return new Promise(function (resolve) {
		const client = new XMLHttpRequest();
		client.open('GET', new URL((novel ? '/novel' : '') + '/member_tag_all.php?id=' + userId, location));
		client.responseType = 'document';
		client.addEventListener('load', function (event) {
			const doc = event.target.response;
			
			const tagCloudSection = doc.getElementsByClassName(novel
				? 'area_new'
				: /* class="area_new promotion-comic" の回避 */'user-tags')[0];
			const tagCouldAnchors = {};
			for (const anchor of tagCloudSection.querySelectorAll('li a')) {
				tagCouldAnchors[anchor.firstChild.data] = anchor;
			}
			
			const counts = doc.querySelectorAll('.tag-list > dt');
			const tagsAndCounts = {};
			for (const dt of counts) {
				const count = Number.parseInt(dt.textContent);
				for (const anchor of dt.nextElementSibling.getElementsByTagName('a')) {
					const tag = anchor.text;
					if (count > 1) {
						tagsAndCounts[tag] = count;
					}

					// タグクラウドのリンクが旧URLになっている不具合を修正
					if (tag in tagCouldAnchors) {
						tagCouldAnchors[tag].href = anchor;
					}
				}
			}

			GM.setValue(userId + (novel ? '-novel' : ''), {
				expire: new Date(Date.now() + CACHE_LIFETIME * DateUtils.MINUTES_TO_MILISECONDS).toISOString(),
				tagCloudSection: tagCloudSection.outerHTML,
				tagsAndCounts,
			});

			resolve({ tagCloudSection, tagsAndCounts });
		});
		client.send();
	});
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址