Tag counter

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

当前为 2023-06-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Tag counter
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.1
  5. // @description add tags counter to list of entries. Great for MFC editors!
  6. // @author Nefere
  7. // @match https://myfigurecollection.net/entry/*
  8. // @match https://myfigurecollection.net/browse.v4.php*
  9. // @match https://myfigurecollection.net/browse/calendar/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=myfigurecollection.net
  11. // @grant GM.getValue
  12. // @grant GM.setValue
  13. // ==/UserScript==
  14.  
  15. (async function() {
  16. 'use strict';
  17. var TAG_CLASSNAME = "us-tag";
  18. var FAKE_CLASS_PLACEHOLDER = "what-i-was-looking-for";
  19. var REQUEST_DELAY = 1000;
  20. var CACHE_FRESH_SECONDS = 10 * 60;
  21. var CACHE_SAVE_ENTRIES = [];
  22. var CACHE_SAVE_AFTER_SETTING_VALUES_ORDER = 5;
  23. var tagCounterCache;
  24. function sleep(ms) {
  25. return new Promise(resolve => setTimeout(resolve, ms));
  26. };
  27. async function getTagCounterCache () {
  28. return new Map(Object.entries(
  29. JSON.parse(await GM.getValue('tagCounterCache', '{}'))));
  30. };
  31. async function saveTagCounterCache() {
  32. var newTagCounterCache = await getTagCounterCache();
  33. for (var entry of CACHE_SAVE_ENTRIES) {
  34. newTagCounterCache.set(entry.key, entry.value);
  35. }
  36. GM.setValue('tagCounterCache', JSON.stringify(Object.fromEntries(newTagCounterCache)));
  37. tagCounterCache = newTagCounterCache;
  38. };
  39. async function pushToTagCounterCache(url, tagCounter) {
  40. if (tagCounter) {
  41. var time = Date.now();
  42. var entry = {key: url, value: {'number': tagCounter, 'updatedTime': time}};
  43. tagCounterCache.set(entry.key, entry.value);
  44. CACHE_SAVE_ENTRIES.push(entry);
  45. if (CACHE_SAVE_ENTRIES.length % CACHE_SAVE_AFTER_SETTING_VALUES_ORDER == 0) {
  46. saveTagCounterCache();
  47. }
  48. }
  49. };
  50. function getTagCounterFromTagCounterCache(url) {
  51. var tagCounterPair = tagCounterCache.get(url);
  52. if (tagCounterPair == null) {
  53. return 0;
  54. }
  55. var rottenPairDate = new Date(tagCounterPair.updatedTime);
  56. rottenPairDate.setSeconds(rottenPairDate.getSeconds() + CACHE_FRESH_SECONDS);
  57. if ( rottenPairDate < Date.now()) {
  58. tagCounterCache.delete(url);
  59. return 0;
  60. }
  61. return tagCounterPair.number;
  62. };
  63. function addStyles() {
  64. $("<style>")
  65. .prop("type", "text/css")
  66. .html("\
  67. .item-icon ." + TAG_CLASSNAME + " {\
  68. position: absolute;\
  69. display: block;\
  70. right: 1px;\
  71. bottom: 1px;\
  72. height: 16px;\
  73. padding: 0 4px;\
  74. font-weight: 700;\
  75. color: gold;\
  76. background-color: darkgreen\
  77. }")
  78. .appendTo("head");
  79. };
  80. function getEntryContainers() {
  81. var pathname = window.location.pathname;
  82. if (pathname.includes("/entry/") /* encyclopedia entry */
  83. || pathname.includes("/browse.v4.php") /* search results with filters */
  84. || pathname.includes("/browse/calendar/")/* calendar page */
  85. ) {
  86. var result = $("#wide .result");
  87. return result;
  88. }
  89. console.log("unsupported getEntryContainers");
  90. return $(FAKE_CLASS_PLACEHOLDER);
  91. };
  92. function isDetailedList () {
  93. var search = window.location.search;
  94. var searchParams = new URLSearchParams(search);
  95. var outputParam = searchParams.get("output"); /* 0 - detailedList, 1,2 - grid, 3 - diaporama */
  96. return outputParam == 0;
  97. };
  98. function getItemsFromContainer(entryContainer) {
  99. var icons = $(entryContainer).find(".item-icons .item-icon");
  100. if (icons.length > 0) {
  101. return icons;
  102. }
  103. var pathname = window.location.pathname;
  104. if (pathname.includes("/browse.v4.php") /* search page, detailed list view */
  105. && isDetailedList()) {
  106. return $(FAKE_CLASS_PLACEHOLDER);
  107. }
  108. console.log("unsupported getItemsFromContainer");
  109. return $(FAKE_CLASS_PLACEHOLDER);
  110. };
  111. function getTagCounterFromHtml(html){
  112. var parser = new DOMParser();
  113. var doc = parser.parseFromString(html, 'text/html');
  114. var tagCounterNode = doc.querySelector('.tbx-target-TAGS .count');
  115. return tagCounterNode.textContent;
  116. };
  117. function addTagCounterToSearchResult(itemLinkElement, countOfTags) {
  118. var tagElement = document.createElement("span");
  119. tagElement.setAttribute("class", TAG_CLASSNAME);
  120. tagElement.textContent = countOfTags;
  121. itemLinkElement.appendChild(tagElement);
  122. };
  123.  
  124. async function fetchAndHandle (queue) {
  125. var resultQueue = [];
  126. for(var itemElement of queue) {
  127. var itemLinkElement = itemElement.firstChild;
  128. var entryLink = itemLinkElement.getAttribute("href");
  129. fetch(entryLink).then(function (response) {
  130. if (response.ok) {
  131. return response.text();
  132. }
  133. return Promise.reject(response);
  134. }).then(function (html) {
  135. var countOfTags = getTagCounterFromHtml(html);
  136. addTagCounterToSearchResult(itemLinkElement, countOfTags);
  137. pushToTagCounterCache(entryLink, countOfTags);
  138. }).catch(function (err) {
  139. if (err.status == 429) {
  140. console.warn('Too many requests. Added the request to fetch later', err.url);
  141. resultQueue.push(itemElement);
  142. REQUEST_DELAY = REQUEST_DELAY * 1.1;
  143. console.info('Increased delay to ' + REQUEST_DELAY);
  144. }
  145. });
  146. await sleep(REQUEST_DELAY);
  147. }
  148. return resultQueue;
  149. };
  150. async function main(){
  151. var cacheQueue = [];
  152. var entryContainers = getEntryContainers();
  153. entryContainers.each(function(i, entryContainer) {
  154. var itemsElements = getItemsFromContainer(entryContainer);
  155. itemsElements.each(function(i, itemElement) {
  156. cacheQueue.push(itemElement);
  157. });
  158. });
  159.  
  160. var queue = [];
  161. tagCounterCache = await getTagCounterCache();
  162. for(var itemElement of cacheQueue) {
  163. var itemLinkElement = itemElement.firstChild;
  164. var entryLink = itemLinkElement.getAttribute("href");
  165. var cache = getTagCounterFromTagCounterCache(entryLink);
  166. if (cache > 0) {
  167. addTagCounterToSearchResult(itemLinkElement, cache);
  168. } else {
  169. queue.push(itemElement);
  170. }
  171. }
  172. while (queue.length) {
  173. queue = await fetchAndHandle(queue);
  174. }
  175. saveTagCounterCache();
  176.  
  177. };
  178. addStyles();
  179. main();
  180. })();