-
Notifications
You must be signed in to change notification settings - Fork 6
/
userscript.plugin.ts
255 lines (248 loc) · 9.52 KB
/
userscript.plugin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
/**
* I18n field of the userscript.
*/
type I18nField = { [locale: string]: string };
/**
* Userscript's all headers.
*/
interface UserScriptOptions {
'require-template': string;
name: string;
'i18n-names': I18nField;
namespace: string;
description: string;
'i18n-descriptions': I18nField;
version: string;
author: string;
homepage: string;
homepageURL: string;
website: string;
source: string;
icon: string;
iconURL: string;
defaulticon: string;
icon64: string;
icon64URL: string;
updateURL: string;
downloadURL: string;
supportURL: string;
license: string;
include: string[];
match: string[];
exclude: string[];
require: string[];
resources: string[];
keyedResources: { [key: string]: string };
connect: string[];
'run-at': string;
grant: string[];
antifeature: string[];
noframes: boolean;
nocompat: string;
}
/**
* Brief interface of the main structure of "package.json".
*/
interface PackageJsonOptions {
name: string;
version: string;
description: string;
author: string;
homepage: string;
userscript: Partial<UserScriptOptions>;
dependencies: { [key: string]: string };
}
/**
* Generate a userscript's headers from "package.json" file.
*
* @returns {string} Return userscript's header.
*/
export function generateHeader() {
const packageJsonRaw = readFileSync(join(__dirname, '../package.json'), 'utf8');
const packageJson = JSON.parse(packageJsonRaw) as Partial<PackageJsonOptions>;
const userscript = packageJson.userscript as Partial<UserScriptOptions>;
// The regular expression used to remove the dependency version string prefix.
const dependencyVersionRegExp = /^[\^~]/;
// Userscript's header.
const headers = ['// ==UserScript=='];
/**
* Add userscript header's name.
* If the name is not set, the package name is used. If neither is set, an error is thrown.
*/
if (packageJson.name || userscript.name) {
headers.push(`// @name ${userscript.name ?? packageJson.name}`);
} else {
throw new Error('No name specified in package.json');
}
/**
* Add userscript header's i18n-names.
*/
if (userscript['i18n-names'] && typeof userscript['i18n-names'] === 'object') {
for (const [locale, name] of Object.entries(userscript['i18n-names'])) {
headers.push(`// @name:${locale} ${name}`);
}
}
/**
* Add userscript header's version.
* If the version is not set, the package version is used. If neither is set, an error is thrown.
*/
if (packageJson.version || userscript.version) {
headers.push(`// @version ${userscript.version ?? packageJson.version}`);
} else {
throw new Error('No version specified in package.json');
}
// Add userscript header's namespace.
if (userscript.namespace) {
headers.push(`// @namespace ${userscript.namespace}`);
}
// Add userscript header's description.
if (packageJson.description || userscript.description) {
headers.push(`// @description ${userscript.description ?? packageJson.description}`);
}
// Add userscript header's i18n-descriptions.
if (userscript['i18n-descriptions'] && typeof userscript['i18n-descriptions'] === 'object') {
for (const [locale, description] of Object.entries(userscript['i18n-descriptions'])) {
headers.push(`// @description:${locale} ${description}`);
}
}
// Add userscript header's author.
if (packageJson.author || userscript.author) {
headers.push(`// @author ${userscript.author ?? packageJson.author}`);
}
// Add userscript header's homepage, homepageURL, website or source.
if (packageJson.homepage || userscript.homepage) {
headers.push(`// @homepage ${userscript.homepage ?? packageJson['homepage']}`);
} else if (userscript.homepageURL) {
headers.push(`// @homepageURL ${userscript.homepageURL}`);
} else if (userscript.website) {
headers.push(`// @website ${userscript.website}`);
} else if (userscript.source) {
headers.push(`// @source ${userscript.source}`);
}
// Add userscript header's icon, iconURL or defaulticon.
if (userscript.icon) {
headers.push(`// @icon ${userscript.icon}`);
} else if (userscript.iconURL) {
headers.push(`// @iconURL ${userscript.iconURL}`);
} else if (userscript.defaulticon) {
headers.push(`// @defaulticon ${userscript.defaulticon}`);
}
// Add userscript header's icon64 or icon64URL.
if (userscript.icon64) {
headers.push(`// @icon64 ${userscript.icon64}`);
} else if (userscript.icon64URL) {
headers.push(`// @icon64URL ${userscript.icon64URL}`);
}
// Add userscript header's updateURL.
if (userscript.updateURL) {
headers.push(`// @updateURL ${userscript.updateURL}`);
}
// Add userscript header's downloadURL.
if (userscript.downloadURL) {
headers.push(`// @downloadURL ${userscript.downloadURL}`);
}
// Add userscript header's supportURL.
if (userscript.supportURL) {
headers.push(`// @supportURL ${userscript.supportURL}`);
}
// Add userscript header's license.
if (userscript.license) {
headers.push(`// @license ${userscript.license}`);
}
// Add userscript header's includes.
if (userscript.include && userscript.include instanceof Array) {
for (const include of userscript.include) {
headers.push(`// @include ${include}`);
}
}
// Add userscript header's matches.
if (userscript.match && userscript.match instanceof Array) {
for (const match of userscript.match) {
headers.push(`// @match ${match}`);
}
}
// Add userscript header's excludes.
if (userscript.exclude && userscript.exclude instanceof Array) {
for (const exclude of userscript.exclude) {
headers.push(`// @exclude ${exclude}`);
}
}
/**
* Add userscript header's requires.
* The package name and version will be obtained from the "dependencies" field,
* and the jsdelivr link will be generated automatically.
* You can also set the string template with the parameters "{dependencyName}" and "{dependencyVersion}"
* in the "require-template" field of the "userscript" object in the "package.json" file.
*/
if (packageJson.dependencies) {
const urlTemplate = userscript['require-template'] ?? 'https://cdn.jsdelivr.net/npm/${dependencyName}@${dependencyVersion}';
const requireTemplate = `// @require ${urlTemplate}`;
for (const dependencyName in packageJson.dependencies) {
const dependencyVersion = packageJson.dependencies[dependencyName].replace(dependencyVersionRegExp, '');
headers.push(
requireTemplate
.replace('${dependencyName}', dependencyName)
.replace('${dependencyVersion}', dependencyVersion)
// Due to the potential conflict caused by this patch, the original template replacement statement was added.
.replace('{dependencyName}', dependencyName)
.replace('{dependencyVersion}', dependencyVersion)
);
}
}
// You can also add dependencies separately in the require field of the userscript object.
if (userscript.require && userscript.require instanceof Array) {
for (const require of userscript.require) {
headers.push(`// @require ${require}`);
}
}
// Add userscript header's resources.
if (userscript.resources && userscript.resources instanceof Array) {
for (const resource of userscript.resources) {
headers.push(`// @resource ${resource}`);
}
}
// Add userscript header's resources.
// Some of the resources should contain a specified name, for which userscripts can get value from it
// eg. // @resource mycss http://link.to/some.css
// Userscripts have the ability to apply css with `GM_addStyle(GM_getResourceText('mycss'))`
if (userscript.keyedResources) {
for (const dependencyName in userscript.keyedResources) {
headers.push(`// @resource ${dependencyName} ${userscript.keyedResources[dependencyName]}`);
}
}
// Add userscript header's connects.
if (userscript.connect && userscript.connect instanceof Array) {
for (const connect of userscript.connect) {
headers.push(`// @connect ${connect}`);
}
}
// Add userscript header's run-at.
if (userscript['run-at']) {
headers.push(`// @run-at ${userscript['run-at']}`);
}
// Add userscript header's grants.
if (userscript.grant && userscript.grant instanceof Array) {
for (const grant of userscript.grant) {
headers.push(`// @grant ${grant}`);
}
}
// Add userscript header's antifeatures.
if (userscript.antifeature && userscript.antifeature instanceof Array) {
for (const antifeature of userscript.antifeature) {
headers.push(`// @antifeature ${antifeature}`);
}
}
// Add userscript header's noframes.
if (userscript.noframes) {
headers.push('// @noframes');
}
// Add userscript header's nocompat.
if (userscript.nocompat) {
headers.push(`// @nocompat ${userscript.nocompat}`);
}
// Userscript header's ending.
headers.push('// ==/UserScript==\n')
return headers.join('\n');
}