Greasy Fork User Statistics+

shows user statistics as total installs, total scripts etc.

当前为 2024-12-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Greasy Fork User Statistics+
  3. // @namespace -
  4. // @version 1.4.0
  5. // @description shows user statistics as total installs, total scripts etc.
  6. // @author NotYou
  7. // @match *://greasyfork.org/*/users/*
  8. // @match *://sleazyfork.org/*/users/*
  9. // @license GPL-3.0-or-later
  10. // @run-at document-end
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. class Utils {
  16. static getCurrentTranslationId() {
  17. const $languageSelector = document.querySelector('.language-selector-locale')
  18.  
  19. return $languageSelector ? $languageSelector.value : 'en'
  20. }
  21.  
  22. static addStyle(css) {
  23. const keys = Object.keys(css)
  24. const style = document.createElement('style')
  25. let cssFinal = ''
  26.  
  27. keys.forEach(e => {
  28. const _keys = Object.keys(css[e])
  29.  
  30. cssFinal += e + '{'
  31.  
  32. _keys.forEach(r => {
  33. cssFinal += r.replace(/[A-Z]/, m => `-${m.toLowerCase()}`) + ':' + css[e][r] + ';'
  34. })
  35.  
  36. cssFinal += '}'
  37. })
  38.  
  39. style.textContent = cssFinal
  40. document.querySelector('head').appendChild(style)
  41. }
  42.  
  43. static capitalize(str) {
  44. return str[0].toUpperCase() + str.slice(1)
  45. }
  46. }
  47.  
  48. class Translation {
  49. static get data() {
  50. return {
  51. 'ar': {
  52. stats: 'إحصائيات المستخدم',
  53. works: 'يعمل المستخدم',
  54. },
  55. 'bg': {
  56. stats: 'Потребителска статистика',
  57. works: 'Потребителят работи',
  58. },
  59. 'cs': {
  60. stats: 'Statistiky uživatelů',
  61. works: 'Uživatel pracuje',
  62. },
  63. 'da': {
  64. stats: 'Brugerstatistik',
  65. works: 'Brugeren fungerer',
  66. },
  67. 'de': {
  68. stats: 'Benutzerstatistiken',
  69. works: 'Benutzer funktioniert',
  70. },
  71. 'el': {
  72. stats: 'Στατιστικά στοιχεία χρηστών',
  73. works: 'Ο χρήστης λειτουργεί',
  74. },
  75. 'en': {
  76. stats: 'User statistics',
  77. works: 'User works',
  78. },
  79. 'eo': {
  80. stats: 'Statistiko de uzantoj',
  81. works: 'Uzanto funkcias',
  82. },
  83. 'es': {
  84. stats: 'Estadísticas de usuario',
  85. works: 'El usuario trabaja',
  86. },
  87. 'fi': {
  88. stats: 'Käyttäjätilastot',
  89. works: 'Käyttäjä toimii',
  90. },
  91. 'fr': {
  92. stats: 'Statistiques d\'utilisateurs',
  93. works: 'L\'utilisateur travaille',
  94. },
  95. 'he': {
  96. stats: 'סטטיסטיקות משתמשים',
  97. works: 'משתמש עובד',
  98. },
  99. 'hu': {
  100. stats: 'Felhasználói statisztikák',
  101. works: 'Felhasználó működik',
  102. },
  103. 'id': {
  104. stats: 'Statistik pengguna',
  105. works: 'Pengguna bekerja',
  106. },
  107. 'it': {
  108. stats: 'Statistiche utente',
  109. works: 'L\'utente lavora',
  110. },
  111. 'ja': {
  112. stats: 'ユーザー統計',
  113. works: 'ユーザーは動作します',
  114. },
  115. 'ko': {
  116. stats: '사용자 통계',
  117. works: '사용자 작품',
  118. },
  119. 'ne': {
  120. stats: 'Gebruikersstatistieken',
  121. works: 'Gebruiker werkt',
  122. },
  123. 'pl': {
  124. stats: 'Statystyki użytkowników',
  125. works: 'Użytkownik pracuje',
  126. },
  127. 'ro': {
  128. stats: 'Statistici utilizatori',
  129. works: 'Utilizatorul lucrează',
  130. },
  131. 'ru': {
  132. stats: 'Статистика пользователей',
  133. works: 'Пользовательские работы',
  134. },
  135. 'tr': {
  136. stats: 'Kullanıcı istatistikleri',
  137. works: 'Kullanıcı işleri',
  138. },
  139. 'uk': {
  140. stats: 'Статистика користувачів',
  141. works: 'Користувач працює',
  142. },
  143. 'vi': {
  144. stats: 'Thống kê người dùng',
  145. works: 'Người dùng hoạt động',
  146. },
  147. 'zh-CN': {
  148. stats: '用户统计',
  149. works: '用户作品',
  150. },
  151. 'zh-TW': {
  152. stats: '用戶統計',
  153. works: '用戶作品',
  154. },
  155. }
  156. }
  157.  
  158. static getById(id) {
  159. return this.data[id]
  160. }
  161. }
  162.  
  163. class Data {
  164. constructor() {
  165. this.total = 0
  166. this.daily = 0
  167. this.scripts = 0
  168. this.styles = 0
  169. this.libraries = 0
  170. this.stats = 0
  171. this.works = 0
  172. }
  173.  
  174. increaseTotal(value) {
  175. this.total += value
  176. this.stats += value
  177. }
  178.  
  179. increaseDaily(value) {
  180. this.daily += value
  181. this.stats += value
  182. }
  183.  
  184. increaseScripts() {
  185. this.scripts++
  186. this.works++
  187. }
  188.  
  189. increaseStyles() {
  190. this.styles++
  191. this.works++
  192. }
  193.  
  194. increaseLibraries() {
  195. this.libraries++
  196. this.works++
  197. }
  198. }
  199.  
  200. class Stats {
  201. constructor(title, data, maxValue) {
  202. this.title = Object.assign(document.createElement('h3'), {
  203. textContent: title
  204. })
  205. this.data = data
  206. this.node = document.createElement('div')
  207. this.node.appendChild(this.title)
  208.  
  209. for (const key in data) {
  210. const value = data[key]
  211. const percentage = value / maxValue * 100
  212.  
  213. if (percentage > 0) {
  214. const $bar = this.bar(percentage, key, value)
  215.  
  216. this.node.appendChild($bar)
  217. }
  218. }
  219. }
  220.  
  221. get statsNode() {
  222. return this.node
  223. }
  224.  
  225. bar(percentage, key, value) {
  226. const $bar = document.createElement('div')
  227. const $progress = document.createElement('div')
  228. const $text = document.createElement('span')
  229. let bg = '128, 128, 128'
  230.  
  231. $bar.className = 'statistics-bar'
  232.  
  233. switch (key) {
  234. case 'total':
  235. bg = '255, 28, 28'
  236. break
  237. case 'daily':
  238. bg = '255, 58, 58'
  239. break
  240. case 'styles':
  241. bg = '50, 149, 208'
  242. break
  243. case 'scripts':
  244. bg = '236, 203, 27'
  245. break
  246. case 'libraries':
  247. bg = '221, 102, 15'
  248. break
  249. }
  250. $progress.style.width = percentage + '%'
  251. $progress.style.backgroundColor = 'rgba(' + bg + ', .7)'
  252.  
  253. $text.textContent = Utils.capitalize(key) + ` (${value.toLocaleString()})`
  254.  
  255. $bar.appendChild($text)
  256. $bar.appendChild($progress)
  257.  
  258. return $bar
  259. }
  260. }
  261.  
  262. class Styles {
  263. static init() {
  264. Utils.addStyle({
  265. '#user-statistics': {
  266. position: 'relative',
  267. },
  268.  
  269. '.statistics-bar': {
  270. width: 'calc(100% - 2.4vw)',
  271. margin: '1em',
  272. marginBottom: '1.5em',
  273. },
  274.  
  275. '.statistics-bar div': {
  276. height: '3px',
  277. borderRadius: '20px',
  278. padding: '3px',
  279. position: 'relative',
  280. },
  281.  
  282. '.statistics-bar div[style*=" 0%"]': {
  283. padding: '0',
  284. },
  285.  
  286. '.statistics-bar div[style*=" 0%"] + span': {
  287. color: 'unset !important',
  288. },
  289.  
  290. '#user-statistics-pin-btn': {
  291. width: '25px',
  292. height: '25px',
  293. backgroundColor: 'rgb(191, 191, 191)',
  294. display: 'block',
  295. position: 'absolute',
  296. right: '10px',
  297. top: '10px',
  298. borderRadius: '50%',
  299. cursor: 'pointer',
  300. backgroundImage: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACDElEQVR42mNkoDFgJFejk6igUIWGfKrb4QudNLGgWE3Ou0RNbvPlj1+qgZa0U92C+SaaBe7iQn0g9pVPX3FaQpYFDVqKNmmKUtsYGRl5Qfz/QHDp45cajyMX2yi2AGR4upL0NqCZPEALGGEWvPn2g+HE2/fVaZfutZNtAczlQCYPsjjI8Lffv4PZt7/9QLGEaAvQXQ5yNUg/1PD/yL5BtoQoC4CGWwMN3w5k8oIMgFkANJzx3Y8fDDAxmAUg5q2v38uAlvRgtaBdR9nVSpg/XICVRQLozO9i7Kzu+IIFCbzf++Zj5d1vP+9c//Lt1IVP375gtcBBVEB0vonWXg5mJl1k1yFFKCN6sIAMn/f4tevip6/PERXJMEvYmRh1iHH5vMevgIa/OYcugTcO0HzCgByhSHHxDpfhREVynreHXqEo9/nPDx8wIUcovmAh2gI7ByfJ6vrGfc/v3VYTWTX3H8+nDyzEupygBTDDHz17qnb/0YOLV1cujcpi/7OKjRESJy9+/l4Wef52DKEQYCTG8JXz57vcvXXznSk/t2iLhhww4pl0X/z8tTbi3O0Qki3AZThMHmbJw28/D6ZdvpdLkgWEDEe2xFiAR3PGw5eHSLJg3ZbtG99+/OiDz3BSAdwCY1Mz/oyCorvXblzbtWnlyhxqGI5igZyCAg8w/f17fP/+N2oYjDWIaAFobgEA6ol7KD65m7AAAAAASUVORK5CYII=)',
  301. backgroundSize: '80% 80%',
  302. backgroundRepeat: 'no-repeat',
  303. backgroundPosition: '40% 40%',
  304. filter: 'grayscale(1)',
  305. },
  306.  
  307. '#user-statistics.stats-pinned': {
  308. position: 'fixed',
  309. zIndex: '100',
  310. right: '10px',
  311. top: 'calc(50vh - 175px)',
  312. borderRadius: '4px',
  313. width: '400px',
  314. height: '350px',
  315. padding: '4px',
  316. border: '1px solid rgb(0, 0 ,0)',
  317. },
  318.  
  319. '#user-statistics.stats-pinned #user-statistics-pin-btn': {
  320. /* GPL-3.0 @Saki */
  321. backgroundImage: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABqUlEQVR42u2USUvDUBSF8zIYEzqXlnbR/gYHdCe4VRyg0ILiwuF3OSxEsYWCInUruFMcfkO7KZ2blrRmIJ4XWsnCgVREhDwINDd957vvnJsQ5pcX8QAegIlEIpymaTP9fv/RWff5fAuiKD43m01jYkA8Hud1XT8MhUJZRVFyELseQddQy7fb7SIge9VqVXcNCAaDPMuyR6lUaofneQLAoNPpZC3LYsLhcCEQCEimaVrlcvmMELLbarUMVwBJkuYSicQdOpUgQCBs9Xo9DT+pPVP2ZixAB7VabRkW3ru2CJ2uRqNR2q1MAWPQ+DlEh7BtC91f/iTkFVxFv98vOgEQ15BBrtFoXE0cMl04wQYAeQpw1pHJEPZkALiZGADhTQDOIS59ZBEyGcCeLK6SawBCXkwmk7c0ZIfgK9VHJu+ngU1qvV5fgmVPrgAQ5tHwSTqd3uY4zh5T2i3dM7JsGmPKVCqVU/xv3/WY0hWLxQTDMI4xTRn4nYOI/aLhfh0NXKBWEAThAGP66dv8bcj0JPhUzKqq+uCsy7I8D/GXbrdrfrX/7z92HuD/A94AoxXXGeB8ZfgAAAAASUVORK5CYII=)',
  322. },
  323.  
  324. '#user-statistics.stats-pinned .statistics-bar': {
  325. margin: '.5em',
  326. },
  327.  
  328. '.statistics-bar div::before': {
  329. content: '""',
  330. position: 'absolute',
  331. width: 'calc(100% - 2px)',
  332. height: '7px',
  333. margin: '-5px -5px',
  334. borderRadius: '20px',
  335. boxShadow: '0 0 4px 0 rgba(0, 0, 0, 0.3), 0 0 4px 0 rgba(0, 0, 0, 0.3) inset',
  336. border: '1px solid rgb(34, 34, 34)',
  337. padding: '2px',
  338. },
  339.  
  340. '#user-statistics-pin-btn:only-child': {
  341. display: 'none',
  342. }
  343. })
  344. }
  345. }
  346.  
  347. class Main {
  348. static init() {
  349. const translationId = Utils.getCurrentTranslationId()
  350. const currentTranslation = Translation.getById(translationId)
  351. const data = new Data()
  352.  
  353. const $stats = document.createElement('div')
  354. $stats.id = 'user-statistics'
  355.  
  356. const $pinBtn = document.createElement('div')
  357. $pinBtn.id = 'user-statistics-pin-btn'
  358. $pinBtn.addEventListener('click', () => {
  359. const styles = window.getComputedStyle(document.body)
  360.  
  361. if ($stats.classList.contains('stats-pinned')) {
  362. $stats.style.cssText = ''
  363. } else {
  364. $stats.style.backgroundColor = styles.backgroundColor
  365. $stats.style.color = styles.color
  366. }
  367.  
  368. $stats.classList.toggle('stats-pinned')
  369. })
  370.  
  371. $stats.appendChild($pinBtn)
  372.  
  373. const isCitrusGF = Boolean(document.querySelector('#script-table'))
  374.  
  375. if (isCitrusGF) {
  376. document.querySelectorAll('#script-table tbody tr').forEach(script => {
  377. data.increaseDaily(Number(script.querySelector(':nth-child(4)').textContent))
  378. data.increaseDaily(Number(script.querySelector(':nth-child(5)').textContent))
  379. data.increaseScripts()
  380. })
  381. } else {
  382. document.querySelectorAll('.script-list > li').forEach(script => {
  383. const { dataset } = script
  384.  
  385. data.increaseTotal(Number(dataset.scriptTotalInstalls))
  386. data.increaseDaily(Number(dataset.scriptDailyInstalls))
  387.  
  388. if (dataset.scriptType === 'library') {
  389. data.increaseLibraries()
  390. } else if (dataset.scriptLanguage === 'js') {
  391. data.increaseScripts()
  392. } else {
  393. data.increaseStyles()
  394. }
  395. })
  396. }
  397.  
  398. const stats = new Stats(currentTranslation.stats, {
  399. total: data.total,
  400. daily: data.daily
  401. }, data.stats)
  402.  
  403. const works = new Stats(currentTranslation.works, {
  404. scripts: data.scripts,
  405. styles: data.styles,
  406. libraries: data.libraries
  407. }, data.works)
  408.  
  409. $stats.appendChild(stats.statsNode)
  410. $stats.appendChild(works.statsNode)
  411.  
  412. Styles.init()
  413.  
  414. document.querySelector('#about-user').appendChild($stats)
  415. }
  416. }
  417.  
  418. Main.init()
  419. })()
  420.  
  421.  
  422.  
  423.  
  424.  
  425.  
  426.  
  427.  
  428.  
  429.  
  430.  
  431.  
  432.  
  433.  
  434.