Skip to content

Commit 9af100a

Browse files
committed
fix: do some changes for Spotify 1.2.86
1 parent c77447a commit 9af100a

File tree

4 files changed

+89
-70
lines changed

4 files changed

+89
-70
lines changed

css-map.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
"cUwQnQoE3OqXqSYLT0hv": "link-subtle",
171171
"VUXMMFKWudUWE1kIXZoS": "link-subtle",
172172
"rC9xwL4gaksmshIjHbNn": "link-subtle",
173+
"uBpmNFia37U4nzmX": "link-subtle",
173174
"iKgf4UDhbRTHxmZSuAEc": "lyrics-lyrics-adLeaderboardIsEnabled",
174175
"L9xhJOJnV2OL5Chm3Jew": "lyrics-lyrics-background",
175176
"o4GE4jG5_QICak2JK_bn": "lyrics-lyrics-background",
@@ -661,13 +662,16 @@
661662
"rBX1EWVZ2EaPwP4y1Gkd": "main-globalNav-icon",
662663
"jdlOKroADlFeZZQeTdp8": "main-globalNav-link-icon",
663664
"dIfr5oVr5kotAi0HsIsW": "main-globalNav-link-icon",
665+
"_Bg_zSvFrEutyacG": "main-globalNav-link-icon",
664666
"bWBqSiXEceAj1SnzqusU": "main-globalNav-navLink",
665667
"obd_bH64Snp1npdw29XM": "main-globalNav-navLink",
666668
"YEAFPNm87XbzS4sF5dDe": "main-globalNav-navLink",
669+
"kUHE42xvQVzWqabl": "main-globalNav-navLink",
667670
"voA9ZoTTlPFyLpckNw3S": "main-globalNav-navLinkActive",
668671
"ETjtwGvAB4lRVqSzm8nA": "main-globalNav-navLinkActive",
669672
"AonZ39aVKATRTjY28Uww": "main-globalNav-navLinkActive",
670673
"Ufz621LN174DTRDis7EY": "main-globalNav-navLinkActive",
674+
"kxv3By32Og8yDEXy": "main-globalNav-navLinkActive",
671675
"QrpHSphgBSqzODEHqr_t": "main-globalNav-searchContainer",
672676
"lj0eGI6WEtfxFX7irC03": "main-globalNav-searchContainer",
673677
"v8JHoFMumOgbCn8vsTvC": "main-globalNav-searchContainer",

jsHelper/spicetifyWrapper.js

