Skip to content

Commit

Permalink
fix: switch to @include rules because @match is too limited
Browse files Browse the repository at this point in the history
  • Loading branch information
ROpdebee committed Jun 19, 2022
1 parent c08fe6c commit 5cfe324
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 97 deletions.
2 changes: 1 addition & 1 deletion build/plugin-userscript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const DEFAULT_OPTIONS = {
metadataOrder: [
'name', 'description', 'version', 'author', 'license', 'namespace',
'homepageURL', 'supportURL', 'downloadURL', 'updateURL',
'match', 'exclude', 'require', 'resource', 'run-at', 'grant', 'connect',
'include', 'exclude', 'require', 'resource', 'run-at', 'grant', 'connect',
],
};

Expand Down
107 changes: 97 additions & 10 deletions src/lib/util/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
export interface UserscriptCustomMetadata {
name: string;
description: string;
match: readonly string[] | string;
include: readonly string[] | string;
exclude?: readonly string[] | string;
require?: readonly string[] | string;
// https://wiki.greasespot.net/Metadata_Block#.40run-at
Expand All @@ -28,17 +28,104 @@ export interface UserscriptDefaultMetadata {
export type UserscriptMetadata = UserscriptCustomMetadata & Partial<UserscriptDefaultMetadata>;
export type AllUserscriptMetadata = UserscriptCustomMetadata & UserscriptDefaultMetadata & { version: string };

// Borrowed and adapted from kellnerd
// https://github.com/kellnerd/musicbrainz-bookmarklets/blob/730ed0f96a81ef9bb239ed564f247bd68f84bee3/tools/userscriptMetadata.js#L68
interface CreateURLRuleRegexOptions {
query?: 'mandatory' | 'forbidden' | 'allowed';
fragment?: 'mandatory' | 'forbidden' | 'allowed';
}

const MB_DOMAIN_REGEX = /(\w+\.)?musicbrainz\.org/;

function constructSuffix({
query = 'allowed',
fragment = 'allowed',
}: CreateURLRuleRegexOptions): string {
// Short circuit for shorter regexes
if (query === 'allowed' && fragment === 'allowed') {
return '([?#]|$)';
}

let suffix = '';
if (query === 'mandatory') {
suffix += String.raw`\?.+?`;
} else if (query === 'allowed') {
suffix += String.raw`(\?.+?)?`;
}

if (fragment === 'mandatory') {
suffix += '#.+?';
} else if (fragment === 'allowed') {
suffix += '(#.+?)?';
}

return suffix + '$';
}

export function createURLRuleRegex(domainPattern: string | RegExp, pathRegex: string | RegExp, options: CreateURLRuleRegexOptions = {}): string {
const domainRegex = typeof domainPattern !== 'string' ? domainPattern.source : domainPattern.replaceAll('.', '\\.');
pathRegex = (typeof pathRegex === 'string') ? pathRegex : pathRegex.source;

export function transformMBMatchURL(requireString: string): string {
return `*://*.musicbrainz.org/${requireString}`;
return `/^https?://${domainRegex}/${pathRegex}${constructSuffix(options)}/`;
}

function removeTemplateAndCreateRule(options: CreateURLRuleRegexOptions, pattern: string): string;
function removeTemplateAndCreateRule(options: CreateURLRuleRegexOptions, template: TemplateStringsArray, ...values: string[]): string;
function removeTemplateAndCreateRule(options: CreateURLRuleRegexOptions, pattern: string | TemplateStringsArray, ...values: string[]): string {
if (typeof pattern === 'string') {
return createURLRuleRegex(MB_DOMAIN_REGEX, pattern, options);
} else {
const fullPattern = pattern.raw[0] + pattern.raw.slice(1)
.map((lit, idx) => `${values[idx]}${lit}`)
.join('');
return createURLRuleRegex(MB_DOMAIN_REGEX, fullPattern, options);
}
}

// To be used with a tagged template literal
export function createMBRegex(options: CreateURLRuleRegexOptions): (pattern: string | TemplateStringsArray) => string;
export function createMBRegex(pattern: string): string;
export function createMBRegex(pattern: TemplateStringsArray, ...values: string[]): string;
export function createMBRegex(patternOrOptions: CreateURLRuleRegexOptions | string | TemplateStringsArray, ...values: string[]): ((pattern: string) => string) | ((pattern: TemplateStringsArray, ...values2: string[]) => string) | string {
// The two first branches could in practice be merged, but TypeScript struggles with that.
if (typeof patternOrOptions === 'string') {
return removeTemplateAndCreateRule({}, patternOrOptions);
} else if (Array.isArray(patternOrOptions)) {
return removeTemplateAndCreateRule({}, patternOrOptions as TemplateStringsArray, ...values);
} else {
return removeTemplateAndCreateRule.bind(undefined, patternOrOptions as CreateURLRuleRegexOptions);
}
}

const mb = createMBRegex;
export const MBID_REGEX_PART = String.raw`[a-f\d-]{36}`;
const mbid = MBID_REGEX_PART;

// List again -stolen- borrowed from kellnerd. Genre doesn't have an edit history.
export const MB_EDITABLE_ENTITIES = [
'area',
'artist',
'event',
'instrument',
'label',
'place',
'recording',
'release',
'release-group',
'series',
'work',
'url',
];

/** Any pages on which edits can occur */
export const MB_EDIT_PAGE_PATHS = [
'edit/*',
// <entity>/<entity_id>/edits, user/<username>/edits/open, search/edits?condition.0=..., ...
// TODO: This also matches /search/edits, on which no edits are shown. Should somehow be excluded
'*/edits*',
'user/*/votes',
'*/open_edits',
export const MB_EDIT_DISPLAY_PAGE_PATTERNS = [
mb`edit/\d+`,
// <entity>/<entity_id>/edits, <entity>/<entity_id>/open_edits
mb`(${MB_EDITABLE_ENTITIES.join('|')})/${mbid}/(open_)?edits`,
// user/<username>/edits/...
mb`user/[^/]+/edits(/\w+)?`,
// user/<username>/votes
mb`user/[^/]+/votes`,
// Edit search
mb({ query: 'mandatory' })`search/edits`,
];
4 changes: 2 additions & 2 deletions src/mb_blind_votes/meta.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { UserscriptMetadata } from '@lib/util/metadata';
import { MB_EDIT_PAGE_PATHS, transformMBMatchURL } from '@lib/util/metadata';
import { MB_EDIT_DISPLAY_PAGE_PATTERNS } from '@lib/util/metadata';

const metadata: UserscriptMetadata = {
name: 'MB: Blind Votes',
description: 'Blinds editor details before your votes are cast.',
// FIXME: This should run at document-start to ensure that editor details
// don't flash onto the screen while the page is still loading.
'run-at': 'document-end',
match: MB_EDIT_PAGE_PATHS.map((path) => transformMBMatchURL(path)),
include: MB_EDIT_DISPLAY_PAGE_PATTERNS,
};

export default metadata;
16 changes: 7 additions & 9 deletions src/mb_caa_dimensions/meta.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import type { UserscriptMetadata } from '@lib/util/metadata';
import { MB_EDIT_PAGE_PATHS, transformMBMatchURL } from '@lib/util/metadata';
import { createMBRegex as mb, MB_EDIT_DISPLAY_PAGE_PATTERNS, MBID_REGEX_PART as mbid } from '@lib/util/metadata';

const metadata: UserscriptMetadata = {
name: 'MB: Display CAA image dimensions',
description: 'Displays the dimensions and size of images in the cover art archive.',
'run-at': 'document-start',
match: [
'release/*',
'release-group/*',
...MB_EDIT_PAGE_PATHS,
].map((path) => transformMBMatchURL(path)),
include: [
...MB_EDIT_DISPLAY_PAGE_PATTERNS,
mb`release(-group)?/${mbid}(/.+?)?`,
],
exclude: [
transformMBMatchURL('release/*/edit'),
transformMBMatchURL('release/*/edit-relationships'),
transformMBMatchURL('release-group/*/edit'),
mb`release(-group)?/${mbid}/edit`,
mb`release-group/${mbid}/edit-relationships`,
],
};

Expand Down
20 changes: 7 additions & 13 deletions src/mb_enhanced_cover_art_uploads/meta.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import type { UserscriptMetadata } from '@lib/util/metadata';
import { transformMBMatchURL } from '@lib/util/metadata';

const mbMatchedUrls = [
'release/*/add-cover-art',
'release/*/add-cover-art?*',
'release/*/cover-art',
].map((url) => transformMBMatchURL(url));
import { createMBRegex as mb, createURLRuleRegex, MBID_REGEX_PART as mbid } from '@lib/util/metadata';

const metadata: UserscriptMetadata = {
name: 'MB: Enhanced Cover Art Uploads',
description: 'Enhance the cover art uploader! Upload directly from a URL, automatically import covers from Discogs/Spotify/Apple Music/..., automatically retrieve the largest version, and more!',
'run-at': 'document-end',
match: [
...mbMatchedUrls,
'*://atisket.pulsewidth.org.uk/*',
'*://etc.marlonob.info/atisket/*',
'*://vgmdb.net/album/*',
include: [
mb`release/${mbid}/add-cover-art`,
mb`release/${mbid}/cover-art`,
createURLRuleRegex('atisket.pulsewidth.org.uk/', /.*?/, { query: 'mandatory' }),
createURLRuleRegex('etc.marlonob.info', /atisket\/.*?/, { query: 'mandatory' }),
createURLRuleRegex('vgmdb.net', /album\/\d+/),
],
exclude: ['*://atisket.pulsewidth.org.uk/'],
grant: [
'GM.xmlhttpRequest',
// Used for some favicons
Expand Down
15 changes: 5 additions & 10 deletions src/mb_multi_external_links/meta.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import type { UserscriptMetadata } from '@lib/util/metadata';
import { transformMBMatchURL } from '@lib/util/metadata';
import { createMBRegex as mb, MB_EDITABLE_ENTITIES, MBID_REGEX_PART as mbid } from '@lib/util/metadata';

const metadata: UserscriptMetadata = {
name: 'MB: QoL: Paste multiple external links at once',
description: 'Enables pasting multiple links, separated by whitespace, into the external link editor.',
'run-at': 'document-end',
match: [
'*/edit',
'*/edit?*',
'release/*/edit-relationships*',
'*/add',
'*/add?*',
'*/create',
'*/create?*',
].map((path) => transformMBMatchURL(path)),
include: [
mb`(${MB_EDITABLE_ENTITIES.join('|')})/(add|create|${mbid}/edit)`,
mb`release/${mbid}/edit-relationships`,
],
};

export default metadata;
8 changes: 2 additions & 6 deletions src/mb_qol_inline_recording_tracks/meta.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import type { UserscriptMetadata } from '@lib/util/metadata';
import { transformMBMatchURL } from '@lib/util/metadata';
import { createMBRegex as mb, MBID_REGEX_PART as mbid } from '@lib/util/metadata';

const metadata: UserscriptMetadata = {
// FIXME: This name isn't very descriptive.
name: 'MB: QoL: Inline all recordings\' tracks on releases',
description: 'Display all tracks and releases on which a recording appears from the release page.',
'run-at': 'document-end',
match: transformMBMatchURL('release/*'),
exclude: [
transformMBMatchURL('release/add'),
transformMBMatchURL('release/*/edit*'),
],
include: mb`release/${mbid}`,
};

export default metadata;
8 changes: 2 additions & 6 deletions src/mb_qol_seed_recording_disambiguation/meta.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import type { UserscriptMetadata } from '@lib/util/metadata';
import { transformMBMatchURL } from '@lib/util/metadata';
import { createMBRegex as mb, MBID_REGEX_PART as mbid } from '@lib/util/metadata';

const metadata: UserscriptMetadata = {
name: 'MB: QoL: Seed the batch recording comments script',
description: 'Seed the recording comments for the batch recording comments userscripts with live and DJ-mix data.',
'run-at': 'document-end',
match: transformMBMatchURL('release/*'),
exclude: [
transformMBMatchURL('release/add'),
transformMBMatchURL('release/*/edit*'),
],
include: mb`release/${mbid}`,
};

export default metadata;
8 changes: 2 additions & 6 deletions src/mb_qol_select_all_update_recordings/meta.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import type { UserscriptMetadata } from '@lib/util/metadata';
import { transformMBMatchURL } from '@lib/util/metadata';
import { createMBRegex as mb, MBID_REGEX_PART as mbid } from '@lib/util/metadata';

const metadata: UserscriptMetadata = {
name: 'MB: QoL: Select All Update Recordings',
description: 'Add buttons to release editor to select all "Update recordings" checkboxes.',
'run-at': 'document-idle',
match: [
'release/*/edit',
'release/*/edit?*',
'release/add*',
].map((path) => transformMBMatchURL(path)),
include: mb`release/(add|${mbid}/edit)`,
};

export default metadata;
6 changes: 2 additions & 4 deletions src/mb_supercharged_caa_edits/meta.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import type { UserscriptMetadata } from '@lib/util/metadata';
import { MB_EDIT_PAGE_PATHS, transformMBMatchURL } from '@lib/util/metadata';
import { MB_EDIT_DISPLAY_PAGE_PATTERNS } from '@lib/util/metadata';

const metadata: UserscriptMetadata = {
name: 'MB: Supercharged Cover Art Edits',
description: 'Supercharges reviewing cover art edits. Displays release information on CAA edits. Enables image comparisons on removed and added images.',
'run-at': 'document-end',
match: [
...MB_EDIT_PAGE_PATHS,
].map((path) => transformMBMatchURL(path)),
include: MB_EDIT_DISPLAY_PAGE_PATTERNS,
require: [
'https://cdn.jsdelivr.net/npm/resemblejs@4.1.0/resemble.min.js',
'https://cdn.jsdelivr.net/npm/moment@2.29.3/min/moment-with-locales.min.js',
Expand Down
50 changes: 24 additions & 26 deletions src/mb_work_code_toolbox/meta.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
import type { UserscriptMetadata } from '@lib/util/metadata';
import { MB_EDIT_PAGE_PATHS, transformMBMatchURL } from '@lib/util/metadata';

const mbPagesWithWorkCodeInput = [
'*/edit',
'*/edit?*',
'release/*/edit-relationships',
'release/*/edit-relationships?*',
'*/create',
'*/create?*',
].map((path) => transformMBMatchURL(path));

const mbPagesWithWorkCodesDisplayed = [
// Any edit page can display work codes
...MB_EDIT_PAGE_PATHS,
// Works themselves, of course
'work/*',
// Collections of works
'collection/*',
// Table of works to which artist is related.
'artist/*/works*',
].map((path) => transformMBMatchURL(path));
import { createMBRegex as mb, createURLRuleRegex, MB_EDIT_DISPLAY_PAGE_PATTERNS, MB_EDITABLE_ENTITIES, MBID_REGEX_PART as mbid } from '@lib/util/metadata';

const metadata: UserscriptMetadata = {
name: 'MB: Work code toolbox',
description: 'Copy work identifiers from various online repertoires and paste them into MB works with ease. Validate work code formatting: Highlight invalid or ill-formatted codes.',
'run-at': 'document-end',
match: [
...mbPagesWithWorkCodeInput,
...mbPagesWithWorkCodesDisplayed,
'https://online.gema.de/werke/search.faces*',
'https://iswcnet.cisac.org/*',
include: [
// Work code input
// Edit and creation pages, either works themselves or another entity linked to works (via embedded iframe)
mb`(${MB_EDITABLE_ENTITIES.join('|')})/(create|${mbid}/edit)`,
// Relationship editor via embedded iframe
mb`release/${mbid}/edit-relationships`,

// Work code display, for validation
// Any page that displays edits
...MB_EDIT_DISPLAY_PAGE_PATTERNS,
// Any work tab (overlaps with a previous match though)
mb`work/${mbid}/.+`,
// Collections of works.
mb`collection/${mbid}`,
// Table of works to which author is related.
mb`artist/${mbid}/works`,

// Repertoires
createURLRuleRegex('online.gema.de', /werke\/search\.faces/),
// We need to match the index page of ISWCNet too, it's a single-page
// application and the userscript may not get loaded after solving the
// captcha.
createURLRuleRegex('iswcnet.cisac.org', /.*/),
],
grant: [
'GM.getValue',
Expand Down
16 changes: 12 additions & 4 deletions tests/unit/lib/util/metadata.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { transformMBMatchURL } from '@lib/util/metadata';
import { createMBRegex as mb } from '@lib/util/metadata';

describe('transforming match URLs', () => {
describe('creating MB regexes', () => {
it('should prefix MB domain', () => {
expect(transformMBMatchURL('release/add'))
.toBe('*://*.musicbrainz.org/release/add');
// Need to remove surrounding slashes
const pattern = new RegExp(mb`release/add`.slice(1, -1));

expect(pattern.test('https://musicbrainz.org/release/add')).toBeTrue();
expect(pattern.test('https://beta.musicbrainz.org/release/add')).toBeTrue();
expect(pattern.test('https://musicbrainz.org/release/add?x=123')).toBeTrue();
expect(pattern.test('https://musicbrainz.org/release/add?x=123#fragment')).toBeTrue();
expect(pattern.test('https://musicbrainz.org/release/add#fragment')).toBeTrue();
expect(pattern.test('https://musicbrainz.org/release/add/123')).toBeFalse();
expect(pattern.test('https://musicbrainz.org/release/adda')).toBeFalse();
});
});

0 comments on commit 5cfe324

Please sign in to comment.