55export interface UserscriptCustomMetadata {
66 name : string ;
77 description : string ;
8- match : readonly string [ ] | string ;
8+ include : readonly string [ ] | string ;
99 exclude ?: readonly string [ ] | string ;
1010 require ?: readonly string [ ] | string ;
1111 // https://wiki.greasespot.net/Metadata_Block#.40run-at
@@ -28,17 +28,104 @@ export interface UserscriptDefaultMetadata {
2828export type UserscriptMetadata = UserscriptCustomMetadata & Partial < UserscriptDefaultMetadata > ;
2929export type AllUserscriptMetadata = UserscriptCustomMetadata & UserscriptDefaultMetadata & { version : string } ;
3030
31+ // Borrowed and adapted from kellnerd
32+ // https://github.com/kellnerd/musicbrainz-bookmarklets/blob/730ed0f96a81ef9bb239ed564f247bd68f84bee3/tools/userscriptMetadata.js#L68
33+ interface CreateURLRuleRegexOptions {
34+ query ?: 'mandatory' | 'forbidden' | 'allowed' ;
35+ fragment ?: 'mandatory' | 'forbidden' | 'allowed' ;
36+ }
37+
38+ const MB_DOMAIN_REGEX = / ( \w + \. ) ? m u s i c b r a i n z \. o r g / ;
39+
40+ function constructSuffix ( {
41+ query = 'allowed' ,
42+ fragment = 'allowed' ,
43+ } : CreateURLRuleRegexOptions ) : string {
44+ // Short circuit for shorter regexes
45+ if ( query === 'allowed' && fragment === 'allowed' ) {
46+ return '([?#]|$)' ;
47+ }
48+
49+ let suffix = '' ;
50+ if ( query === 'mandatory' ) {
51+ suffix += String . raw `\?.+?` ;
52+ } else if ( query === 'allowed' ) {
53+ suffix += String . raw `(\?.+?)?` ;
54+ }
55+
56+ if ( fragment === 'mandatory' ) {
57+ suffix += '#.+?' ;
58+ } else if ( fragment === 'allowed' ) {
59+ suffix += '(#.+?)?' ;
60+ }
61+
62+ return suffix + '$' ;
63+ }
64+
65+ export function createURLRuleRegex ( domainPattern : string | RegExp , pathRegex : string | RegExp , options : CreateURLRuleRegexOptions = { } ) : string {
66+ const domainRegex = typeof domainPattern !== 'string' ? domainPattern . source : domainPattern . replaceAll ( '.' , '\\.' ) ;
67+ pathRegex = ( typeof pathRegex === 'string' ) ? pathRegex : pathRegex . source ;
3168
32- export function transformMBMatchURL ( requireString : string ) : string {
33- return `*://*.musicbrainz.org/${ requireString } ` ;
69+ return `/^https?://${ domainRegex } /${ pathRegex } ${ constructSuffix ( options ) } /` ;
3470}
3571
72+ function removeTemplateAndCreateRule ( options : CreateURLRuleRegexOptions , pattern : string ) : string ;
73+ function removeTemplateAndCreateRule ( options : CreateURLRuleRegexOptions , template : TemplateStringsArray , ...values : string [ ] ) : string ;
74+ function removeTemplateAndCreateRule ( options : CreateURLRuleRegexOptions , pattern : string | TemplateStringsArray , ...values : string [ ] ) : string {
75+ if ( typeof pattern === 'string' ) {
76+ return createURLRuleRegex ( MB_DOMAIN_REGEX , pattern , options ) ;
77+ } else {
78+ const fullPattern = pattern . raw [ 0 ] + pattern . raw . slice ( 1 )
79+ . map ( ( lit , idx ) => `${ values [ idx ] } ${ lit } ` )
80+ . join ( '' ) ;
81+ return createURLRuleRegex ( MB_DOMAIN_REGEX , fullPattern , options ) ;
82+ }
83+ }
84+
85+ // To be used with a tagged template literal
86+ export function createMBRegex ( options : CreateURLRuleRegexOptions ) : ( pattern : string | TemplateStringsArray ) => string ;
87+ export function createMBRegex ( pattern : string ) : string ;
88+ export function createMBRegex ( pattern : TemplateStringsArray , ...values : string [ ] ) : string ;
89+ export function createMBRegex ( patternOrOptions : CreateURLRuleRegexOptions | string | TemplateStringsArray , ...values : string [ ] ) : ( ( pattern : string ) => string ) | ( ( pattern : TemplateStringsArray , ...values2 : string [ ] ) => string ) | string {
90+ // The two first branches could in practice be merged, but TypeScript struggles with that.
91+ if ( typeof patternOrOptions === 'string' ) {
92+ return removeTemplateAndCreateRule ( { } , patternOrOptions ) ;
93+ } else if ( Array . isArray ( patternOrOptions ) ) {
94+ return removeTemplateAndCreateRule ( { } , patternOrOptions as TemplateStringsArray , ...values ) ;
95+ } else {
96+ return removeTemplateAndCreateRule . bind ( undefined , patternOrOptions as CreateURLRuleRegexOptions ) ;
97+ }
98+ }
99+
100+ const mb = createMBRegex ;
101+ export const MBID_REGEX_PART = String . raw `[a-f\d-]{36}` ;
102+ const mbid = MBID_REGEX_PART ;
103+
104+ // List again -stolen- borrowed from kellnerd. Genre doesn't have an edit history.
105+ export const MB_EDITABLE_ENTITIES = [
106+ 'area' ,
107+ 'artist' ,
108+ 'event' ,
109+ 'instrument' ,
110+ 'label' ,
111+ 'place' ,
112+ 'recording' ,
113+ 'release' ,
114+ 'release-group' ,
115+ 'series' ,
116+ 'work' ,
117+ 'url' ,
118+ ] ;
119+
36120/** Any pages on which edits can occur */
37- export const MB_EDIT_PAGE_PATHS = [
38- 'edit/*' ,
39- // <entity>/<entity_id>/edits, user/<username>/edits/open, search/edits?condition.0=..., ...
40- // TODO: This also matches /search/edits, on which no edits are shown. Should somehow be excluded
41- '*/edits*' ,
42- 'user/*/votes' ,
43- '*/open_edits' ,
121+ export const MB_EDIT_DISPLAY_PAGE_PATTERNS = [
122+ mb `edit/\d+` ,
123+ // <entity>/<entity_id>/edits, <entity>/<entity_id>/open_edits
124+ mb `(${ MB_EDITABLE_ENTITIES . join ( '|' ) } )/${ mbid } /(open_)?edits` ,
125+ // user/<username>/edits/...
126+ mb `user/[^/]+/edits(/\w+)?` ,
127+ // user/<username>/votes
128+ mb `user/[^/]+/votes` ,
129+ // Edit search
130+ mb ( { query : 'mandatory' } ) `search/edits` ,
44131] ;
0 commit comments