Tag counter

add tags counter to list of entries. Great for MFC editors!

目前为 2023-06-15 提交的版本。查看 最新版本

// ==UserScript==
// @name         Tag counter
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  add tags counter to list of entries. Great for MFC editors!
// @author       Nefere
// @match        https://myfigurecollection.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=myfigurecollection.net
// @grant        GM.getValue
// @grant        GM.setValue
// ==/UserScript==

(async function() {
    'use strict';
    var TAG_CLASSNAME = "us-tag";
    var FAKE_CLASS_PLACEHOLDER = "what-i-was-looking-for";
	var REQUEST_DELAY = 1000;
	var CACHE_FRESH_SECONDS = 10 * 60;
	var CACHE_SAVE_ENTRIES = [];
	var CACHE_SAVE_AFTER_SETTING_VALUES_ORDER = 5;
	var tagCounterCache;
	
	function sleep(ms) {
		return new Promise(resolve => setTimeout(resolve, ms));
	};
	async function getTagCounterCache () {
		return new Map(Object.entries(
			JSON.parse(await GM.getValue('tagCounterCache', '{}'))));
	};
	async function saveTagCounterCache() {
		var newTagCounterCache = await getTagCounterCache();
		for (var entry of CACHE_SAVE_ENTRIES) {
			newTagCounterCache.set(entry.key, entry.value);
		}
		GM.setValue('tagCounterCache', JSON.stringify(Object.fromEntries(newTagCounterCache)));
		tagCounterCache = newTagCounterCache;
	};
	async function pushToTagCounterCache(url, tagCounter) {
		if (tagCounter) {
			var time = Date.now();
			var entry = {key: url, value: {'number': tagCounter, 'updatedTime': time}};
			tagCounterCache.set(entry.key, entry.value);
			CACHE_SAVE_ENTRIES.push(entry);
			if (CACHE_SAVE_ENTRIES.length % CACHE_SAVE_AFTER_SETTING_VALUES_ORDER == 0) {
				saveTagCounterCache();
			}
		}
	};
	function getTagCounterFromTagCounterCache(url) {
		var tagCounterPair = tagCounterCache.get(url);
		if (tagCounterPair == null) {
			return 0;
		}
		var rottenPairDate = new Date(tagCounterPair.updatedTime);
		rottenPairDate.setSeconds(rottenPairDate.getSeconds() + CACHE_FRESH_SECONDS);
		if ( rottenPairDate < Date.now()) {
			tagCounterCache.delete(url);
			return 0;
		}
		return tagCounterPair.number;
	};
    function addStyles() {
        $("<style>")
        .prop("type", "text/css")
        .html("\
        .item-icon ." + TAG_CLASSNAME + " {\
            position: absolute;\
            display: block;\
            right: 1px;\
            bottom: 1px;\
            height: 16px;\
            padding: 0 4px;\
            font-weight: 700;\
            color: gold;\
            background-color: darkgreen\
          }")
        .appendTo("head");
    };
    function getEntryContainers() {
        if (window.location.pathname.includes("/entry/")) {
            var result = $("#wide .results .result");
            return result;
        }
        console.log("unsupported getEntryContainers");
        return $(FAKE_CLASS_PLACEHOLDER);
    };
    function getItemsFromContainer(entryContainer) {
        var icons = $(entryContainer).find(".item-icons .item-icon");
        if (icons.length > 0) {
            return icons;
        }
        console.log("unsupported getItemsFromContainer");
        return $(FAKE_CLASS_PLACEHOLDER);
    };
	function getTagCounterFromHtml(html){
        var parser = new DOMParser();
        var doc = parser.parseFromString(html, 'text/html');
        var tagCounterNode = doc.querySelector('.tbx-target-TAGS .count');
        return tagCounterNode.textContent;
	};
	function addTagCounterToSearchResult(itemLinkElement, countOfTags) {
        var tagElement = document.createElement("span");
        tagElement.setAttribute("class", TAG_CLASSNAME);
        tagElement.textContent = countOfTags;
        itemLinkElement.appendChild(tagElement);
	};

	async function fetchAndHandle (queue) {
		var resultQueue = [];
		for(var itemElement of queue) {
			var itemLinkElement = itemElement.firstChild;
			var entryLink = itemLinkElement.getAttribute("href");
			
			fetch(entryLink).then(function (response) {
				if (response.ok) {
					return response.text();
				}
				return Promise.reject(response);
			}).then(function (html) {
				var countOfTags = getTagCounterFromHtml(html);
				addTagCounterToSearchResult(itemLinkElement, countOfTags);
				pushToTagCounterCache(entryLink, countOfTags);
			}).catch(function (err) {
				if (err.status == 429) {
					console.warn('Too many requests. Added the request to fetch later', err.url);
					resultQueue.push(itemElement);
					REQUEST_DELAY = REQUEST_DELAY * 1.1;
					console.info('Increased delay to ' + REQUEST_DELAY);
				}
			});
			await sleep(REQUEST_DELAY);
			
		}
		return resultQueue;
	};
	async function main(){
		var cacheQueue = [];
		var entryContainers = getEntryContainers();
		entryContainers.each(function(i, entryContainer) {
			var itemsElements = getItemsFromContainer(entryContainer);
			itemsElements.each(function(i, itemElement) {
				cacheQueue.push(itemElement);
			});
		});

		var queue = [];
		tagCounterCache = await getTagCounterCache();
		for(var itemElement of cacheQueue) {
			var itemLinkElement = itemElement.firstChild;
				var entryLink = itemLinkElement.getAttribute("href");
				var cache = getTagCounterFromTagCounterCache(entryLink);
				if (cache > 0) {
					addTagCounterToSearchResult(itemLinkElement, cache);
				} else {
					queue.push(itemElement);
				}
		}
		while (queue.length) {
			queue = await fetchAndHandle(queue);
		}
		saveTagCounterCache();

	};
	
	addStyles();
	main();
})();