Greasyfork/Sleazyfork Stats Display

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

当前为 2024-11-24 提交的版本,查看 最新版本

  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.1
  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. function appendVersionNumbers() {
  24. const listItems = document.querySelectorAll('li[data-script-id]');
  25. if (!listItems || listItems.length === 0) return;
  26. listItems.forEach(listItem => {
  27. const version = listItem.getAttribute('data-script-version');
  28. if (!version) return;
  29. const separator = listItem.querySelector('.name-description-separator');
  30. if (!separator) return;
  31. const existingVersion = separator.previousElementSibling;
  32. if (existingVersion && existingVersion.classList.contains('script-version')) return;
  33. const versionSpan = document.createElement('span');
  34. versionSpan.textContent = ` v${version} `;
  35. versionSpan.style.color = '#000000';
  36. versionSpan.style.fontWeight = 'bold';
  37. versionSpan.classList.add('script-version');
  38. separator.parentNode.insertBefore(versionSpan, separator);
  39. });
  40. }
  41.  
  42. function displayVersionNumbers() {
  43. const headings = document.querySelectorAll('h2 a.script-link');
  44. headings.forEach(heading => {
  45. const version = heading.closest('li').getAttribute('data-script-version');
  46. const versionSpan = document.createElement('span');
  47. versionSpan.textContent = ` v${version}`;
  48. versionSpan.style.color = '#000';
  49. heading.insertAdjacentElement('afterend', versionSpan);
  50. });
  51. }
  52.  
  53. function displayUserStats() {
  54. const parseInstallCount = text => parseInt(text.replace(/,/g, '')) || 0;
  55. const scripts = [...document.querySelectorAll('.script-list-total-installs span')]
  56. .filter(element => !element.textContent.includes('Total installs'));
  57. if (scripts.length === 0) return;
  58. const totalInstalls = scripts.reduce((sum, element) =>
  59. sum + parseInstallCount(element.textContent), 0);
  60. const statsData = {
  61. scriptsCount: scripts.length,
  62. totalInstalls: totalInstalls
  63. };
  64. const statsElement = document.createElement('section');
  65. statsElement.id = 'user-stats';
  66. const userDiscussionsElement = document.getElementById('user-discussions');
  67. if (!userDiscussionsElement) return;
  68. const stylesToCopy = [
  69. 'padding',
  70. 'border',
  71. 'borderRadius',
  72. 'backgroundColor',
  73. 'color',
  74. 'fontSize',
  75. 'fontFamily',
  76. 'lineHeight'
  77. ];
  78. const computedStyle = window.getComputedStyle(userDiscussionsElement);
  79. stylesToCopy.forEach(property => {
  80. statsElement.style[property] = computedStyle.getPropertyValue(property);
  81. });
  82. const header = document.createElement('header');
  83. const headerStyle = window.getComputedStyle(userDiscussionsElement.querySelector('header'));
  84. header.style.padding = headerStyle.padding;
  85. header.style.borderBottom = headerStyle.borderBottom;
  86. const h3 = document.createElement('h3');
  87. h3.textContent = 'Stats';
  88. const originalH3Style = window.getComputedStyle(userDiscussionsElement.querySelector('h3'));
  89. h3.style.margin = originalH3Style.margin;
  90. h3.style.fontSize = originalH3Style.fontSize;
  91. header.appendChild(h3);
  92. const contentSection = document.createElement('section');
  93. contentSection.className = 'text-content';
  94. const originalContentStyle = window.getComputedStyle(userDiscussionsElement.querySelector('.text-content'));
  95. contentSection.style.padding = originalContentStyle.padding;
  96. const p = document.createElement('p');
  97. 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' : ''}.`;
  98. contentSection.appendChild(p);
  99. statsElement.appendChild(header);
  100. statsElement.appendChild(contentSection);
  101. userDiscussionsElement.parentNode.insertBefore(statsElement, userDiscussionsElement.nextSibling);
  102. }
  103.  
  104. function init() {
  105. const currentPath = window.location.pathname;
  106. if (currentPath.includes('/scripts')) {
  107. appendVersionNumbers();
  108. } else if (currentPath.includes('/users/')) {
  109. displayUserStats();
  110. displayVersionNumbers();
  111. }
  112. }
  113.  
  114. window.addEventListener('load', init);
  115. let lastUrl = location.href;
  116. const scriptListObserver = new MutationObserver((mutations) => {
  117. const url = location.href;
  118. if (url !== lastUrl) {
  119. lastUrl = url;
  120. }
  121. const hasRelevantChanges = mutations.some(mutation =>
  122. [...mutation.addedNodes].some(node =>
  123. node.nodeType === 1 &&
  124. (node.matches?.('li[data-script-id]') || node.querySelector?.('li[data-script-id]'))
  125. )
  126. );
  127. if (hasRelevantChanges) {
  128. init();
  129. }
  130. });
  131.  
  132. function startObserver() {
  133. const observeTarget = document.querySelector('#browse-script-list, .script-list');
  134. if (observeTarget) {
  135. scriptListObserver.observe(observeTarget, {
  136. childList: true,
  137. subtree: true
  138. });
  139. } else {
  140. requestAnimationFrame(startObserver);
  141. }
  142. }
  143.  
  144. startObserver();
  145. })();