MetadataParser

A solution to parse user-script metadata blocks.

Detta skript bör inte installeras direkt. Det är ett bibliotek för andra skript att inkludera med meta-direktivet // @require https://update.greasyfork.org/scripts/566592/1756806/MetadataParser.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!)

Upphovsman
NotYou
Version
1.0.0
Skapad
2026-02-17
Uppdaterad
2026-02-17
Size
31,4 KB
Licens
N/A

MetadataParser

Description

A solution to parse user-script metadata blocks.

Library contains 3 classes:

  • Metadata - to manage metadata (get keys, set keys, remove keys, loop through keys, stringify result etc.)
  • MetadataParser - to parse metadata
  • MetadataSanitizer - to sanitize parsed metadata (sanitization only is for the keys, not for the values)

Dependencies

// @require https://unpkg.com/[email protected]/lib/index.umd.js
// @require https://update.greasyfork.org/scripts/565798/1752557/Zod%203x%20Error%20Formatter.js

Usage Example:

// ==UserScript==
// @name Userscript
// @namespace -
// @version 1.0.0
// @description try to take over the world!
// @author NotYou
// @match *://*/*
// @license MIT
// @grant GM.xmlHttpRequest
// @require https://unpkg.com/[email protected]/lib/index.umd.js
// @require https://update.greasyfork.org/scripts/565798/1752557/Zod%203x%20Error%20Formatter.js
// @require https://update.greasyfork.org/scripts/445697/1756471/Greasy%20Fork%20API.js
// @require https://update.greasyfork.org/scripts/566592/1756464/MetadataParser.js
// @connect greasyfork.org
// ==/UserScript==

/* global GreasyFork, MetadataParser, MetadataSanitizer */

!async function() {
    'use strict';

    const targetUserId = 824432
    const GF = new GreasyFork()
    const userData = await GF.getUserData(targetUserId)

    const targetScriptId = userData.scripts[Math.floor(Math.random() * userData.scripts.length)].id
    const scriptMeta = await GF.script.getMeta(targetScriptId)

    const result = new MetadataParser({
        sanitizer: new MetadataSanitizer({
            format: 'tampermonkey'
        })
    }).safeParse(scriptMeta)

    if (result.success) {
        const metadata = result.data

        console.log(`Script with the name ${metadata.get('name').value} ${metadata.has('require')
                    ? `requires these libraries: ${metadata.getAll('require').map(item => `"${item.value}"`).join(', ')}`
                    : `is indepedent script!`}`)
    } else {
        console.log(`User Id: "${targetUserId}"; Script Id: "${targetScriptId}"; Error ${result.error}`)
    }
}();

Docs

Types

Not real types, just typescript representation. Zod is used for the runtime types

MetadataItem

interface MetadataItem {
  key: string
  value: string | null
  locale: string | null // string format xx, or xx-YY
}

FormatIdSchema

Used for the metadata sanitizer

type FormatIdSchema = "greasyfork" | "greasemonkey" | "tampermonkey" | "violentmonkey" | "scriptcat"

ConfigSchema

Metadata sanitizer config

interface ConfigSchema {
  format: FormatIdSchema
  allowNonExistentKeys?: boolean // default: true
  allowUnexpectedMultilingualKeys?: boolean // default: false
  allowDuplicatesForUniqueKeys?: boolean // default: false
  requireMandatoryKeys?: boolean // default: true
}

ParseOptionsSchema

Options for the metadata parser

interface ParseOptionsSchema {
  sanitizer?: InstanceType<typeof MetadataSanitizer>
  strictSyntax?: boolean // default: false
}

Metadata

methods shown further are instance methods only.

Note: MetadataParser.prototype.parse returns Metadata instance

constructor(metadata: MetadataItem[])

getRawData(): MetadataItem[]

gets an array of metadata directly

const metadata = new MetadataParser().parse(`// ==UserScript==
// @some-key some value
// ==/UserScript==`)

console.log(metadata.getRawData())

/*
[
    {
        "key": "some-key",
        "value": "some value",
        "locale": null
    }
]
*/

indexOf(key: string, locale: string | null = null): number

returns index of a first found key

const metadata = new MetadataParser().parse(`// ==UserScript==
// @some-key some value
// @some-key:en-US some value
// ==/UserScript==`)

console.log(metadata.indexOf('some-key')) // 0
console.log(metadata.indexOf('some-key', 'en-US')) // 1
console.log(metadata.indexOf('some-key-that-does-not-exist')) // -1

get(key: string, locale: string | null = null): MetadataItem | undefined

const metadata = new MetadataParser().parse(`// ==UserScript==
// @some-key some value
// @some-key:en-US some value
// ==/UserScript==`)

