Skip to content

Commit e157627

Browse files
committed
fix: adapt wrapper to spotify 1.2.83
1 parent 0ac2037 commit e157627

File tree

1 file changed

+65
-52
lines changed

1 file changed

+65
-52
lines changed

jsHelper/spicetifyWrapper.js

Lines changed: 65 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,18 @@ applyScrollingFix();
504504
});
505505
})();
506506

507+
const fnStr = (f) => {
508+
try {
509+
return f.toString();
510+
} catch {
511+
try {
512+
return Function.prototype.toString.call(f);
513+
} catch {
514+
return "";
515+
}
516+
}
517+
};
518+
507519
(async function hotloadWebpackModules() {
508520
while (!window?.webpackChunkclient_web) {
509521
await new Promise((r) => setTimeout(r, 50));
@@ -554,7 +566,14 @@ applyScrollingFix();
554566
return a;
555567
}, {});
556568
};
557-
const functionModules = modules.filter((module) => typeof module === "function");
569+
const webpackFactories = new Set(Object.values(require.m));
570+
const functionModules = modules.flatMap((module) =>
571+
typeof module === "function"
572+
? [module]
573+
: typeof module === "object" && module
574+
? Object.values(module).filter((v) => typeof v === "function" && !webpackFactories.has(v))
575+
: []
576+
);
558577
const exportedReactObjects = groupBy(modules.filter(Boolean), (x) => x.$$typeof);
559578
const exportedMemos = exportedReactObjects[Symbol.for("react.memo")];
560579
const exportedForwardRefs = exportedReactObjects[Symbol.for("react.forward_ref")];
@@ -564,17 +583,18 @@ applyScrollingFix();
564583
const componentRegexes = componentNames.map((n) => new RegExp(`"data-encore-id":(?:[a-zA-Z_\$][\w\$]*\\.){2}${n}\\b`));
565584
const componentPairs = [functionModules.map((f) => [f, f]), exportedForwardRefs.map((f) => [f.render, f])]
566585
.flat()
567-
.map(([s, f]) => [componentNames.find((_, i) => s.toString().match(componentRegexes[i])), f]);
586+
.map(([s, f]) => [componentNames.find((_, i) => fnStr(s)?.match(componentRegexes[i])), f]);
587+
568588
return Object.fromEntries(componentPairs);
569589
};
570590
const reactComponentsUI = exposeReactComponentsUI({ modules, functionModules, exportedForwardRefs });
571591

