Greasy Fork API

Parse information on greasyfork.org

Verze ze dne 29. 05. 2022. Zobrazit nejnovější verzi.

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greasyfork.org/scripts/445697/1055431/Greasy%20Fork%20API.js

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name Greasy Fork API
// @namespace -
// @version 1.0.0
// @description Parse information on greasyfork.org
// @author NotYou
// @license LGPL-3.0
// @grant none
// ==/UserScript==

/*
"Program" is a set of instructions that a computer uses to perform a specific function.
"Library" is a program that is embedded in other programs.
"This library" refers to "Greasy Fork API" library.
"Account" is an arrangement in which a person uses the Internet or email services of a particular company, organization or individual person's web resource.
"I" refers to individual person that owns internet account "NotYou" at web resource "greasyfork.org".

I does not responsible for any
damage caused by this library
*/

/*
TODO:
- [x] Feedback
- [ ] Convesations
- [ ] Versions
*/

class GreasyFork {
    error(errorable, message) {
        if(!errorable) throw new Error(message)
    }

    languages() {
        return [
            'ar', 'bg', 'cs', 'da', 'de', 'el', 'en', 'eo', 'es', 'fi', 'fr', 'fr-CA', 'he', 'hu', 'id', 'it', 'ja', 'ko', 'nb', 'nl', 'pl', 'pt-BR', 'ro', 'ru', 'sk', 'sr', 'sv', 'th', 'tr', 'uk', 'ug', 'vi', 'zh-CN', 'zh-TW'
        ]
    }

    parseScriptElement(i) {
        if(!i||!i.dataset.scriptAuthors) return null
        let _ = null,
            data = i.dataset,
            rating = i.children[0].children[1].children[7] ? i.children[0].children[1].children[7].children[0].innerText.split('\n').join(' ').split(' ') : _,
            scriptLink = i.children[0].children[0].children[0] ? i.children[0].children[0].children[0].href : _,
            source = !scriptLink ? _ : data.scriptType == 'library' ? scriptLink.slice(0, scriptLink.indexOf('-'))+'/code/index.js' : scriptLink.slice(0, scriptLink.indexOf('-'))+'/code/script.user.js'
        return {
            name: data.scriptName,
            id: +(data.scriptId),
            version: data.scriptVersion,
            lang: data.scriptLanguage,
            installs: +(data.scriptTotalInstalls),
            dailyInstalls: +(data.scriptDailyInstalls),
            rating: +(data.scriptRatingScore),
            ratingGood: rating ? rating[0] == '0' ? 0 : +(rating[0]) : _,
            ratingOk: rating ? rating[1] == '0' ? 0 : +(rating[1]) : _,
            ratingBad: rating ? rating[2] == '0' ? 0 : +(rating[2]) : _,
            authors: JSON.parse(data.scriptAuthors) || data.scriptAuthors.replaceAll('"', '"'),
            createDate: data.scriptCreatedDate,
            updateDate: data.scriptUpdatedDate,
            type: data.scriptType,
            cssAsJs: data.cssAvailableAsJs == 'true' ? true : false,
            sensitive: data.sensitive == 'true' ? true : false,
            element: i,
            url: scriptLink,
            source: source
        }
    }

    parseScriptCodeMeta(code) {
        if(typeof code != 'string'||!code.match(/\/\/ ==UserScript==/)) return null
        var arr = code.replaceAll('\n', '').match(/\/\/ ==UserScript==.*\/\/ ==\/UserScript==/)[0].split('// ')
        for (let i = 0; i < 2; i++) arr.shift()
        arr.pop()
        let result = []
        for (let i of arr) {
            let data = i.replace(/ . +/, ' ').split(' ')
            let name = data[0]
            data.shift()
            let value = data.join(' ')
            result.push({
                meta: name,
                value: value
            })
        }
        return result
    }

    ls() {
        function ls(id) {
            if(document.querySelector(`#${id}`)) {
                let result = []
                for (let i of document.querySelector(`#${id}`).children) {
                    result.push(new GreasyFork().parseScriptElement(i))
                }
                return result
            } return null
        }
        return {
            scripts: ls('user-script-list'),
            browsed: ls('browse-script-list'),
            libraries: ls('user-library-script-list')
        }
    }