console.log(metadata.get('some-key')) // { key: "some-key", value: "some value", locale: null }
console.log(metadata.get('some-key', 'en-US')) // { key: "some-key", value: "some value", locale: "en-US" }
console.log(metadata.get('some-key-that-does-not-exist')) // undefined

getAll(key: string, locale: string | null = null): MetadataItem[]

const metadata = new MetadataParser().parse(`// ==UserScript==
// @some-key some value
// @some-key:en-US some value
// ==/UserScript==`)

console.log(metadata.getAll('some-key'))
/*
[
    {
        "key": "some-key",
        "value": "some value",
        "locale": null
    },
    {
        "key": "some-key",
        "value": "some value",
        "locale": "en-US"
    }
]
*/
console.log(metadata.getAll('some-key', 'en-US')) // [{ key: "some-key", value: "some value", locale: "en-US" }]
console.log(metadata.getAll('some-key-that-does-not-exist')) // []

has(key: string, locale: string | null = null)

const metadata = new MetadataParser().parse(`// ==UserScript==
// @some-key some value
// @some-key:en-US some value
// ==/UserScript==`)

console.log(metadata.has('some-key')) // true
console.log(metadata.has('some-key', 'en-US')) // true
console.log(metadata.has('some-key', 'fr')) // false
console.log(metadata.has('some-key-that-does-not-exist')) // false

add(item: MetadataItem): void

const metadata = new MetadataParser().parse(`// ==UserScript==
// @some-key some value
// @some-key:en-US some value
// ==/UserScript==`)

metadata.add({
  key: 'new-value',
  value: 'just another value',
  locale: null
})

console.log(metadata.stringify(true))
/*
// ==UserScript==
// @some-key       some value
// @some-key:en-US some value
// @new-value      just another value
// ==/UserScript==
*/

set(key: string, value: string, locale: string | null = null): void

If doesn't find item with provided key, then method just creates the item using the method add

const metadata = new MetadataParser().parse(`// ==UserScript==
// @some-key some value
// @some-key:en-US some value
// ==/UserScript==`)

metadata.set('some-key', 'another value')
metadata.set('new-key', 'new value')

console.log(metadata.stringify(true))
/*
// ==UserScript==
// @some-key       another value
// @some-key:en-US some value
// @new-key        new value
// ==/UserScript==
*/

forEach(callback: (value: MetadataItem, index: number, array: MetadataItem[])): void

const metadata = new MetadataParser().parse(`// ==UserScript==
// @first-item some value
// @second-item some value
// @third-item some value
// @fourth-item some value
// ==/UserScript==`)

metadata.forEach(item => {
    console.log(item.key)
})
/*
first-item
second-item
third-item
fourth-item
*/

remove(key: string, locale: string | null = null): MetadataItem[]

returns removed element

const metadata = new MetadataParser().parse(`// ==UserScript==
// @some-key some value
// @some-key:en-US some value
// @not-needed some value
// ==/UserScript==`)

metadata.remove('not-needed') // [ { key: "not-needed", value: "some value", locale: null } ]
metadata.remove('some-key', 'en-US') // [ { key: "some-key", value: "some value", locale: "en-US" } ]

console.log(metadata.stringify(true))
/*
// ==UserScript==
// @some-key some value
// ==/UserScript==
*/

removeAll(key: string, locale: string | null = null): MetadataItem[]

returns removed keys

const metadata = new MetadataParser().parse(`// ==UserScript==
// @some-key some value
// @some-key:en-US some value
// @another-key another value
// @another-key another value
// @another-key another value
// ==/UserScript==`)

console.log(metadata.removeAll('some-key'))
/*
[
  {
    key: "some-key",
    value: "some value",
    locale: null
  },
  {
    key: "some-key",
    value: "some value",
    locale: 'en-US'
  },
]

console.log(metadata.stringify(true))
/*
// ==UserScript==
// @another-key another value
// @another-key another value
// @another-key another value
// ==/UserScript==
*/

stringify(pretty: boolean = false): string

returns stringified and formatted version of the metadata. If pretty argument is true, then metadata values will be aligned

const metadata = new Metadata([
    {
        key: 'key',
        value: 'some value',
        locale: null
    },
    {
        key: 'second-key',
        value: 'second value',
        locale: null
    },
    {
        key: 'another-key',
        value: 'another value',
        locale: 'es-ES'
    },
])

console.log(metadata.stringify())

/*
// ==UserScript==
// @key some value
// @second-key second value
// @another-key:es-ES another value
// ==/UserScript==
*/


console.log(metadata.stringify(true))

/*
// ==UserScript==
// @key               some value
// @second-key        second value
// @another-key:es-ES another value
// ==/UserScript==
*/

MetadataParser

constructor(defaultOptions: ParseOptionsSchema)

