Greasy Fork User Statistics+

shows user statistics as total installs, total scripts etc.

当前为 2022-11-19 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name Greasy Fork User Statistics+
// @namespace -
// @version 1.3.0
// @description shows user statistics as total installs, total scripts etc.
// @author NotYou
// @include *greasyfork.org/*/users/*
// @include *sleazyfork.org/*/users/*
// @license GPL-3.0-or-later
// @run-at document-end
// @grant none
// ==/UserScript==

(function() {
    let translations = {
        'ar': {
            stats: 'إحصائيات المستخدم',
            works: 'يعمل المستخدم',
        },
        'bg': {
            stats: 'Потребителска статистика',
            works: 'Потребителят работи',
        },
        'cs': {
            stats: 'Statistiky uživatelů',
            works: 'Uživatel pracuje',
        },
        'da': {
            stats: 'Brugerstatistik',
            works: 'Brugeren fungerer',
        },
        'de': {
            stats: 'Benutzerstatistiken',
            works: 'Benutzer funktioniert',
        },
        'el': {
            stats: 'Στατιστικά στοιχεία χρηστών',
            works: 'Ο χρήστης λειτουργεί',
        },
        'en': {
            stats: 'User statistics',
            works: 'User works',
        },
        'eo': {
            stats: 'Statistiko de uzantoj',
            works: 'Uzanto funkcias',
        },
        'es': {
            stats: 'Estadísticas de usuario',
            works: 'El usuario trabaja',
        },
        'fi': {
            stats: 'Käyttäjätilastot',
            works: 'Käyttäjä toimii',
        },
        'fr': {
            stats: 'Statistiques d\'utilisateurs',
            works: 'L\'utilisateur travaille',
        },
        'he': {
            stats: 'סטטיסטיקות משתמשים',
            works: 'משתמש עובד',
        },
        'hu': {
            stats: 'Felhasználói statisztikák',
            works: 'Felhasználó működik',
        },
        'id': {
            stats: 'Statistik pengguna',
            works: 'Pengguna bekerja',
        },
        'it': {
            stats: 'Statistiche utente',
            works: 'L\'utente lavora',
        },
        'ja': {
            stats: 'ユーザー統計',
            works: 'ユーザーは動作します',
        },
        'ko': {
            stats: '사용자 통계',
            works: '사용자 작품',
        },
        'ne': {
            stats: 'Gebruikersstatistieken',
            works: 'Gebruiker werkt',
        },
        'pl': {
            stats: 'Statystyki użytkowników',
            works: 'Użytkownik pracuje',
        },
        'ro': {
            stats: 'Statistici utilizatori',
            works: 'Utilizatorul lucrează',
        },
        'ru': {
            stats: 'Статистика пользователей',
            works: 'Пользовательские работы',
        },
        'tr': {
            stats: 'Kullanıcı istatistikleri',
            works: 'Kullanıcı işleri',
        },
        'uk': {
            stats: 'Статистика користувачів',
            works: 'Користувач працює',
        },
        'vi': {
            stats: 'Thống kê người dùng',
            works: 'Người dùng hoạt động',
        },
        'zh-CN': {
            stats: '用户统计',
            works: '用户作品',
        },
        'zh-TW': {
            stats: '用戶統計',
            works: '用戶作品',
        },
    }

    let currentTranslation = translations[document.querySelector('#language-selector-locale').value] || translations.en

    let data = new Proxy({
        total: 0,
        daily: 0,
        scripts: 0,
        styles: 0,
        libraries: 0,

        stats: 0,
        works: 0,
    }, {
        set(target, prop, value) {
            let t = target

            t[prop] = value

            t.stats = t.total + t.daily
            t.works = t.scripts + t.styles + t.libraries
        }
    })

    let stats = document.createElement('div')

    let isCitrusGF = Boolean(document.querySelector('#script-table'))

    stats.id = 'user-statistics'

    addStyle({
        '#user-statistics': {
            position: 'relative',
        },

        '.statistics-bar': {
            width: 'calc(100% - 2.4vw)',
            margin: '1em',
            marginBottom: '1.5em',
        },

        '.statistics-bar div': {
            height: '3px',
            borderRadius: '20px',
            padding: '3px',
            position: 'relative',
        },

        '.statistics-bar div[style*=" 0%"]': {
            padding: '0',
        },

        '.statistics-bar div[style*=" 0%"] + span': {
            color: 'unset !important',
        },

        '#user-statistics-pin-btn': {
            width: '25px',
            height: '25px',
            backgroundColor: 'rgb(191, 191, 191)',
            display: 'block',
            position: 'absolute',
            right: '10px',
            top: '10px',
            borderRadius: '50%',
            cursor: 'pointer',
            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=)',
            backgroundSize: '80% 80%',
            backgroundRepeat: 'no-repeat',
            backgroundPosition: '40% 40%',
            filter: 'grayscale(1)',
        },

        '#user-statistics.stats-pinned': {
            position: 'fixed',
            zIndex: '100',
            right: '10px',
            top: 'calc(50vh - 175px)',
            borderRadius: '4px',
            width: '400px',
            height: '350px',
            padding: '4px',
            border: '1px solid rgb(0, 0 ,0)',
        },

        '#user-statistics.stats-pinned #user-statistics-pin-btn': {
            /* GPL-3.0 @Saki */
            backgroundImage: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABqUlEQVR42u2USUvDUBSF8zIYEzqXlnbR/gYHdCe4VRyg0ILiwuF3OSxEsYWCInUruFMcfkO7KZ2blrRmIJ4XWsnCgVREhDwINDd957vvnJsQ5pcX8QAegIlEIpymaTP9fv/RWff5fAuiKD43m01jYkA8Hud1XT8MhUJZRVFyELseQddQy7fb7SIge9VqVXcNCAaDPMuyR6lUaofneQLAoNPpZC3LYsLhcCEQCEimaVrlcvmMELLbarUMVwBJkuYSicQdOpUgQCBs9Xo9DT+pPVP2ZixAB7VabRkW3ru2CJ2uRqNR2q1MAWPQ+DlEh7BtC91f/iTkFVxFv98vOgEQ15BBrtFoXE0cMl04wQYAeQpw1pHJEPZkALiZGADhTQDOIS59ZBEyGcCeLK6SawBCXkwmk7c0ZIfgK9VHJu+ngU1qvV5fgmVPrgAQ5tHwSTqd3uY4zh5T2i3dM7JsGmPKVCqVU/xv3/WY0hWLxQTDMI4xTRn4nYOI/aLhfh0NXKBWEAThAGP66dv8bcj0JPhUzKqq+uCsy7I8D/GXbrdrfrX/7z92HuD/A94AoxXXGeB8ZfgAAAAASUVORK5CYII=)',
        },

        '#user-statistics.stats-pinned .statistics-bar': {
            margin: '.5em',
        },

        '.statistics-bar div::before': {
            content: '""',
            position: 'absolute',
            width: 'calc(100% - 2px)',
            height: '7px',
            margin: '-5px -5px',
            borderRadius: '20px',
            boxShadow: '0 0 4px 0 rgba(0, 0, 0, 0.3), 0 0 4px 0 rgba(0, 0, 0, 0.3) inset',
            border: '1px solid rgb(34, 34, 34)',
            padding: '2px',
        },
    })

    let pinBtn = document.createElement('div')
    pinBtn.id = 'user-statistics-pin-btn'
    pinBtn.addEventListener('click', () => {
        let styles = window.getComputedStyle(document.body)


        if(stats.classList.contains('stats-pinned')) {
            stats.style.cssText = ''
        } else {
            stats.style.backgroundColor = styles.backgroundColor
            stats.style.color = styles.color
        }
        stats.classList.toggle('stats-pinned')
    })
    stats.appendChild(pinBtn)

    document.querySelectorAll('.script-list > li, #script-table tbody tr').forEach(e => {
        let dataset = e.dataset

        if(isCitrusGF && Object.keys(dataset).length === 0) {
            data.daily += +e.querySelector(':nth-child(4)').textContent
            data.total += +e.querySelector(':nth-child(5)').textContent

            data.scripts++
        } else {
            data.total += +dataset.scriptTotalInstalls
            data.daily += +dataset.scriptDailyInstalls

            if(dataset.scriptType === 'library') {
                data.libraries++
            } else {
                data[dataset.scriptLanguage === 'js' ? 'scripts' : 'styles']++
            }
        }
    })

    createStat({
        title: 'stats',
        values: [
            'total',
            'daily',
        ]
    })

    createStat({
        title: 'works',
        values: [
            'scripts',
            'styles',
            'libraries',
        ]
    })

    function createStat(input) {
        let { values, title } = input
        let titleEl = document.createElement('h3')

        if(data[title] > 0) {
            titleEl.textContent = currentTranslation[title]
            stats.appendChild(titleEl)

            values.forEach(e => {
                let value = data[e]
                let total = data[title]
                let width = value / total * 100

                if(width > 0) {
                    createBar(width, e, value)
                }
            })
        }
    }

    function createBar(width, name, value) {
        let bar = document.createElement('div')
        let barActual = document.createElement('div')
        let text = document.createElement('span')
        let bg = '128, 128, 128'

        bar.className = 'statistics-bar'

        switch (name) {
            case 'total':
                bg = '255, 28, 28'
                break
            case 'daily':
                bg = '255, 58, 58'
                break
            case 'styles':
                bg = '50, 149, 208'
                break
            case 'scripts':
                bg = '236, 203, 27'
                break
            case 'libraries':
                bg = '221, 102, 15'
                break
        }
        barActual.style.width = width + '%'
        barActual.style.backgroundColor = 'rgba(' + bg + ', .7)'

        text.textContent = capitalize(name) + ` (${value.toLocaleString()})`

        bar.appendChild(text)
        bar.appendChild(barActual)
        stats.appendChild(bar)
    }

    function addStyle(css) {
        let keys = Object.keys(css)
        let cssActual = ''
        let style = document.createElement('style')

        keys.forEach(e => {
            let _keys = Object.keys(css[e])

            cssActual += e + '{'

            _keys.forEach(r => {
                cssActual += r.replace(/[A-Z]/, m => `-${m.toLowerCase()}`) + ':' + css[e][r] + ';'
            })

            cssActual += '}'
        })

        style.textContent = cssActual
        document.querySelector('head').appendChild(style)
    }

    function capitalize(str) {
        return str[0].toUpperCase() + str.slice(1)
    }

    document.querySelector('#about-user').appendChild(stats)
})()