572592
const knownMenuTypes = ["album", "show", "artist", "track", "playlist"];
573593
const menus = modules
574594
.map((m) => {
575-
const valueMatch = m?.type?.toString().match(/value:"([\w-]+)"/);
595+
const valueMatch = (m?.type ? fnStr(m.type) : "").match(/value:"([\w-]+)"/);
576596
if (valueMatch) return [m, valueMatch[1]];
577-
const typeMatch = m?.type?.toString().match(/type:[\w$]+\.[\w$]+\.([A-Z_]+)/);
597+
const typeMatch = (m?.type ? fnStr(m.type) : "").match(/type:[\w$]+\.[\w$]+\.([A-Z_]+)/);
578598
if (typeMatch) return [m, typeMatch[1].toLowerCase()];
579599
return null;
580600
})
@@ -599,7 +619,7 @@ applyScrollingFix();
599619
...functionModules
600620
.flatMap((m) => {
601621
return cardTypesToFind.map((type) => {
602-
if (m.toString().includes(`featureIdentifier:"${type}"`)) {
622+
if (fnStr(m).includes(`featureIdentifier:"${type}"`)) {
603623
cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1);
604624
return [type[0].toUpperCase() + type.slice(1), m];
605625
}
@@ -610,7 +630,7 @@ applyScrollingFix();
610630
.flatMap((m) => {
611631
return cardTypesToFind.map((type) => {
612632
try {
613-
if (m?.type?.toString().includes(`featureIdentifier:"${type}"`)) {
633+
if ((m?.type ? fnStr(m.type) : "").includes(`featureIdentifier:"${type}"`)) {
614634
cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1);
615635
return [type[0].toUpperCase() + type.slice(1), m];
616636
}
@@ -630,7 +650,7 @@ applyScrollingFix();
630650
.filter(([_, v]) => v.toString().includes("[native code]"))
631651
.map(([i]) => require(i))
632652
.find((e) => typeof e === "function"),
633-
Color: functionModules.find((m) => m.toString().includes("static fromHex") || m.toString().includes("this.rgb")),
653+
Color: functionModules.find((m) => fnStr(m).includes("static fromHex") || fnStr(m).includes("this.rgb")),
634654
Player: {
635655
...Spicetify.Player,
636656
get origin() {
@@ -642,79 +662,74 @@ applyScrollingFix();
642662
get Request() {
643663
return Spicetify.Platform?.GraphQLLoader || Spicetify.GraphQL.Handler?.(Spicetify.GraphQL.Context);
644664
},
645-
Context: functionModules.find((m) => m.toString().includes("subscription") && m.toString().includes("mutation")),
646-
Handler: functionModules.find((m) => m.toString().includes("GraphQL subscriptions are not supported")),
665+
Context: functionModules.find((m) => fnStr(m).includes("subscription") && fnStr(m).includes("mutation")),
666+
Handler: functionModules.find((m) => fnStr(m).includes("GraphQL subscriptions are not supported")),
647667
},
648668
ReactComponent: {
649669
...Spicetify.ReactComponent,
650670
TextComponent: modules.find((m) => m?.h1 && m?.render),
651-
Menu: functionModules.find((m) => m.toString().includes("getInitialFocusElement") && m.toString().includes("children")),
652-
MenuItem: functionModules.find((m) => m.toString().includes("handleMouseEnter") && m.toString().includes("onClick")),
653-
MenuSubMenuItem: functionModules.find((f) => f.toString().includes("subMenuIcon")),
654-
Slider: wrapProvider(functionModules.find((m) => m.toString().includes("progressBarRef"))),
655-
RemoteConfigProvider: functionModules.find((m) => m.toString().includes("resolveSuspense") && m.toString().includes("configuration")),
671+
Menu: functionModules.find((m) => fnStr(m).includes("getInitialFocusElement") && fnStr(m).includes("children")),
672+
MenuItem: functionModules.find((m) => fnStr(m).includes("handleMouseEnter") && fnStr(m).includes("onClick")),
673+
MenuSubMenuItem: functionModules.find((f) => fnStr(f).includes("subMenuIcon")),
674+
Slider: wrapProvider(functionModules.find((m) => fnStr(m).includes("progressBarRef"))),
675+
RemoteConfigProvider: functionModules.find((m) => fnStr(m).includes("resolveSuspense") && fnStr(m).includes("configuration")),
656676
RightClickMenu: functionModules.find(
657-
(m) =>
658-
m.toString().includes("action") && m.toString().includes("open") && m.toString().includes("trigger") && m.toString().includes("right-click")
677+
(m) => fnStr(m).includes("action") && fnStr(m).includes("open") && fnStr(m).includes("trigger") && fnStr(m).includes("right-click")
659678
),
660-
TooltipWrapper: functionModules.find((m) => m.toString().includes("renderInline") && m.toString().includes("showDelay")),
679+
TooltipWrapper: functionModules.find((m) => fnStr(m).includes("renderInline") && fnStr(m).includes("showDelay")),
661680
ButtonPrimary: reactComponentsUI.ButtonPrimary,
662681
ButtonSecondary: reactComponentsUI.ButtonSecondary,
663682
ButtonTertiary: reactComponentsUI.ButtonTertiary,
664683
Snackbar: {
665-
wrapper: functionModules.find((m) => m.toString().includes("encore-light-theme") && m.toString().includes("elevated")),
666-
simpleLayout: functionModules.find((m) => ["leading", "center", "trailing"].every((keyword) => m.toString().includes(keyword))),
667-
ctaText: functionModules.find((m) => m.toString().includes("ctaText")),
668-
styledImage: functionModules.find((m) => m.toString().includes("placeholderSrc")),
684+
wrapper: functionModules.find((m) => fnStr(m).includes("encore-light-theme") && fnStr(m).includes("elevated")),
685+
simpleLayout: functionModules.find((m) => ["leading", "center", "trailing"].every((keyword) => fnStr(m).includes(keyword))),
686+
ctaText: functionModules.find((m) => fnStr(m).includes("ctaText")),
687+
styledImage: functionModules.find((m) => fnStr(m).includes("placeholderSrc")),
669688
},
670689
Chip: reactComponentsUI.Chip,
671-
Toggle: functionModules.find((m) => m.toString().includes("onSelected") && m.toString().includes('type:"checkbox"')),
690+
Toggle: functionModules.find((m) => fnStr(m).includes("onSelected") && fnStr(m).includes('type:"checkbox"')),
672691
Cards: {
673692
Default: reactComponentsUI.Card,
674693
FeatureCard: functionModules.find(
675-
(m) => m.toString().includes("?highlight") && m.toString().includes("headerText") && m.toString().includes("imageContainer")
694+
(m) => fnStr(m).includes("?highlight") && fnStr(m).includes("headerText") && fnStr(m).includes("imageContainer")
676695
),
677-
Hero: functionModules.find((m) => m?.toString().includes('"herocard-click-handler"')),
696+
Hero: functionModules.find((m) => fnStr(m).includes('"herocard-click-handler"')),
678697
CardImage: functionModules.find(
679698
(m) =>
680-
m.toString().includes("isHero") &&
681-
(m.toString().includes("withWaves") || m.toString().includes("isCircular")) &&
682-
m.toString().includes("imageWrapper")
699+
fnStr(m).includes("isHero") && (fnStr(m).includes("withWaves") || fnStr(m).includes("isCircular")) && fnStr(m).includes("imageWrapper")
683700
),
684701
...Object.fromEntries(cards),
685702
},
686-
Router: functionModules.find((m) => m.toString().includes("navigationType") && m.toString().includes("static")),
687-
Routes: functionModules.find((m) => m.toString().match(/\([\w$]+\)\{let\{children:[\w$]+,location:[\w$]+\}=[\w$]+/)),
688-
Route: functionModules.find((m) => m.toString().match(/^function [\w$]+\([\w$]+\)\{\(0,[\w$]+\.[\w$]+\)\(!1\)\}$/)),
689-
StoreProvider: functionModules.find((m) => m.toString().includes("notifyNestedSubs") && m.toString().includes("serverState")),
690-
ScrollableContainer: functionModules.find((m) => m.toString().includes("scrollLeft") && m.toString().includes("showButtons")),
703+
Router: functionModules.find((m) => fnStr(m).includes("navigationType") && fnStr(m).includes("static")),
704+
Routes: functionModules.find((m) => fnStr(m).match(/\([\w$]+\)\{let\{children:[\w$]+,location:[\w$]+\}=[\w$]+/)),
705+
Route: functionModules.find((m) => fnStr(m).match(/^function [\w$]+\([\w$]+\)\{\(0,[\w$]+\.[\w$]+\)\(!1\)\}$/)),
706+
StoreProvider: functionModules.find((m) => fnStr(m).includes("notifyNestedSubs") && fnStr(m).includes("serverState")),
707+
ScrollableContainer: functionModules.find((m) => fnStr(m).includes("scrollLeft") && fnStr(m).includes("showButtons")),
691708
IconComponent: reactComponentsUI.Icon,
692709
...Object.fromEntries(menus),
693710
},
694711
ReactHook: {
695-
DragHandler: functionModules.find((m) => m.toString().includes("dataTransfer") && m.toString().includes("data-dragging")),
712+
DragHandler: functionModules.find((m) => fnStr(m).includes("dataTransfer") && fnStr(m).includes("data-dragging")),
696713
useExtractedColor: functionModules.find(
697-
(m) => m.toString().includes("extracted-color") || (m.toString().includes("colorRaw") && m.toString().includes("useEffect"))
714+
(m) => fnStr(m).includes("extracted-color") || (fnStr(m).includes("colorRaw") && fnStr(m).includes("useEffect"))
698715
),
699716
},
700717
// React Query
701718
// https://github.com/TanStack/query
702719
// v3 until Spotify v1.2.29
703720
// v5 since Spotify v1.2.30
704721
ReactQuery: cache.find((module) => module.useQuery) || {
705-
PersistQueryClientProvider: functionModules.find((m) => m.toString().includes("persistOptions")),
706-
QueryClient: functionModules.find((m) => m.toString().includes("defaultMutationOptions")),
707-
QueryClientProvider: functionModules.find((m) => m.toString().includes("use QueryClientProvider")),
722+
PersistQueryClientProvider: functionModules.find((m) => fnStr(m).includes("persistOptions")),
723+
QueryClient: functionModules.find((m) => fnStr(m).includes("defaultMutationOptions")),
724+
QueryClientProvider: functionModules.find((m) => fnStr(m).includes("use QueryClientProvider")),
708725
notifyManager: modules.find((m) => m?.setBatchNotifyFunction),
709-
useMutation: functionModules.find((m) => m.toString().includes("mutateAsync")),
726+
useMutation: functionModules.find((m) => fnStr(m).includes("mutateAsync")),
710727
useQuery: functionModules.find((m) =>
711-
m.toString().match(/^function [\w_$]+\(([\w_$]+),([\w_$]+)\)\{return\(0,[\w_$]+\.[\w_$]+\)\(\1,[\w_$]+\.[\w_$]+,\2\)\}$/)
712-
),
713-
useQueryClient: functionModules.find(
714-
(m) => m.toString().includes("client") && m.toString().includes("Provider") && m.toString().includes("mount")
728+
fnStr(m).match(/^function [\w_$]+\(([\w_$]+),([\w_$]+)\)\{return\(0,[\w_$]+\.[\w_$]+\)\(\1,[\w_$]+\.[\w_$]+,\2\)\}$/)
715729
),
730+
useQueryClient: functionModules.find((m) => fnStr(m).includes("client") && fnStr(m).includes("Provider") && fnStr(m).includes("mount")),
716731
useSuspenseQuery: functionModules.find(
717-
(m) => m.toString().includes("throwOnError") && m.toString().includes("suspense") && m.toString().includes("enabled")
732+
(m) => fnStr(m).includes("throwOnError") && fnStr(m).includes("suspense") && fnStr(m).includes("enabled")
718733
),
719734
},
720735
ReactFlipToolkit: {
@@ -729,7 +744,7 @@ applyScrollingFix();
729744

730745
if (!Spicetify.ContextMenuV2._context) Spicetify.ContextMenuV2._context = Spicetify.React.createContext({});
731746
if (!Spicetify.ReactComponent.Navigation)
732-
Spicetify.ReactComponent.Navigation = exportedMemoFRefs.find((m) => m.type.render.toString().includes("navigationalRoot"));
747+
Spicetify.ReactComponent.Navigation = exportedMemoFRefs.find((m) => fnStr(m.type.render).includes("navigationalRoot"));
733748

734749
(function waitForChunks() {
735750
const listOfComponents = [
@@ -743,7 +758,7 @@ applyScrollingFix();
743758
// "Cards.Show",
744759
// "Cards.Track",
745760
];
746-
if (listOfComponents.every((component) => Spicetify.ReactComponent[component] !== undefined)) return;
761+
if (listOfComponents.every((component) => component.split(".").reduce((o, k) => o?.[k], Spicetify.ReactComponent) !== undefined)) return;
747762
const cache = Object.keys(require.m).map((id) => require(id));
748763
const modules = cache
749764
.filter((module) => typeof module === "object")
@@ -798,7 +813,7 @@ applyScrollingFix();
798813
Spicetify.ReactComponent.Toggle = Object.values(require(toggleChunk[0]))[0].render;
799814
}
800815

801-
if (!listOfComponents.every((component) => Spicetify.ReactComponent[component] !== undefined)) {
816+
if (!listOfComponents.every((component) => component.split(".").reduce((o, k) => o?.[k], Spicetify.ReactComponent) !== undefined)) {
802817
setTimeout(waitForChunks, 100);
803818
return;
804819
}
@@ -815,16 +830,14 @@ applyScrollingFix();
815830
// https://github.com/iamhosseindhv/notistack
816831
Spicetify.Snackbar = {
817832
...Spicetify.Snackbar,
818-
SnackbarProvider: functionModules.find((m) => m.toString().includes("enqueueSnackbar called with invalid argument")),
819-
useSnackbar: functionModules.find((m) => m.toString().match(/^function\(\)\{return\(0,[\w$]+\.useContext\)\([\w$]+\)\}$/)),
833+
SnackbarProvider: functionModules.find((m) => fnStr(m).includes("enqueueSnackbar called with invalid argument")),
834+
useSnackbar: functionModules.find((m) => fnStr(m).match(/^function\(\)\{return\(0,[\w$]+\.useContext\)\([\w$]+\)\}$/)),
820835
};
821836
})();
822837

823838
const localeModule = modules.find((m) => m?.getTranslations);
824839
if (localeModule) {
825-
const createUrlLocale = functionModules.find(
826-
(m) => m.toString().includes("has") && m.toString().includes("baseName") && m.toString().includes("language")
827-
);
840+
const createUrlLocale = functionModules.find((m) => fnStr(m).includes("has") && fnStr(m).includes("baseName") && fnStr(m).includes("language"));
828841
Spicetify.Locale = {
829842
get _relativeTimeFormat() {
830843
return localeModule._relativeTimeFormat;
@@ -892,7 +905,7 @@ applyScrollingFix();
892905
Spicetify.ReactComponent.ConfirmDialog = Object.values(require(confirmDialogChunk[0])).find((m) => typeof m === "object");
893906
} else {
894907
Spicetify.ReactComponent.ConfirmDialog = functionModules.find(
895-
(m) => m.toString().includes("isOpen") && m.toString().includes("shouldCloseOnEsc") && m.toString().includes("onClose")
908+
(m) => fnStr(m).includes("isOpen") && fnStr(m).includes("shouldCloseOnEsc") && fnStr(m).includes("onClose")
896909
);
897910
}
898911

0 commit comments

Comments
 (0)