Greasyfork/Sleazyfork Stats Display

Displays total number of scripts, installs, and version numbers for users on Greasyfork/Sleazyfork.

目前为 2024-11-25 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Greasyfork/Sleazyfork Stats Display
  3. // @description Displays total number of scripts, installs, and version numbers for users on Greasyfork/Sleazyfork.
  4. // @icon https://greasyfork.org/vite/assets/blacklogo96-CxYTSM_T.png
  5. // @version 1.2
  6. // @author afkarxyz
  7. // @namespace https://github.com/afkarxyz/misc-scripts/
  8. // @supportURL https://github.com/afkarxyz/misc-scripts/issues
  9. // @license MIT
  10. // @match https://greasyfork.org/*/users/*
  11. // @match https://greasyfork.org/users/*
  12. // @match https://sleazyfork.org/*/users/*
  13. // @match https://sleazyfork.org/users/*
  14. // @match https://greasyfork.org/*/scripts*
  15. // @match https://greasyfork.org/scripts*
  16. // @match https://sleazyfork.org/*/scripts*
  17. // @match https://sleazyfork.org/scripts*
  18. // @grant none
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23. const style = document.createElement('style');
  24. style.textContent = `
  25. .badge {
  26. border: 1px solid transparent;
  27. display: inline-block;
  28. font-size: 0.65em;
  29. font-weight: 600;
  30. line-height: 1;
  31. padding: 0.15em 0.3em;
  32. text-align: center;
  33. vertical-align: baseline;
  34. white-space: nowrap;
  35. margin-right: 0.5em;
  36. }
  37. .badge-js {
  38. background-color: #efd81d;
  39. color: #000;
  40. }
  41. .badge-css {
  42. background-color: #254bdd;
  43. color: #fff;
  44. }
  45. .script-version {
  46. color: #000;
  47. }
  48. `;
  49. document.head.appendChild(style);
  50. function hasBadge(h2Element) {
  51. return h2Element.querySelector('.badge') !== null;
  52. }
  53.  
  54. function isCssScript(h2Element) {
  55. const parentLi = h2Element.closest('li[data-script-language]');
  56. if (parentLi && parentLi.getAttribute('data-script-language') === 'css') {
  57. return true;
  58. }
  59. return h2Element.querySelector('.badge-css') !== null;
  60. }
  61.  
  62. function createBadge(type) {
  63. const badge = document.createElement('span');
  64. badge.className = `badge badge-${type}`;
  65. badge.title = type === 'js' ? 'User script' : 'CSS script';
  66. badge.textContent = type.toUpperCase();
  67. return badge;
  68. }
  69.  
  70. function createVersionSpan(version) {
  71. const versionSpan = document.createElement('span');
  72. versionSpan.textContent = `v${version}`;
  73. versionSpan.classList.add('script-version');
  74. return versionSpan;
  75. }
  76.  
  77. function addBadgeAndVersion(h2Element, version) {
  78. if (!h2Element) return;
  79.  
  80. const scriptLink = h2Element.querySelector('.script-link');
  81. if (!scriptLink) return;
  82.  
  83. const existingVersion = h2Element.querySelector('.script-version');
  84. const existingBadge = h2Element.querySelector('.badge');
  85. if (existingVersion) existingVersion.remove();
  86. if (existingBadge) existingBadge.remove();
  87.  
  88. const badgeType = isCssScript(h2Element) ? 'css' : 'js';
  89. const badge = createBadge(badgeType);
  90. scriptLink.insertAdjacentElement('afterend', badge);
  91. if (version) {
  92. const versionSpan = createVersionSpan(version);
  93. badge.insertAdjacentElement('afterend', versionSpan);
  94. }
  95. }
  96.  
  97. function appendVersionNumbers() {
  98. const listItems = document.querySelectorAll('li[data-script-id]');
  99. if (!listItems || listItems.length === 0) return;
  100. listItems.forEach(listItem => {
  101. const version = listItem.getAttribute('data-script-version');
  102. if (!version) return;
  103. const h2 = listItem.querySelector('h2');
  104. if (h2) {
  105. addBadgeAndVersion(h2, version);
  106. }
  107. });
  108. }
  109.  
  110. function displayVersionNumbers() {
  111. const headings = document.querySelectorAll('h2 a.script-link');
  112. headings.forEach(heading => {
  113. const version = heading.closest('li').getAttribute('data-script-version');
  114. if (version) {
  115. addBadgeAndVersion(heading.parentElement, version);
  116. }
  117. });
  118. }
  119.  
  120. function displayUserStats() {
  121. const parseInstallCount = text => parseInt(text.replace(/,/g, '')) || 0;
  122. const scripts = [...document.querySelectorAll('.script-list-total-installs span')]
  123. .filter(element => !element.textContent.includes('Total installs'));
  124. if (scripts.length === 0) return;
  125. const totalInstalls = scripts.reduce((sum, element) =>
  126. sum + parseInstallCount(element.textContent), 0);
  127. const statsData = {
  128. scriptsCount: scripts.length,
  129. totalInstalls: totalInstalls
  130. };
  131. if (document.getElementById('user-stats')) return;
  132. const statsElement = document.createElement('section');
  133. statsElement.id = 'user-stats';
  134. const userDiscussionsElement = document.getElementById('user-discussions');
  135. if (!userDiscussionsElement) return;
  136. const stylesToCopy = [
  137. 'padding',
  138. 'border',
  139. 'borderRadius',
  140. 'backgroundColor',
  141. 'color',
  142. 'fontSize',
  143. 'fontFamily',
  144. 'lineHeight'
  145. ];
  146. const computedStyle = window.getComputedStyle(userDiscussionsElement);
  147. stylesToCopy.forEach(property => {
  148. statsElement.style[property] = computedStyle.getPropertyValue(property);
  149. });
  150. const header = document.createElement('header');
  151. const headerStyle = window.getComputedStyle(userDiscussionsElement.querySelector('header'));
  152. header.style.padding = headerStyle.padding;
  153. header.style.borderBottom = headerStyle.borderBottom;
  154. const h3 = document.createElement('h3');
  155. h3.textContent = 'Stats';
  156. const originalH3Style = window.getComputedStyle(userDiscussionsElement.querySelector('h3'));
  157. h3.style.margin = originalH3Style.margin;
  158. h3.style.fontSize = originalH3Style.fontSize;
  159. header.appendChild(h3);
  160. const contentSection = document.createElement('section');
  161. contentSection.className = 'text-content';
  162. const originalContentStyle = window.getComputedStyle(userDiscussionsElement.querySelector('.text-content'));
  163. contentSection.style.padding = originalContentStyle.padding;
  164. const p = document.createElement('p');
  165. p.innerHTML = `This user has <strong>${statsData.scriptsCount}</strong> script${statsData.scriptsCount !== 1 ? 's' : ''} with <strong>${statsData.totalInstalls.toLocaleString()}</strong> total install${statsData.totalInstalls !== 1 ? 's' : ''}.`;
  166. contentSection.appendChild(p);
  167. statsElement.appendChild(header);
  168. statsElement.appendChild(contentSection);
  169. userDiscussionsElement.parentNode.insertBefore(statsElement, userDiscussionsElement.nextSibling);
  170. }
  171.  
  172. function init() {
  173. const currentPath = window.location.pathname;
  174. if (currentPath.includes('/scripts')) {
  175. appendVersionNumbers();
  176. } else if (currentPath.includes('/users/')) {
  177. displayUserStats();
  178. displayVersionNumbers();
  179. }
  180. }
  181.  
  182. window.addEventListener('load', init);
  183. let lastUrl = location.href;
  184. const observer = new MutationObserver((mutations) => {
  185. const url = location.href;
  186. if (url !== lastUrl) {
  187. lastUrl = url;
  188. }
  189. const hasRelevantChanges = mutations.some(mutation =>
  190. [...mutation.addedNodes].some(node =>
  191. node.nodeType === 1 &&
  192. (node.matches?.('li[data-script-id]') ||
  193. node.querySelector?.('li[data-script-id]') ||
  194. node.matches?.('h2') ||
  195. node.querySelector?.('h2'))
  196. )
  197. );
  198. if (hasRelevantChanges) {
  199. init();
  200. }
  201. });
  202.  
  203. function startObserver() {
  204. const observeTarget = document.querySelector('#browse-script-list, .script-list');
  205. if (observeTarget) {
  206. observer.observe(observeTarget, {
  207. childList: true,
  208. subtree: true
  209. });
  210. } else {
  211. requestAnimationFrame(startObserver);
  212. }
  213. }
  214.  
  215. startObserver();
  216. init();
  217. })();