Skip to content

Commit 7e34ed4

Browse files
committed
parseMetadata 代码优化
1 parent fabd2e9 commit 7e34ed4

File tree

2 files changed

+261
-36
lines changed

2 files changed

+261
-36
lines changed

src/pkg/utils/script.test.ts

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ console.log('Hello World');
107107

108108
const result = parseMetadata(code);
109109
expect(result).not.toBeNull();
110+
expect(result?.name).toEqual(["测试脚本"]);
111+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
110112
expect(result?.description).toEqual([""]);
111113
expect(result?.author).toEqual([""]);
112114
});
@@ -133,6 +135,8 @@ console.log('Hello World');
133135

134136
const result = parseMetadata(code);
135137
expect(result).not.toBeNull();
138+
expect(result?.name).toEqual(["测试脚本"]);
139+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
136140
expect(result?.match).toEqual([
137141
"https://example.org/*",
138142
"https://test.com/*",
@@ -171,6 +175,8 @@ console.log('Hello World');
171175

172176
const result = parseMetadata(code);
173177
expect(result).not.toBeNull();
178+
expect(result?.name).toEqual(["测试脚本"]);
179+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
174180
expect(result?.match).toEqual([
175181
"https://example.org/*",
176182
"https://test.com/*",
@@ -215,6 +221,8 @@ console.log('Hello World');
215221

216222
const result = parseMetadata(code);
217223
expect(result).not.toBeNull();
224+
expect(result?.name).toEqual(["测试脚本"]);
225+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
218226
expect(result?.match).toEqual([
219227
"https://example.org/*",
220228
"https://test.com/*",
@@ -259,17 +267,244 @@ console.log('Hello World');
259267

260268
const result = parseMetadata(code);
261269
expect(result).not.toBeNull();
270+
expect(result?.name).toEqual(["测试脚本"]);
271+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
272+
expect(result?.match).toEqual([
273+
"https://example.org/*",
274+
"https://test.com/*",
275+
"https://demo.com/*",
276+
"https://example.com/*",
277+
]);
278+
expect(result?.grant).toEqual(["GM_setValue", "GM_getValue"]);
279+
expect(result?.description).toEqual([""]);
280+
expect(result?.author).toEqual([""]);
281+
});
282+
283+
it.concurrent("正確解析元数据(換行空白1)", () => {
284+
const code = `
285+
// ==UserScript==
286+
// @name 测试脚本
287+
// @namespace http://tampermonkey.net/
288+
// @match https://example.org/*
289+
// @match https://test.com/*
290+
// @match https://demo.com/*
291+
// @version 1.0.0
292+
// @description
293+
// @early-start
294+
// @author
295+
// @match https://example.com/*
296+
// @grant
297+
GM_setValue
298+
// @grant GM_getValue
299+
// ==/UserScript==
300+
console.log('Hello World');
301+
`;
302+
303+
const result = parseMetadata(code);
304+
expect(result).not.toBeNull();
305+
expect(result?.name).toEqual(["测试脚本"]);
306+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
262307
expect(result?.match).toEqual([
263308
"https://example.org/*",
264309
"https://test.com/*",
265310
"https://demo.com/*",
266311
"https://example.com/*",
267312
]);
313+
expect(result?.["early-start"]).toEqual([""]);
314+
expect(result?.grant).toEqual(["", "GM_getValue"]);
315+
expect(result?.description).toEqual([""]);
316+
expect(result?.author).toEqual([""]);
317+
});
318+
319+
it.concurrent("正確解析元数据(換行空白2)", () => {
320+
const code = `
321+
// ==UserScript==
322+
// @name 测试脚本
323+
// @namespace http://tampermonkey.net/
324+
// @match https://example.org/*
325+
// @match https://test.com/*
326+
//
327+
@match https://demo.com/*
328+
// @version 1.0.0
329+
// @description
330+
// @early-start
331+
// @author
332+
// @match https://example.com/*
333+
// @grant GM_setValue
334+
// @grant GM_getValue
335+
// ==/UserScript==
336+
console.log('Hello World');
337+
`;
338+
339+
const result = parseMetadata(code);
340+
expect(result).not.toBeNull();
341+
expect(result?.name).toEqual(["测试脚本"]);
342+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
343+
expect(result?.match).toEqual(["https://example.org/*", "https://test.com/*", "https://example.com/*"]);
344+
expect(result?.["early-start"]).toEqual([""]);
345+
expect(result?.grant).toEqual(["GM_setValue", "GM_getValue"]);
346+
expect(result?.description).toEqual([""]);
347+
expect(result?.author).toEqual([""]);
348+
});
349+
350+
it.concurrent("正確解析元数据(換行空白3)", () => {
351+
const code = `
352+
// ==UserScript==
353+
// @name 测试脚本
354+
// @namespace http://tampermonkey.net/
355+
// @match https://example.org/*
356+
// match https://test.com/*
357+
// match https://demo.com/*
358+
// @version 1.0.0
359+
// @description
360+
//
361+
@early-start
362+
// @author
363+
// @match https://example.com/*
364+
// @grant GM_setValue
365+
// @grant GM_getValue
366+
// ==/UserScript==
367+
console.log('Hello World');
368+
`;
369+
370+
const result = parseMetadata(code);
371+
expect(result).not.toBeNull();
372+
expect(result?.name).toEqual(["测试脚本"]);
373+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
374+
expect(result?.match).toEqual(["https://example.org/*", "https://example.com/*"]);
375+
expect(result?.["early-start"]).toEqual(undefined);
268376
expect(result?.grant).toEqual(["GM_setValue", "GM_getValue"]);
269377
expect(result?.description).toEqual([""]);
270378
expect(result?.author).toEqual([""]);
271379
});
272380

381+
it.concurrent("忽略非元数据的注釋", () => {
382+
const code = `
383+
/*
384+
Copyright <YEAR> <COPYRIGHT HOLDER>
385+
386+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
387+
388+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
389+
390+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
391+
*/
392+
// The above is The MIT License.
393+
// ==UserScript==
394+
// Ignore me please
395+
// @name 测试脚本
396+
// I am a comment
397+
// @namespace http://tampermonkey.net/
398+
// @match https://example.org/*
399+
// match https://test.com/*
400+
// match https://demo.com/*
401+
// @version 1.0.0
402+
// -------------------------------------------------
403+
// @description This is Description
404+
// -------------------------------------------------
405+
// 不要使用 @early-start
406+
// @author
407+
// @match https://example.com/*
408+
//
409+
// @grant GM_setValue
410+
// @grant GM_getValue
411+
//
412+
// This is just a comment.
413+
// ==/UserScript==
414+
console.log('Hello World');
415+
`;
416+
417+
const result = parseMetadata(code);
418+
expect(result).not.toBeNull();
419+
expect(result?.name).toEqual(["测试脚本"]);
420+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
421+
expect(result?.match).toEqual(["https://example.org/*", "https://example.com/*"]);
422+
expect(result?.["early-start"]).toEqual(undefined);
423+
expect(result?.grant).toEqual(["GM_setValue", "GM_getValue"]);
424+
expect(result?.description).toEqual(["This is Description"]);
425+
expect(result?.author).toEqual([""]);
426+
});
427+
428+
it.concurrent("兼容TM: 可不包含空白開首(1)", () => {
429+
const code = `
430+
//==UserScript==
431+
//@name 测试脚本
432+
//@namespace http://tampermonkey.net/
433+
// @match https://example.org/*
434+
// @match https://test.com/*
435+
// @match https://demo.com/*
436+
// @version 1.0.0
437+
// -------------------------------------------------
438+
// @description This is Description
439+
// -------------------------------------------------
440+
// 不要使用 @early-start
441+
// @author
442+
// @match https://example.com/*
443+
//
444+
// @grant GM_setValue
445+
// @grant GM_getValue
446+
//
447+
// This is just a comment.
448+
// ==/UserScript==
449+
console.log('Hello World');
450+
`;
451+
452+
const result = parseMetadata(code);
453+
expect(result).not.toBeNull();
454+
expect(result?.name).toEqual(["测试脚本"]);
455+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
456+
expect(result?.match).toEqual([
457+
"https://example.org/*",
458+
"https://test.com/*",
459+
"https://demo.com/*",
460+
"https://example.com/*",
461+
]);
462+
expect(result?.["early-start"]).toEqual(undefined);
463+
expect(result?.grant).toEqual(["GM_setValue", "GM_getValue"]);
464+
expect(result?.description).toEqual(["This is Description"]);
465+
expect(result?.author).toEqual([""]);
466+
});
467+
468+
it.concurrent("兼容TM: 可不包含空白開首(2)", () => {
469+
const code = `
470+
// ==UserScript==
471+
// @name 测试脚本
472+
// @namespace http://tampermonkey.net/
473+
// @match https://example.org/*
474+
// @match https://test.com/*
475+
// @match https://demo.com/*
476+
// @version 1.0.0
477+
// -------------------------------------------------
478+
// @description This is Description
479+
// -------------------------------------------------
480+
// 不要使用 @early-start
481+
// @author
482+
// @match https://example.com/*
483+
//
484+
//@grant GM_setValue
485+
// @grant GM_getValue
486+
//
487+
//This is just a comment.
488+
//==/UserScript==
489+
console.log('Hello World');
490+
`;
491+
492+
const result = parseMetadata(code);
493+
expect(result).not.toBeNull();
494+
expect(result?.name).toEqual(["测试脚本"]);
495+
expect(result?.namespace).toEqual(["http://tampermonkey.net/"]);
496+
expect(result?.match).toEqual([
497+
"https://example.org/*",
498+
"https://test.com/*",
499+
"https://demo.com/*",
500+
"https://example.com/*",
501+
]);
502+
expect(result?.["early-start"]).toEqual(undefined);
503+
expect(result?.grant).toEqual(["GM_setValue", "GM_getValue"]);
504+
expect(result?.description).toEqual(["This is Description"]);
505+
expect(result?.author).toEqual([""]);
506+
});
507+
273508
it.concurrent("缺少name字段应返回null", () => {
274509
const code = `
275510
// ==UserScript==

src/pkg/utils/script.ts

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,32 @@ import { nextTime } from "./cron";
1616
import { parseUserConfig } from "./yaml";
1717
import { t as i18n_t } from "@App/locales/locales";
1818

19+
const HEADER_BLOCK = /\/\/[ \t]*==User(Script|Subscribe)==([\s\S]+?)\/\/[ \t]*==\/User\1==/m;
20+
const META_LINE = /\/\/[ \t]*@(\S+)[ \t]*(.*)$/gm;
21+
1922
// 从脚本代码抽出Metadata
2023
export function parseMetadata(code: string): SCMetadata | null {
21-
let issub = false;
22-
let regex = /\/\/\s*==UserScript==([\s\S]+?)\/\/\s*==\/UserScript==/m;
23-
let header = regex.exec(code);
24-
if (!header) {
25-
regex = /\/\/\s*==UserSubscribe==([\s\S]+?)\/\/\s*==\/UserSubscribe==/m;
26-
header = regex.exec(code);
27-
if (!header) {
28-
return null;
29-
}
30-
issub = true;
31-
}
32-
regex = /\/\/\s*@(\S+)((.+?)$|$)/gm;
33-
const ret = {} as SCMetadata;
34-
let meta: RegExpExecArray | null = regex.exec(header[1]);
35-
while (meta !== null) {
36-
const [key, val] = [meta[1].toLowerCase().trim(), meta[2].trim()];
37-
let values = ret[key];
38-
if (!values) {
39-
values = [];
40-
}
41-
values.push(val);
42-
ret[key] = values;
43-
meta = regex.exec(header[1]);
44-
}
45-
if (ret.name === undefined) {
46-
return null;
47-
}
48-
if (Object.keys(ret).length < 3) {
24+
let isSubscribe = false;
25+
let headerContent: string;
26+
let m: RegExpExecArray | null;
27+
if ((m = HEADER_BLOCK.exec(code))) {
28+
isSubscribe = m[1] === "Subscribe";
29+
headerContent = m[2];
30+
} else {
4931
return null;
5032
}
51-
if (!ret.namespace) {
52-
ret.namespace = [""];
53-
}
54-
if (issub) {
55-
ret.usersubscribe = [];
33+
const metadata: SCMetadata = {} as SCMetadata;
34+
META_LINE.lastIndex = 0; // 重置正则表达式的lastIndex(用于复用)
35+
while ((m = META_LINE.exec(headerContent)) !== null) {
36+
const key = m[1].toLowerCase();
37+
const val = m[2]?.trim() ?? "";
38+
const values = metadata[key] || (metadata[key] = []);
39+
values.push(val);
5640
}
57-
return ret;
41+
if (!metadata.name || Object.keys(metadata).length < 3) return null;
42+
if (!metadata.namespace) metadata.namespace = [""];
43+
if (isSubscribe) metadata.usersubscribe = [];
44+
return metadata;
5845
}
5946

6047
// 从网址取得脚本代码
@@ -91,12 +78,15 @@ export async function prepareScriptByCode(
9178
if (!metadata) {
9279
throw new Error(i18n_t("error_metadata_invalid"));
9380
}
94-
if (metadata.name === undefined) {
81+
// 不接受空白name
82+
if (!metadata.name?.[0]) {
9583
throw new Error(i18n_t("error_script_name_required"));
9684
}
97-
if (metadata.version === undefined) {
85+
// 不接受空白version
86+
if (!metadata.version?.[0]) {
9887
throw new Error(i18n_t("error_script_version_required"));
9988
}
89+
// 可接受空白namespace
10090
if (metadata.namespace === undefined) {
10191
throw new Error(i18n_t("error_script_namespace_required"));
10292
}

0 commit comments

Comments
 (0)