Greasy Fork API

Parse information on greasyfork.org

Ekde 2022/05/29. Vidu La ĝisdata versio.

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/445697/1055543/Greasy%20Fork%20API.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==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'
    }
}