Lines changed: 72 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -580,16 +580,20 @@ const fnStr = (f) => {
580580
const exportedMemos = exportedReactObjects[Symbol.for("react.memo")];
581581
const exportedForwardRefs = exportedReactObjects[Symbol.for("react.forward_ref")];
582582
const exportedMemoFRefs = exportedMemos.filter((m) => m.type.$$typeof === Symbol.for("react.forward_ref"));
583-
const exposeReactComponentsUI = ({ modules, functionModules, exportedForwardRefs }) => {
584-
const componentNames = Object.keys(modules.filter(Boolean).find((e) => e.BrowserDefaultFocusStyleProvider));
585-
const componentRegexes = componentNames.map((n) => new RegExp(`"data-encore-id":(?:[a-zA-Z_$][w$]*\\.){2}${n}\\b`));
586-
const componentPairs = [functionModules.map((f) => [f, f]), exportedForwardRefs.map((f) => [f.render, f])]
583+
const exposeReactComponentsUI = ({ modules, functionModules, exportedForwardRefs, exportedMemoFRefs }) => {
584+
const componentNames = Object.keys(modules.filter(Boolean).find((e) => typeof e.BrowserDefaultFocusStyleProvider === "string"));
585+
const componentRegexes = componentNames.map((n) => new RegExp(`"data-encore-id":(?:[a-zA-Z_$][\\w$]*\\.){2}${n}\\b`));
586+
const componentPairs = [
587+
functionModules.map((f) => [f, f]),
588+
exportedForwardRefs.map((f) => [f.render, f]),
589+
exportedMemoFRefs.map((f) => [f.type.render, f]),
590+
]
587591
.flat()
588592
.map(([s, f]) => [componentNames.find((_, i) => fnStr(s)?.match(componentRegexes[i])), f]);
589593

590594
return Object.fromEntries(componentPairs);
591595
};
592-
const reactComponentsUI = exposeReactComponentsUI({ modules, functionModules, exportedForwardRefs });
596+
const reactComponentsUI = exposeReactComponentsUI({ modules, functionModules, exportedForwardRefs, exportedMemoFRefs });
593597

594598
const knownMenuTypes = ["album", "show", "artist", "track", "playlist"];
595599
const menus = modules
@@ -616,6 +620,11 @@ const fnStr = (f) => {
616620
})
617621
.filter(Boolean);
618622

623+
const menuOverrides = [
624+
["PlaylistMenu", exportedMemos?.find((m) => fnStr(m.type).includes("labelPlacement") && fnStr(m.type).includes("menuPlacement"))],
625+
["TrackMenu", exportedMemos?.find((m) => fnStr(m.type).includes("canSwitchVisuals") && fnStr(m.type).includes("showCanvasAction"))],
626+
].filter(([, v]) => v !== undefined);
627+
619628
const cardTypesToFind = ["album", "artist", "audiobook", "episode", "playlist", "profile", "show", "track"];
620629
const cards = [
621630
...functionModules
@@ -1041,7 +1050,25 @@ body[data-dragging-uri-type] .spicetify-sc-chevronBtn { pointer-events: none; }`
10411050
StoreProvider: functionModules.find((m) => fnStr(m).includes("notifyNestedSubs") && fnStr(m).includes("serverState")),
10421051
ScrollableContainer: _ScrollableContainer,
10431052
IconComponent: reactComponentsUI.Icon,
1053+
Navigation: (() => {
1054+
// Spotify >= 1.2.86
1055+
try {
1056+
const navModuleEntry = Object.entries(require.m).find(([, v]) => fnStr(v).includes("navigationalRoot") && fnStr(v).includes("noLink"));
1057+
if (navModuleEntry) {
1058+
const Logo = require(navModuleEntry[0])?.A;
1059+
if (typeof Logo === "function") {
1060+
const element = Logo({ customLink: "/", noLink: false, hasText: false });
1061+
if (element?.type) return element.type;
1062+
}
1063+
}
1064+
// <= Spotify 1.2.85
1065+
return exportedMemoFRefs.find((m) => fnStr(m.type?.render).includes("navigationalRoot"));
1066+
} catch {
1067+
return undefined;
1068+
}
1069+
})(),
10441070
...Object.fromEntries(menus),
1071+
...Object.fromEntries(menuOverrides),
10451072
},
10461073
ReactHook: {
10471074
DragHandler: functionModules.find((m) => fnStr(m).includes("dataTransfer") && fnStr(m).includes("data-dragging")),
@@ -1078,21 +1105,12 @@ body[data-dragging-uri-type] .spicetify-sc-chevronBtn { pointer-events: none; }`
10781105
});
10791106

10801107
if (!Spicetify.ContextMenuV2._context) Spicetify.ContextMenuV2._context = Spicetify.React.createContext({});
1081-
if (!Spicetify.ReactComponent.Navigation)
1082-
Spicetify.ReactComponent.Navigation = exportedMemoFRefs.find((m) => fnStr(m.type.render).includes("navigationalRoot"));
10831108