defaultOptions are the options that will be provided if the options argument in parse and/or safeParse method is not provided.

parse(metadataString: string, options: ParseOptionsSchema): InstanceType<typeof Metadata>

const metadataParser = new MetadataParser()

metadataParser.parse(`// ==UserScript==
// @usable-key and a real value
// ==/UserScript==`) // no errors!


metadataParser.parse(`// ==UserScript==
// @usable-key and a real value
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey'
  })
}) // SanitizationError: Not all mandatory keys are present. These keys are missing: "name", "version", "description"


metadataParser.parse(`// ==UserScript==
// @name User-script
// @version 1.0.0
// @description Hello!
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey'
  })
}) // no errors!

metadataParser.parse(`// ==UserScript==
// @name User-script
// @version 1.0.0

some random text

// @description Hello!
// ==/UserScript==`, {
  strictSyntax: true
}) // ParsingError: Invalid syntax, cannot parse the metadata. Invalid line: "some random text"

metadataParser.parse(`// ==UserScript==
// @name User-script
// @ version 1.0.0
// @description Hello!
// ==/UserScript==`, {
  strictSyntax: true
}) // ParsingError: ParsingError: Invalid syntax, cannot parse the metadata. Invalid line: "// @ version  1.0.0"


metadataParser.parse(`// ==UserScript==
// @name User-script

this line and the line  below will be ignored, since they use invalid syntax that is not part of metadata block syntax
// @ version 1.0.0
// @description Hello!
// ==/UserScript==`, {
  strictSyntax: false
}) // no errors! But version item is ignored since it is invalid

safeParse(metadataString: string, options: ParseOptionsSchema): { success: true, data: InstanceType<typeof Metadata> } | { success: false, error: InstanceType<typeof Error> | InstanceType<typeof ParsingError> | InstanceType<typeof SanitizationError> }

const metadataParser = new MetadataParser()

const result = metadataParser.safeParse(`// ==UserScript==
// @name User-script
// @version 1.0.0
// @description Hello!
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey'
  })
})

if (result.success) {
  const message = `Parsed script has version ${result.data.get('version').value}`

  console.log(message)
} else {
  console.log('Script metadata block was not parsed successfully!')
}

const result2 = metadataParser.safeParse(`// ==UserScript==
// @name User-script
// @version 1.0.0
// @version:en-US 1.0.0USA
// @description Hello!
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey'
  })
})

if (result2.success) {
  const message = `Parsed script has version ${result.data.get('version').value}`

  console.log(message)
} else {
  console.log('Script metadata block was not parsed successfully!')
}

MetadataSanitizer

constructor(config: ConfigSchema)

sanitize(metadata: InstanceType<typeof Metadata>): InstanceType<typeof Metadata>

console.log(new MetadataParser().safeParse(`// ==UserScript==
// @name User-script
// @version 1.0.0
// @description Hello!
// @fake-key
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey'
  })
})) // no errors!

console.log(new MetadataParser().safeParse(`// ==UserScript==
// @name User-script
// @version 1.0.0
// @description Hello!
// @fake-key
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey',
    allowNonExistentKeys: false
  })
}).error) // SanitizationError: You cannot use key "fake-key" because it is not available in the format "tampermonkey"



console.log(new MetadataParser().safeParse(`// ==UserScript==
// @name User-script
// @version 1.0.0
// @version:en-US 1.0.0USA
// @description Hello!
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey'
  })
}).error) // SanitizationError: You cannot use locale codes for the non-multilingual key "version" with the format "tampermonkey"

console.log(new MetadataParser().safeParse(`// ==UserScript==
// @name User-script
// @version 1.0.0
// @version:en-US 1.0.0USA
// @description Hello!
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey',
    allowUnexpectedMultilingualKeys: true
  })
})) // no errors!



console.log(new MetadataParser().safeParse(`// ==UserScript==
// @name User-script
// @name New User-Script
// @version 1.0.0
// @description Hello!
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey'
  })
}).error) // SanitizationError: You cannot use key "name" multiple times with the format "tampermonkey"

console.log(new MetadataParser().safeParse(`// ==UserScript==
// @name User-script
// @name New User-Script
// @version 1.0.0
// @description Hello!
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey',
    allowDuplicatesForUniqueKeys: true
  })
})) // no errors!



console.log(new MetadataParser().safeParse(`// ==UserScript==
// @name User-script
// @description Hello!
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey'
  })
}).error) // SanitizationError: Not all mandatory keys are present. These keys are missing: "version"

console.log(new MetadataParser().safeParse(`// ==UserScript==
// @name User-script
// @description Hello!
// ==/UserScript==`, {
  sanitizer: new MetadataSanitizer({
    format: 'tampermonkey',
    requireMandatoryKeys: false
  })
}))