    get(q) {
        let parser = new DOMParser(),
            error = new GreasyFork().error,
            langs = new GreasyFork().languages(),
            str = str => str.replaceAll('\n', '').replaceAll(' ', ''),
            _ = null

        return {
            script(query = q) {
                let errMsg = 'Argument 1 is not defined'
                return {
                    async asJSON(id = query || q) {
                        error(id, errMsg)
                        return fetch(`https://greasyfork.org/scripts/${id}.json`).then((r) =>
                             r.json()
                        ).then((c) => {
                            return c
                        })
                    },
                    async code(id = query || q) {
                        error(id, errMsg)
                        return fetch(`https://greasyfork.org/scripts/${id}/code`).then((r) =>
                            r.text()
                        ).then((c) => {
                            return parser.parseFromString(c, 'text/html').querySelector('pre').innerText
                        })
                    },
                    async history(id = query || q) {
                        error(id, errMsg)
                        return fetch(`https://greasyfork.org/scripts/${id}/versions`).then((r) =>
                            r.text()
                        ).then((c) => {
                            let list = parser.parseFromString(c, 'text/html').querySelectorAll('.history_versions li'), result = []
                            for (let i of list) {
                                let ver = i.children[1].children[0],
                                    time = i.children[2],
                                    log = i.children[3]

                                result.push({
                                    version: {
                                        id: +(ver.href.match(/\?version=\d.+/)[0].replace('?version=', '')),
                                        text: ver.innerText,
                                        url: ver.href
                                    },
                                    time: {
                                        text: time.innerText,
                                        iso:  time.attributes.datetime.value
                                    },
                                    changelog: log ? {
                                        html: log.innerHTML,
                                        text: log.innerText
                                    } : _
                                })
                            }
                            return result
                        })
                    },
                    async feedback(id = query || q) {
                        error(id, errMsg)
                        return fetch(`https://greasyfork.org/scripts/${id}/feedback`).then((r) =>
                            r.text()
                        ).then((c) => {
                            let list = parser.parseFromString(c, 'text/html').querySelectorAll('.discussion-list-container'), result = []
                            for (let i of list) {
                                let di = i.children[0],
                                    ti = di.children[1],
                                    mt = di.children[0],
                                    mt0 = mt.children[0],
                                    mt1 = mt.children[1],
                                    a0 = mt0.children[0],
                                    a1 = mt1.children[0].children[0],
                                    url = ti.href,
                                    last = mt1.children[0].children

                                result.push({
                                    id: +(url.match(/ns\/\d.+/)[0].replace('ns/', '')),
                                    url: url,
                                    rating: ti.children[0].classList.contains('rating-icon') ? ti.children[0].innerText : _,
                                    text: ti.children[1] ? ti.children[1].innerText.replace('\n        ', '').replace('\n      ', '') : ti.children[0].innerText.replace('\n        ', '').replace('\n      ', ''),
                                    meta: {
                                        first: {
                                            id: +(a0.href.match(/\d.+-/)[0].replace(/-.+/, '').replace('-', '')),
                                            url: a0.href,
                                            nickname: a0.innerText
                                        },
                                        last: {
                                            id: +(a1.href.match(/\d.+-/)[0].replace(/-.+/, '').replace('-', '')),
                                            url: a1.href,
                                            nickname: a1.innerText
                                        },
                                        time: {
                                            first: {
                                                iso: mt0.children[1].attributes.datetime.value,
                                                text: mt0.children[1].innerText
                                            },
                                            last: last[last.length-1].attributes.datetime ? {
                                                iso:  last[last.length-1].attributes.datetime.value,
                                                text: last[last.length-1].innerText
                                            } : _
                                        }
                                    }
                                })
                            }
                            return result.length > 1 ? result : _
                        })
                    },
                    async stats(id = query || q) {
                        error(id, errMsg)
                        return fetch(`https://greasyfork.org/scripts/${id}/stats.json`).then((r) =>
                            r.json()
                        ).then((c) => {
                            return c
                        })
                    },
                    async info(id = query || q, options = {}) {
                        error(id, errMsg)
                        return fetch(str(`https://greasyfork.org/
                        ${options.locale && langs.includes(options.locale) ? options.locale : 'en'}
                        /scripts/${id}${options.locale ? `&locale_override=1` : ''}`)).then((r) =>
                            r.text()
                        ).then((c) => {
                            let doc = parser.parseFromString(c, 'text/html'),
                                screenshots = _,
                                addInfo = _,
                                appliesTo = [],
                                comp = _,
                                support = doc.querySelector('#script-feedback-suggestion').children.length == 3 ? support = doc.querySelector('#script-feedback-suggestion').children[0].href : _,
                                getInfo = value => doc.querySelector(`dd.script-${value}`),
                                license = getInfo('show-license'),
                                d_installs = getInfo('show-daily-installs') ? +(getInfo('show-daily-installs').innerText.replaceAll(',', '')) : _,
                                installs = getInfo('show-total-installs') ? +(getInfo('show-total-installs').innerText.replaceAll(',', '')) : _,
                                r_g = doc.querySelector('.good-rating-count') ? +(doc.querySelector('.good-rating-count').innerText) : _,
                                r_o = doc.querySelector('.ok-rating-count') ? +(doc.querySelector('.ok-rating-count').innerText) : _,
                                r_b = doc.querySelector('.bad-rating-count') ? +(doc.querySelector('.bad-rating-count').innerText) : _,
                                c_date_text = getInfo('show-created-date').innerText,
                                c_date_iso = doc.querySelector('dd.script-show-created-date').children[0].children[0].attributes.datetime.value,
                                u_date_text = getInfo('show-updated-date').innerText,
                                u_date_iso = doc.querySelector('dd.script-show-updated-date').children[0].children[0].attributes.datetime.value,
                                global = doc.querySelector('#script-info header'),
                                install = doc.querySelector('#install-area') ? doc.querySelectorAll('#install-area *:not(.install-help-link)') : _,
                                data = install ? install[0].dataset : _,
                                yours = doc.querySelector('#script-links').children,
                                require = doc.querySelector('#script-content p code') ? doc.querySelector('#script-content p code').innerText.replace('// @require ', '') : _

                            if(doc.querySelector('.user-screenshots')) {
                                screenshots = []
                                doc.querySelectorAll('.user-screenshots a').forEach((e) => {
                                    screenshots.push({
                                        source: e.href,
                                        thumbnail: e.children[0].src
                                    })
                                })
                            }
                            doc.querySelectorAll('dd.script-show-applies-to li').forEach((e) => {
                                appliesTo.push(e.innerText)
                            })
                            if(doc.querySelector('.script-show-compatibility')) {
                                comp = []
                                doc.querySelectorAll('.script-show-compatibility img').forEach((e) => {
                                    comp.push({
                                        browser: e.alt.replace('Compatible with ', ''),
                                        value: e.title.replaceAll('\n', ' '),
                                    })
                                })
                            }
                            if(doc.querySelector('#additional-info')) addInfo = doc.querySelector('#additional-info')
                            return {
                                id: +(id),
                                name: global.children[0].innerText,
                                desc: global.children[1].innerText,
                                version: getInfo('show-version').innerText,
                                isYour: yours[yours.length-1].children[0].href.match(/\/admin/) ? true : false,
                                isPrevVersion: data ? data.isPreviousVersion == 'true' ? true : false : _,
                                installs: {
                                    daily: d_installs,
                                    total: installs,
                                },
                                rating: {
                                    good: r_g,
                                    ok: r_o,
                                    bad: r_b,
                                },
                                date: {
                                    created: {
                                        text: c_date_text,
                                        iso: c_date_iso,
                                    },
                                    updated: {
                                        text: u_date_text,
                                        iso: u_date_iso,
                                    }
                                },
                                license: {
                                    name: license.innerText,
                                    url: license.children[0].children[0] ? license.children[0].children[0].href : _
                                },
                                screenshots: screenshots,
                                additionalInfo: addInfo ? [
                                    {
                                        html: addInfo.innerHTML,
                                        text: addInfo.innerText
                                    }
                                ] : _,
                                appliesTo: appliesTo,
                                compatibility: comp,
                                supportUrl: support,
                                requireUrl: require
                            }
                        })
                    },
                    async set(id = query || q, options = {}) {
                        error(id, errMsg)
                        return id && fetch(str(`https://greasyfork.org/
                        ${options.locale && langs.includes(options.locale) ? options.locale : 'en'}
                        /scripts?set=${id}
                        ${options.sort ? `&sort=${options.sort}` : ''}
                        ${options.page ? `&page=${options.page}` : ''}
                        ${options.localeFilter ? `&filter_locale=${options.localeFilter}` : ''}
                        ${options.locale ? `&locale_override=1` : ''}`)).then(r =>
                            r.text()
                        ).then((c) => {
                            let result = [], list = parser.parseFromString(c, 'text/html').querySelectorAll('#browse-script-list li')
                            for (let i of list) {
                                result.push(new GreasyFork().parseScriptElement(i))
                            }
                            return result
                        })
                    },
                }
            },

            async user(id = q) {
                error(id, 'Argument 1 is not defined')
                return fetch(`https://greasyfork.org/users/${id}`).then((r) =>
                    r.text()
                ).then((c) => {
                    let doc = parser.parseFromString(c, 'text/html'), url = new URL(doc.baseURI)
                    return {
                        nickname: doc.querySelector('#about-user h2').firstChild.data,
                        id: +(id),
                        isMod: doc.querySelector('#about-user > h2 .badge-moderator') ? true : false,
                        scripts:  (() => {
                            if(doc.querySelector('#user-script-list')) {
                                let result = []
                                for (let i of doc.querySelector('#user-script-list').children) {
                                    result.push(new GreasyFork().parseScriptElement(i))
                                }
                                return result
                            } return _
                        })(),
                        libraries: (() => {
                            if(doc.querySelector('#user-library-script-list')) {
                                let result = []
                                for (let i of doc.querySelector('#user-library-script-list').children) {
                                    result.push(new GreasyFork().parseScriptElement(i))
                                }
                                return result
                            } return _
                        })(),
                        scriptSets: (() => {
                            if(doc.querySelector('#user-script-sets')) {
                                let result = []
                                for (let i of doc.querySelector('#user-script-sets').children) {
                                    if(i.firstChild.data === 'Favorites ') return _
                                    let data = i.firstChild.data.split(':')
                                    result.push({
                                        name: data[0],
                                        desc: data[1].replace(' ', '').split('').reverse().join('').replace(' ', '').split('').reverse().join(''),
                                        id: i.children[0].href.replace(/.*?\/scripts\?set=/, '')
                                    })
                                }
                                return result
                            } return _
                        })(),
                        recentComments: (() => {
                            if(doc.querySelectorAll('#user-discussions section ul li a')) {
                                let result = []
                                for (let i of doc.querySelectorAll('#user-discussions section ul li a')) {
                                    result.push({
                                        title: i.lastChild.innerText,
                                        id: !i.href.match(/ns\/\d+/) ? +(i.href.match(/\d+/)[0]) : +(i.href.match(/ns\/\d+/)[0].replace('ns/', '')),
                                        action: i.firstChild.data.replace(': ', ''),
                                        URL: i.href
                                    })
                                }
                                return result.length > 1 ? result : _
                            } return _
                        })()
                    }
                })
            },

            async search(query = q, type, options = {}) {
                if(typeof query != 'string') throw new Error('Argument 1 is not string')
                function searchURL(path) {
                    return str(`https://greasyfork.org/
                    ${options.locale && langs.includes(options.locale) ? options.locale : 'en'}/
                    ${path}${options.asJSON == true ? '.json' : ''}?q=${query ? query : ''}
                    ${options.sort ? `&sort=${options.sort}` : ''}
                    ${options.page ? `&page=${options.page}` : ''}
                    ${options.locale ? `&locale_override=1` : ''}`)
                }
                if(type === 'script') {
                    return fetch(searchURL('scripts')).then((r) =>
                        options.asJSON == true ? r.json() : r.text()
                    ).then((c) => {
                        if(options.asJSON == true) return c
                        let result = [], list = parser.parseFromString(c, 'text/html').querySelectorAll('#browse-script-list li:not(.ad-entry)')
                        for (let i of list) {
                            result.push(new GreasyFork().parseScriptElement(i))
                        }
                        return result
                    })
                }
                if(type === 'library') {
                    return fetch(searchURL('scripts/libraries')).then((r) =>
                        options.asJSON == true ? r.json() : r.text()
                    ).then((c) => {
                        if(options.asJSON == true) return c
                        let result = [], list = parser.parseFromString(c, 'text/html').querySelectorAll('#browse-script-list li')
                        for (let i of list) {
                            result.push(new GreasyFork().parseScriptElement(i))
                        }
                        return result
                    })
                } if(type === 'user') {
                    return fetch(searchURL('users')).then((r) =>
                        options.asJSON == true ? r.json() : r.text()
                    ).then((c) => {
                        if(options.asJSON == true) return c
                        let result = [], list = parser.parseFromString(c, 'text/html').querySelectorAll('#browse-user-list li')
                        for (let i of list) {
                            result.push({
                                nickname: i.children[0].innerText,
                                id: +(i.children[0].href.replace(/.*?\/users\//, '').split('-')[0]),
                                badge: (i.children[1] ? i.children[1].innerText.toLowerCase() : 'none'),
                                url: i.children[0].href,
                                scripts: +(i.innerHTML.match(/- .+/, '')[0].match(/\d/)[0])
                            })
                        }
                        return result
                    })
                } if(type === 'list') {
                    return [
                        'script',
                        'library',
                        'user',
                        'list'
                    ]
                } else throw new Error(`Argument 1 ${type} is not defined`)
            },

            async modlog(options = {}) {
                return fetch(str(`https://greasyfork.org/
                ${options.locale && langs.includes(options.locale) ? options.locale : 'en'}
                /moderator_actions
                ${options.page ? `?page=${options.page}` : '?page=1'}
                ${options.locale ? `&locale_override=1` : ''}`)).then(r =>
                    r.text()
                ).then(c => {
                    let trs = parser.parseFromString(c, 'text/html').querySelectorAll('.log-table > tbody > tr'), result = []
                    for (let i of trs) {
                        let time = i.children[0].children[0],
                            userUrl = i.children[1].children[0].href,
                            item = i.children[2].children[0],
                            res = i.children[4]

                        result.push({
                            time: {
                                text: time.innerText,
                                iso: time.attributes.datetime.value
                            },
                            mod: {
                                id: userUrl.match(/\d.+-/) ? +(userUrl.match(/\d.+-/)[0].replace('-', '')) : userUrl.match(/\d-/) ? +(userUrl.match(/\d-/)[0].replace('-', '')) : _,
                                url: userUrl,
                                nickname: i.children[1].children[0].innerText
                            },
                            item: i.children[2] ? {
                                name: item ? item.innerText : _,
                                url: item ? item.href : _,
                                type: item ? str(item.previousSibling.data.replace(': ', '')) : _
                            } : _,
                            action: i.children[3].innerText,
                            reason: {
                                name: res.innerText.replaceAll(' ', '').replaceAll('\n', ''),
                                url: res.children[0].children[0] ? res.children[0].children[0].href : _
                            }
                        })
                    }
                    return result
                })
            },
        }
    }

    async action(action, options = {}) {
        let error = new GreasyFork().error,
            parser = new DOMParser()

        if(action === 'install') {
            error(options.id, 'Argument 2 { id } is not defined')
            setTimeout(() => {
                window.open(`https://greasyfork.org/scripts/${options.id}/code/source.user.${options.lang === 'css' ? 'css' : 'js'}`, '_top')
            }, options.timeout * 1e3 || 0)
        }
        if(action === 'signout') {
            setTimeout(() => {
                fetch('https://greasyfork.org/users/sign_out')
            }, options.timeout * 1e3 || 0)
        } if(action === 'list') {
            return [
                'install',
                'signout',
                'list'
            ]
        }
        else throw new Error(`Argument 1 ${action} is not defined`)
    }

    version() {
        return '1.0.0'
    }
}