Skip to content

Commit 59cbdba

Browse files
committed
fix: add support for Spotify 1.2.78+ async lazy loading patterns
- Add new regex patterns for async React lazy loading in apply.go - Add FindSymbolWithPattern helper to return matched pattern for reuse - Fix Image Snackbar preprocessing to only match in assignment context - Add defer attribute to Spicetify.Config script block
1 parent 04155e7 commit 59cbdba

File tree

3 files changed

+46
-13
lines changed

3 files changed

+46
-13
lines changed

src/apply/apply.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func htmlMod(htmlPath string, flags Flag) {
158158
customAppList += fmt.Sprintf(`"%s",`, app)
159159
}
160160

161-
helperHTML += fmt.Sprintf(`<script>
161+
helperHTML += fmt.Sprintf(`<script defer>
162162
Spicetify.Config={};
163163
Spicetify.Config["version"]="%s";
164164
Spicetify.Config["current_theme"]="%s";
@@ -260,18 +260,32 @@ func getColorCSS(scheme map[string]string) string {
260260

261261
func insertCustomApp(jsPath string, flags Flag) {
262262
utils.ModifyFile(jsPath, func(content string) string {
263-
const REACT_REGEX = `([\w_\$][\w_\$\d]*(?:\(\))?)\.lazy\(\((?:\(\)=>|function\(\)\{return )(\w+)\.(\w+)\(\d+\)\.then\(\w+\.bind\(\w+,\d+\)\)\}?\)\)`
264-
const REACT_ELEMENT_REGEX = `(\[\w_\$][\w_\$\d]*(?:\(\))?\.createElement|\([\w$\.,]+\))\(([\w\.]+),\{path:"\/collection"(?:,(element|children)?[:.\w,{}()$/*"]+)?\}`
265-
reactSymbs := utils.FindSymbol(
263+
// React lazy loading patterns for dynamic imports
264+
reactPatterns := []string{
265+
// Sync pattern: X.lazy((() => Y.Z(123).then(W.bind(W, 456))))
266+
`([\w_\$][\w_\$\d]*(?:\(\))?)\.lazy\(\((?:\(\)=>|function\(\)\{return )(\w+)\.(\w+)\(\d+\)\.then\(\w+\.bind\(\w+,\d+\)\)\}?\)\)`,
267+
// Async pattern (1.2.78+): m.lazy(async()=>{...await o.e(123).then(...)})
268+
`([\w_\$][\w_\$\d]*)\.lazy\(async\(\)=>\{(?:[^{}]|\{[^{}]*\})*await\s+(\w+)\.(\w+)\(\d+\)\.then\(\w+\.bind\(\w+,\d+\)\)`,
269+
// Async Promise.all pattern (1.2.78+): m.lazy(async()=>await Promise.all([...]).then(...))
270+
`([\w_\$][\w_\$\d]*(?:\(\))?)\.lazy\(async\(\)=>await\s+Promise\.all\(\[[^\]]+\]\)\.then\((\w+)\.bind\((\w+),\d+\)\)`,
271+
}
272+
273+
// React element/route patterns for path matching
274+
elementPatterns := []string{
275+
// JSX pattern (1.2.78+): (0,S.jsx)(se.qh,{path:"/collection/*",element:...})
276+
`(\([\w$\.,]+\))\(([\w\.]+),\{path:"/collection(?:/[\w\*]+)?",?(element|children)?`,
277+
// createElement pattern: X.createElement(Y,{path:"/collection"...})
278+
`(\[\w_\$][\w_\$\d]*(?:\(\))?\.createElement|\([\w$\.,]+\))\(([\w\.]+),\{path:"\/collection"(?:,(element|children)?[:.\w,{}()$/*"]+)?\}`,
279+
}
280+
281+
reactSymbs, matchedReactPattern := utils.FindSymbolWithPattern(
266282
"Custom app React symbols",
267283
content,
268-
[]string{
269-
REACT_REGEX})
270-
eleSymbs := utils.FindSymbol(
284+
reactPatterns)
285+
eleSymbs, matchedElementPattern := utils.FindSymbolWithPattern(
271286
"Custom app React Element",
272287
content,
273-
[]string{
274-
REACT_ELEMENT_REGEX})
288+
elementPatterns)
275289

276290
if (len(reactSymbs) < 2) || (len(eleSymbs) == 0) {
277291
utils.PrintError("Spotify version mismatch with Spicetify. Please report it on our github repository.")
@@ -320,14 +334,14 @@ func insertCustomApp(jsPath string, flags Flag) {
320334

321335
utils.ReplaceOnce(
322336
&content,
323-
REACT_REGEX,
337+
matchedReactPattern,
324338
func(submatches ...string) string {
325339
return fmt.Sprintf("%s%s", submatches[0], appReactMap)
326340
})
327341

328342
utils.ReplaceOnce(
329343
&content,
330-
REACT_ELEMENT_REGEX,
344+
matchedElementPattern,
331345
func(submatches ...string) string {
332346
return fmt.Sprintf("%s%s", appEleMap, submatches[0])
333347
})

src/preprocess/preprocess.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -948,10 +948,11 @@ func exposeAPIs_main(input string) string {
948948
},
949949
{
950950
Name: "Spotify Image Snackbar Interface",
951-
Regex: `\(\({[^}]*,\s*imageSrc`,
951+
Regex: `(=)(\(\({[^}]*,\s*imageSrc)`,
952952
Replacement: func(submatches ...string) string {
953-
return fmt.Sprintf("Spicetify.Snackbar.enqueueImageSnackbar=%s", submatches[0])
953+
return fmt.Sprintf("%sSpicetify.Snackbar.enqueueImageSnackbar=%s", submatches[1], submatches[2])
954954
},
955+
Once: true,
955956
},
956957
{
957958
Name: "React Component: Navigation for navLinks",

src/utils/utils.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,24 @@ func FindSymbol(debugInfo, content string, clues []string) []string {
313313
return nil
314314
}
315315

316+
// FindSymbolWithPattern uses regexp from one or multiple clues to find variable or
317+
// function symbol in obfuscated code. Returns the matched symbols and the pattern that matched.
318+
func FindSymbolWithPattern(debugInfo, content string, clues []string) ([]string, string) {
319+
for _, v := range clues {
320+
re := regexp.MustCompile(v)
321+
found := re.FindStringSubmatch(content)
322+
if found != nil {
323+
return found[1:], v
324+
}
325+
}
326+
327+
if len(debugInfo) > 0 {
328+
PrintError("Cannot find symbol for " + debugInfo)
329+
}
330+
331+
return nil, ""
332+
}
333+
316334
// CreateJunction creates a junction in Windows or a symlink in Linux/Mac.
317335
func CreateJunction(location, destination string) error {
318336
CheckExistAndDelete(destination)

0 commit comments

Comments
 (0)