Greasy Fork User Statistics+

shows user statistics as total installs, total scripts etc.

目前為 2022-11-19 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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)
})()