A solution to parse user-script metadata blocks.
Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/566592/1756806/MetadataParser.js
A solution to parse user-script metadata blocks.
Library contains 3 classes:
// @require https://unpkg.com/[email protected]/lib/index.umd.js
// @require https://update.greasyfork.org/scripts/565798/1752557/Zod%203x%20Error%20Formatter.js
// ==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}`)
}
}();
Not real types, just typescript representation. Zod is used for the runtime types
interface MetadataItem {
key: string
value: string | null
locale: string | null // string format xx, or xx-YY
}
Used for the metadata sanitizer
type FormatIdSchema = "greasyfork" | "greasemonkey" | "tampermonkey" | "violentmonkey" | "scriptcat"
Metadata sanitizer config
interface ConfigSchema {
format: FormatIdSchema
allowNonExistentKeys?: boolean // default: true
allowUnexpectedMultilingualKeys?: boolean // default: false
allowDuplicatesForUniqueKeys?: boolean // default: false
requireMandatoryKeys?: boolean // default: true
}
Options for the metadata parser
interface ParseOptionsSchema {
sanitizer?: InstanceType<typeof MetadataSanitizer>
strictSyntax?: boolean // default: false
}
methods shown further are instance methods only.
Note: MetadataParser.prototype.parse returns Metadata instance
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
}
]
*/
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
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
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')) // []
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
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==
*/
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==
*/
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
*/
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==
*/
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==
*/
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==
*/
defaultOptions are the options that will be provided if the options argument in parse and/or safeParse method is not provided.
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
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!')
}
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
})
}))