diff --git a/src/apply/apply.go b/src/apply/apply.go index 1eff6bda08..58c40222bc 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -260,18 +260,32 @@ func getColorCSS(scheme map[string]string) string { func insertCustomApp(jsPath string, flags Flag) { utils.ModifyFile(jsPath, func(content string) string { - const REACT_REGEX = `([\w_\$][\w_\$\d]*(?:\(\))?)\.lazy\(\((?:\(\)=>|function\(\)\{return )(\w+)\.(\w+)\(\d+\)\.then\(\w+\.bind\(\w+,\d+\)\)\}?\)\)` - const REACT_ELEMENT_REGEX = `(\[\w_\$][\w_\$\d]*(?:\(\))?\.createElement|\([\w$\.,]+\))\(([\w\.]+),\{path:"\/collection"(?:,(element|children)?[:.\w,{}()$/*"]+)?\}` - reactSymbs := utils.FindSymbol( + // React lazy loading patterns for dynamic imports + reactPatterns := []string{ + // Sync pattern: X.lazy((() => Y.Z(123).then(W.bind(W, 456)))) + `([\w_\$][\w_\$\d]*(?:\(\))?)\.lazy\(\((?:\(\)=>|function\(\)\{return )(\w+)\.(\w+)\(\d+\)\.then\(\w+\.bind\(\w+,\d+\)\)\}?\)\)`, + // Async pattern (1.2.78+): m.lazy(async()=>{...await o.e(123).then(...)}) + `([\w_\$][\w_\$\d]*)\.lazy\(async\(\)=>\{(?:[^{}]|\{[^{}]*\})*await\s+(\w+)\.(\w+)\(\d+\)\.then\(\w+\.bind\(\w+,\d+\)\)`, + // Async Promise.all pattern (1.2.78+): m.lazy(async()=>await Promise.all([...]).then(...)) + `([\w_\$][\w_\$\d]*(?:\(\))?)\.lazy\(async\(\)=>await\s+Promise\.all\(\[[^\]]+\]\)\.then\((\w+)\.bind\((\w+),\d+\)\)`, + } + + // React element/route patterns for path matching + elementPatterns := []string{ + // JSX pattern (1.2.78+): (0,S.jsx)(se.qh,{path:"/collection/*",element:...}) + `(\([\w$\.,]+\))\(([\w\.]+),\{path:"/collection(?:/[\w\*]+)?",?(element|children)?`, + // createElement pattern: X.createElement(Y,{path:"/collection"...}) + `([\w_\$][\w_\$\d]*(?:\(\))?\.createElement|\([\w$\.,]+\))\(([\w\.]+),\{path:"\/collection"(?:,(element|children)?[:.\w,{}()$/*"]+)?\}`, + } + + reactSymbs, matchedReactPattern := utils.FindSymbolWithPattern( "Custom app React symbols", content, - []string{ - REACT_REGEX}) - eleSymbs := utils.FindSymbol( + reactPatterns) + eleSymbs, matchedElementPattern := utils.FindSymbolWithPattern( "Custom app React Element", content, - []string{ - REACT_ELEMENT_REGEX}) + elementPatterns) if (len(reactSymbs) < 2) || (len(eleSymbs) == 0) { utils.PrintError("Spotify version mismatch with Spicetify. Please report it on our github repository.") @@ -320,14 +334,14 @@ func insertCustomApp(jsPath string, flags Flag) { utils.ReplaceOnce( &content, - REACT_REGEX, + matchedReactPattern, func(submatches ...string) string { return fmt.Sprintf("%s%s", submatches[0], appReactMap) }) utils.ReplaceOnce( &content, - REACT_ELEMENT_REGEX, + matchedElementPattern, func(submatches ...string) string { return fmt.Sprintf("%s%s", appEleMap, submatches[0]) }) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 9dc2b74c27..433d14add1 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -242,6 +242,14 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla return fmt.Sprintf(`%s || []`, submatches[1]) }) } + + // to avoid syntaxerror on Spotify 1.2.78 and above + if spotifyMajor >= 1 && spotifyMinor >= 2 && spotifyPatch < 78 { + utils.ReplaceOnce(&content, `\(\({[^}]*,\s*imageSrc`, func(submatches ...string) string { + return fmt.Sprintf("Spicetify.Snackbar.enqueueImageSnackbar=%s", submatches[0]) + }) + } + content = additionalPatches(content) } @@ -948,9 +956,9 @@ func exposeAPIs_main(input string) string { }, { Name: "Spotify Image Snackbar Interface", - Regex: `\(\({[^}]*,\s*imageSrc`, + Regex: `(=)(\(\({[^}]*,\s*imageSrc)`, Replacement: func(submatches ...string) string { - return fmt.Sprintf("Spicetify.Snackbar.enqueueImageSnackbar=%s", submatches[0]) + return fmt.Sprintf("%sSpicetify.Snackbar.enqueueImageSnackbar=%s", submatches[1], submatches[2]) }, }, { diff --git a/src/utils/utils.go b/src/utils/utils.go index eb73b92749..cd342cfe4c 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -313,6 +313,24 @@ func FindSymbol(debugInfo, content string, clues []string) []string { return nil } +// FindSymbolWithPattern uses regexp from one or multiple clues to find variable or +// function symbol in obfuscated code. Returns the matched symbols and the pattern that matched. +func FindSymbolWithPattern(debugInfo, content string, clues []string) ([]string, string) { + for _, v := range clues { + re := regexp.MustCompile(v) + found := re.FindStringSubmatch(content) + if found != nil { + return found[1:], v + } + } + + if len(debugInfo) > 0 { + PrintError("Cannot find symbol for " + debugInfo) + } + + return nil, "" +} + // CreateJunction creates a junction in Windows or a symlink in Linux/Mac. func CreateJunction(location, destination string) error { CheckExistAndDelete(destination)