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
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
})
}))