🎉 欢迎访问GreasyFork.Org 镜像站!本镜像站由公众号【爱吃馍】搭建,用于分享脚本。联系邮箱📮

Greasy fork 爱吃馍镜像

📂 缓存分发状态(共享加速已生效)
🕒 页面同步时间:2026/02/05 00:32:55
🔄 下次更新时间:2026/02/05 01:32:55
手动刷新缓存

Greasy Fork API

Get information from Greasy Fork and do actions in it.

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/1748148/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 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 3.1.0
// @description Get data from Greasy Fork, or/and do actions on Greasy Fork
// @author NotYou
// @license LGPL-3.0
// @connect greasyfork.org
// @connect sleazyfork.org
// @grant GM.xmlHttpRequest
// @grant GM.openInTab
// @require https://unpkg.com/[email protected]/lib/index.umd.js
// ==/UserScript==

!function (z) {
    'use strict';

    class Schemas {
        static Id = z.union([
            z.number().int().positive(),
            z.string().regex(/^(?!0)\d+$/)
        ])

        static Query = z.string().trim().optional()

        static Page = z.number().int().optional()

        static FilterLocale = z.boolean().optional()

        static get ScriptsQuery() {
            return z.object({
                q: this.Query,
                page: this.Page,
                filter_locale: this.FilterLocale,
                sort: z.union([
                    z.literal('total_installs'),
                    z.literal('ratings'),
                    z.literal('created'),
                    z.literal('updated'),
                    z.literal('name'),
                ]).optional(),
                by: this.Id.optional(),
                language: z.union([
                    z.literal('js'),
                    z.literal('css'),
                ]).optional()
            })
        }

        static get AdvancedScriptsQuery() {
            const Operator = z.union([
                z.literal('lt'),
                z.literal('gt'),
                z.literal('eq')
            ]).default('gt')

            const Installs = z.number().int().nonnegative().default(0)

            const Datetime = z.union([z.string().datetime(), z.custom(value => value === '')]).default('')

            return this.ScriptsQuery.extend({
                total_installs_operator: Operator,
                total_installs: Installs,
                daily_installs_operator: Operator,
                daily_installs: Installs,
                ratings_operator: Operator,
                ratings: z.number().min(0).max(1).default(0),
                created_operator: Operator,
                created: Datetime,
                updated_operator: Operator,
                updated: Datetime,
                entry_locales: z.array(z.number()).optional(),
                tz: z.string().regex(/^[A-Za-z0-9_+-]+\/[A-Za-z0-9_+-]+(?:\/[A-Za-z0-9_+-]+)?$/).optional()
            })
        }

        static get ScriptsBySiteQuery() {
            const HostnameFormat = z.custom(value => {
                try {
                    new URL(`https://${value}:80/`)

                    return true
                } catch {
                    return false
                }
            })

            return this.ScriptsQuery.extend({
                site: z.union([
                    z.literal('*'),
                    z.string().ip().trim(),
                    HostnameFormat
                ])
            })
        }

        static get ScriptSetQuery() {
            return this.ScriptsQuery.extend({
                set: this.Id
            }).omit({ by: true })
        }

        static get LibrariesQuery() {
            return z.object({
                q: this.Query,
                page: this.Page,
                filter_locale: this.FilterLocale,
                sort: z.union([
                    z.literal('created'),
                    z.literal('updated'),
                    z.literal('name')
                ]).optional(),
                by: this.Id.optional()
            })
        }

        static get UsersQuery() {
            return z.object({
                q: this.Query,
                page: this.Page,
                sort: z.union([
                    z.literal('name'),
                    z.literal('daily_installs'),
                    z.literal('total_installs'),
                    z.literal('ratings'),
                    z.literal('scripts'),
                    z.literal('created_scripts'),
                    z.literal('updated_scripts'),
                ]).optional(),
                author: z.boolean().optional()
            })
        }

        static GetResponse = z.object({
            params: z.array(
                z.tuple([
                    z.custom(value => value instanceof z.ZodSchema),
                    z.any()
                ])
            ),

            getUrl: z.function()
            .args(z.any().array())
            .returns(z.string()),

            type: z.union([
                z.literal('json'),
                z.literal('text')
            ])
        })

        static get Install() {
            return z.object({
                id: this.Id,
                type: z.union([z.literal('js'), z.literal('css')]).default('js')
            })
        }
    }

    class GreasyFork {
        constructor(isSleazyfork = false) {
            if (isSleazyfork) {
                this.hostname = 'api.sleazyfork.org'
            } else {
                this.hostname = 'api.greasyfork.org'
            }
        }

        Schemas = Schemas

        _formatZodError(zodError) {
            if (!(zodError instanceof z.ZodError)) {
                throw new Error('Provided value is not a ZodError')
            }

            const justDisplayMessage = issue => issue.message
            const formatPath = path => path.map(pathItem => {
                if (typeof pathItem === 'number') {
                    return `[${pathItem}]`
                }

                return pathItem.toString()
            }).join('.')

            const formatIssue = issue => {
                const issueFormatter = {
                    "invalid_type": issue => `${issue.message} at path: "${formatPath(issue.path)}"`,
                    "invalid_literal": issue => `${issue.message}, but got "${issue.received}"`,
                    "custom": justDisplayMessage,
                    "invalid_union": issue => {
                        const expectedValues = issue.unionErrors.map(unionError => `"${unionError.issues[0].expected}"`).join(' | ')

                        return `${issue.message} "${formatPath(issue.path)}", expected these values: ${expectedValues}`
                    },
                    "invalid_union_discriminator": justDisplayMessage,
                    "invalid_enum_value": justDisplayMessage,
                    "unrecognized_keys": justDisplayMessage,
                    "invalid_arguments": justDisplayMessage,
                    "invalid_return_type": justDisplayMessage,
                    "invalid_date": justDisplayMessage,
                    "invalid_string": issue => `Invalid string format, validation failed at "${issue.validation}"`,
                    "too_small": justDisplayMessage,
                    "too_big": justDisplayMessage,
                    "invalid_intersection_types": justDisplayMessage,
                    "not_multiple_of": justDisplayMessage,
                    "not_finite": justDisplayMessage
                }[issue.code]

                if (typeof issueFormatter === 'function') {
                    return issueFormatter(issue)
                }

                return `Got unrecognised error! Code: ${issue.code ?? 'undefined'}; Message: ${issue.message ?? 'undefined'}`
            }

            return zodError.issues.map(formatIssue).join('\n\n')
        }

        _getUrl(path) {
            return 'https://' + this.hostname + '/' + path
        }

        _formatHttpError(response) {
            if (typeof response !== 'object' || typeof response.status !== 'number' || typeof response.finalUrl !== 'string') {
                throw new Error('Provided object is not a response-like object')
            }

            const statusText = {
                400: 'Bad Request',
                401: 'Unauthorized',
                402: 'Payment Required',
                403: 'Forbidden',
                404: 'Not Found',
                405: 'Method Not Allowed',
                406: 'Not Acceptable',
                407: 'Proxy Authentication Required',
                408: 'Request Timeout',

                500: 'Internal Server Error',
                501: 'Not Implemented',
                502: 'Bad Gateway',
                503: 'Service Unavailable',
                504: 'Gateway Timeout',
            }[response.status] ?? `https://developer.mozilla.org/docs/Web/HTTP/Reference/Status/${status}`

            return `HTTP Error "${response.finalUrl}": ${response.status} ${statusText}`
        }

        _request(path, options = {}) {
            return new Promise((resolve, reject) => {
                GM.xmlHttpRequest({
                    url: this._getUrl(path),
                    anonymous: true,
                    onload: response => {
                        if (response.status === 200) {
                            resolve(response)
                        } else {
                            reject(this._formatHttpError(response))
                        }
                    },
                    onerror: reject,
                    ...options
                })
            })
        }

        _getTextData(path) {
            return this._request(path)
                .then(response => response.responseText)
        }

        _getJSONData(path) {
            return this._request(path, { responseType: 'json' })
                .then(response => response.response)
        }

        _dataToSearchParams(data) {
            for (const key in data) {
                const value = data[key]

                if (typeof value === 'boolean') {
                    data[key] = value ? 1 : 0
                } else if (typeof value === 'undefined' || value === null) {
                    delete data[key]
                }
            }

            return '?' + new URLSearchParams(data).toString()
        }

        _getResponse(options) {
            const result = this.Schemas.GetResponse.safeParse(options)

            if (!result.success) {
                throw new Error(this._formatZodError(result.error))
            }

            const results = options.params.map(([schema, param]) => schema.safeParse(param))
            const unsuccessfulResult = results.find(result => !result.success)

            if (unsuccessfulResult) {
                throw new Error(this._formatZodError(unsuccessfulResult.error))
            }

            const data = results.map(result => result.data)
            const url = options.getUrl(data)

            if (options.type === 'json') {
                return this._getJSONData(url)
            } else if (options.type === 'text') {
                return this._getTextData(url)
            }
        }

        get script() {
            return {
                getData: id => this._getResponse({
                    params: [
                        [this.Schemas.Id, id]
                    ],
                    getUrl: ([id]) => `scripts/${id}.json`,
                    type: 'json'
                }),

                getCode: id => this._getResponse({
                    params: [
                        [this.Schemas.Id, id]
                    ],
                    getUrl: ([id]) => `https://${this.hostname.replace('api.', '')}/scripts/${id}/code/script.js`,
                    type: 'text'
                }),

                getMeta: id => this._getResponse({
                    params: [
                        [this.Schemas.Id, id]
                    ],
                    getUrl: ([id]) => `https://${this.hostname.replace('api.', '')}/scripts/${id}/code/script.meta.js`,
                    type: 'text'
                }),

                getHistory: id => this._getResponse({
                    params: [
                        [this.Schemas.Id, id]
                    ],
                    getUrl: ([id]) => `scripts/${id}/versions.json`,
                    type: 'json'
                }),

                getStats: id => this._getResponse({
                    params: [
                        [this.Schemas.Id, id]
                    ],
                    getUrl: ([id]) => `scripts/${id}/stats.json`,
                    type: 'json'
                }),

                getStatsCsv: id => this._getResponse({
                    params: [
                        [this.Schemas.Id, id]
                    ],
                    getUrl: ([id]) => `scripts/${id}/stats.json`,
                    type: 'text'
                })
            }
        }

        getScripts(options = {}) {
            return this._getResponse({
                params: [
                    [this.Schemas.ScriptsQuery, options]
                ],
                getUrl: ([options]) => 'scripts.json' + this._dataToSearchParams(options),
                type: 'json'
            })
        }

        getScriptsAdvanced(options = {}) {
            return this._getResponse({
                params: [
                    [this.Schemas.AdvancedScriptsQuery, options]
                ],
                getUrl: ([options]) => {
                    options['entry_locales[]'] = options.entry_locales
                    delete options.entry_locales

                    return 'scripts.json' + this._dataToSearchParams(options)
                },
                type: 'json'
            })
        }

        getScriptsBySite(options = {}) {
            return this._getResponse({
                params: [
                    [this.Schemas.ScriptsBySiteQuery, options]
                ],
                getUrl: ([options]) => {
                    let url = `scripts/by-site/${options.site}.json`

                    delete options.site

                    return url + this._dataToSearchParams(options)
                },
                type: 'json'
            })
        }

        getSitesPopularity() {
            return this._getJSONData('/scripts/by-site.json')
        }

        getScriptSet(options = {}) {
            return this._getResponse({
                params: [
                    [this.Schemas.ScriptSetQuery, options]
                ],
                getUrl: ([options]) => 'scripts.json' + this._dataToSearchParams(options),
                type: 'json'
            })
        }

        getLibraries(options = {}) {
            return this._getResponse({
                params: [
                    [this.Schemas.LibrariesQuery, options]
                ],
                getUrl: ([options]) => 'scripts/libraries.json' + this._dataToSearchParams(options),
                type: 'json'
            })
        }

        getUserData(id) {
            return this._getResponse({
                params: [
                    [this.Schemas.Id, id]
                ],
                getUrl: ([id]) => `users/${id}.json`,
                type: 'json'
            })
        }

        getUsers(options = {}) {
            return this._getResponse({
                params: [
                    [this.Schemas.UsersQuery, options]
                ],
                getUrl: ([options]) => 'users.json' + this._dataToSearchParams(options),
                type: 'json'
            })
        }

        signOut() {
            return this._request('/users/sign_out')
        }

        installUserScript(options) {
            const result = this.Schemas.Install.safeParse(options)

            if (!result.success) {
                throw new Error(this._formatZodError(result.error))
            }

            options = result.data

            const url = this._getUrl(`scripts/${options.id}/code/userscript.user.${options.type}`)

            GM.openInTab(url, { active: true })
        }
    }

    let global = window

    if (typeof unsafeWindow !== 'undefined') {
        global = unsafeWindow
    }

    global.GreasyFork = GreasyFork
}(Zod)