10841109
(function waitForChunks() {
1085-
const listOfComponents = [
1086-
"Slider",
1087-
"Dropdown",
1088-
"Toggle",
1089-
// "Cards.Artist",
1090-
// "Cards.Audiobook",
1091-
// "Cards.Profile",
1092-
// "Cards.Show",
1093-
// "Cards.Track",
1094-
];
1110+
const listOfComponents = ["Slider", "Dropdown", "Toggle", "Cards.Artist", "Cards.Audiobook", "Cards.Profile", "Cards.Show", "Cards.Track"];
10951111
if (listOfComponents.every((component) => component.split(".").reduce((o, k) => o?.[k], Spicetify.ReactComponent) !== undefined)) return;
1112+
const currentChunks = Object.entries(require.m);
1113+
10961114
const cache = Object.keys(require.m).map((id) => require(id));
10971115
const modules = cache
10981116
.filter((module) => typeof module === "object")
@@ -1108,45 +1126,49 @@ body[data-dragging-uri-type] .spicetify-sc-chevronBtn { pointer-events: none; }`
11081126
? Object.values(module).filter((v) => typeof v === "function" && !webpackFactories.has(v))
11091127
: []
11101128
);
1111-
const exportedMemos = modules.filter((m) => m?.$$typeof === Symbol.for("react.memo"));
1112-
const cardTypesToFind = ["artist", "audiobook", "profile", "show", "track"];
1113-
// const cards = [
1114-
// ...functionModules
1115-
// .flatMap((m) => {
1116-
// return cardTypesToFind.map((type) => {
1117-
// if (m.toString().includes(`featureIdentifier:"${type}"`)) {
1118-
// cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1);
1119-
// return [type[0].toUpperCase() + type.slice(1), m];
1120-
// }
1121-
// });
1122-
// })
1123-
// .filter(Boolean),
1124-
// ...modules
1125-
// .flatMap((m) => {
1126-
// return cardTypesToFind.map((type) => {
1127-
// try {
1128-
// if (m?.type?.toString().includes(`featureIdentifier:"${type}"`)) {
1129-
// cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1);
1130-
// return [type[0].toUpperCase() + type.slice(1), m];
1131-
// }
1132-
// } catch {}
1133-
// });
1134-
// })
1135-
// .filter(Boolean),
1136-
// ];
1129+
1130+
const cardTypesToFind = ["audiobook", "profile", "show"];
1131+
const lazyCards = [
1132+
...functionModules
1133+
.flatMap((m) => {
1134+
return cardTypesToFind.map((type) => {
1135+
if (fnStr(m).includes(`featureIdentifier:"${type}"`)) {
1136+
cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1);
1137+
return [type[0].toUpperCase() + type.slice(1), m];
1138+
}
1139+
});
1140+
})
1141+
.filter(Boolean),
1142+
...modules
1143+
.flatMap((m) => {
1144+
return cardTypesToFind.map((type) => {
1145+
try {
1146+
if ((m?.type ? fnStr(m.type) : "").includes(`featureIdentifier:"${type}"`)) {
1147+
cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1);
1148+
return [type[0].toUpperCase() + type.slice(1), m];
1149+
}
1150+
} catch {}
1151+
});
1152+
})
1153+
.filter(Boolean),
1154+
];
1155+
Object.assign(Spicetify.ReactComponent.Cards, Object.fromEntries(lazyCards));
11371156

11381157
Spicetify.ReactComponent.Slider = wrapProvider(functionModules.find((m) => fnStr(m).includes("progressBarRef")));
11391158
Spicetify.ReactComponent.Toggle = functionModules.find((m) => fnStr(m).includes("onSelected") && fnStr(m).includes('type:"checkbox"'));
1140-
// Object.assign(Spicetify.ReactComponent.Cards, Object.fromEntries(cards));
11411159

11421160
// chunks
1143-
const dropdownChunk = chunks.find(([, value]) => fnStr(value).includes("dropDown") && fnStr(value).includes("isSafari"));
1161+
const dropdownChunk = currentChunks.find(([, value]) => fnStr(value).includes("dropdown-list") && fnStr(value).includes('"listbox"'));
11441162
if (dropdownChunk) {
11451163
Spicetify.ReactComponent.Dropdown =
1146-
Object.values(require(dropdownChunk[0]))?.[0]?.render ?? Object.values(require(dropdownChunk[0])).find((m) => typeof m === "function");
1164+
Object.values(require(dropdownChunk[0])).find(
1165+
(m) => m?.$$typeof === Symbol.for("react.forward_ref") && fnStr(m.render).includes("dropdown-list")
1166+
) ??
1167+
Object.values(require(dropdownChunk[0]))?.[0]?.render ??
1168+
Object.values(require(dropdownChunk[0])).find((m) => typeof m === "function");
11471169
}
11481170

1149-
const toggleChunk = chunks.find(([, value]) => fnStr(value).includes("onSelected") && fnStr(value).includes('type:"checkbox"'));
1171+
const toggleChunk = currentChunks.find(([, value]) => fnStr(value).includes("onSelected") && fnStr(value).includes('type:"checkbox"'));
11501172
if (toggleChunk && !Spicetify.ReactComponent.Toggle) {
11511173
Spicetify.ReactComponent.Toggle = Object.values(require(toggleChunk[0]))[0].render;
11521174
}
@@ -1300,22 +1322,11 @@ body[data-dragging-uri-type] .spicetify-sc-chevronBtn { pointer-events: none; }`
13001322
setTimeout(bindColorExtractor, 10);
13011323
return;
13021324
}
1303-
let imageAnalysis = functionModules.find((m) => m.toString().match(/![\w$]+\.isFallback|\{extractColor/g));
1325+
const imageAnalysis = functionModules.find(
1326+
(m) => fnStr(m).match(/![\w$]+\.isFallback|\{extractColor/g) || (fnStr(m).includes("extractedColors") && fnStr(m).includes("imageUris"))
1327+
);
13041328
const fallbackPreset = modules.find((m) => m?.colorDark);
13051329

1306-
// Search chunk in Spotify 1.2.13 or much older because it is impossible to find any distinguishing features
1307-
if (!imageAnalysis) {
1308-
let chunk = chunks.find(
1309-
([, value]) =>
1310-
(value.toString().match(/[\w$]+\.isFallback/g) || value.toString().includes("colorRaw:")) && value.toString().match(/.extractColor/g)
1311-
);
1312-
if (!chunk) {
1313-
await new Promise((resolve) => setTimeout(resolve, 100));
1314-
chunk = chunks.find(([, value]) => value.toString().match(/[\w$]+\.isFallback/g) && value.toString().match(/.extractColor/g));
1315-
}
1316-
imageAnalysis = Object.values(require(chunk[0])).find((m) => typeof m === "function");
1317-
}
1318-
13191330
Spicetify.extractColorPreset = async (image) => {
13201331
const analysis = await imageAnalysis(Spicetify.GraphQL.Request, image);
13211332
for (const result of analysis) {

src/apply/apply.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,12 @@ func insertCustomApp(jsPath string, flags Flag) {
263263
// React lazy loading patterns for dynamic imports
264264
reactPatterns := []string{
265265
// 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+\)\)\}?\)\)`,
266+
`([\w_\$][\w_\$\d]*(?:\(\))?)\.lazy\(\((?:\(\)=>|function\(\)\{return )(\w+)\.(\w+)\(["']?[\w-]+["']?\)\.then\(\w+\.bind\(\w+,["']?[\w-]+["']?\)\)\}?\)\)`,
267267
// 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+\)\)`,
268+
`([\w_\$][\w_\$\d]*)\.lazy\(async\(\)=>\{(?:[^{}]|\{[^{}]*\})*await\s+(\w+)\.(\w+)\(["']?[\w-]+["']?\)\.then\(\w+\.bind\(\w+,["']?[\w-]+["']?\)\)`,
269+
// Async Promise.all pattern (1.2.78+): m.lazy(async()=>await Promise.all([Y.Z(123),...]).then(...))
270+
// Capture the chunk loader from the first entry inside Promise.all, not from .bind()
271+
`([\w_\$][\w_\$\d]*(?:\(\))?)\.lazy\(async\(\)=>await\s+Promise\.all\(\[(\w+)\.(\w+)\(["']?[\w-]+["']?\)`,
271272
}
272273

273274
// React element/route patterns for path matching

src/preprocess/preprocess.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla
104104
readLocalCssMap(&cssTranslationMap)
105105
}
106106

107+
cssMapPairs := make([]string, 0, len(cssTranslationMap)*2)
108+
for k, v := range cssTranslationMap {
109+
cssMapPairs = append(cssMapPairs, k, v)
110+
}
111+
cssMapJSReplacer := strings.NewReplacer(cssMapPairs...)
112+
cssMapJSStringRe := regexp.MustCompile(`"[^"]*"`)
113+
107114
verParts := strings.Split(flags.SpotifyVer, ".")
108115
spotifyMajor, spotifyMinor, spotifyPatch := 0, 0, 0
109116
if len(verParts) > 0 {
@@ -259,11 +266,7 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla
259266
})
260267
}
261268

262-
for k, v := range cssTranslationMap {
263-
utils.Replace(&content, k, func(submatches ...string) string {
264-
return v
265-
})
266-
}
269+
content = cssMapJSStringRe.ReplaceAllStringFunc(content, cssMapJSReplacer.Replace)
267270
content = colorVariableReplaceForJS(content)
268271

269272
return content

0 commit comments

Comments
 (0)