diff --git a/globals.d.ts b/globals.d.ts
index d6ce7dc8ea..56f7666700 100644
--- a/globals.d.ts
+++ b/globals.d.ts
@@ -1581,91 +1581,6 @@ declare namespace Spicetify {
*/
onOutside?: (event: React.MouseEvent) => void;
};
- type PanelSkeletonProps = {
- /**
- * Aria label for the panel. Does not set the panel header content.
- */
- label?: string;
- /**
- * Item URI of the panel. Used as reference for Spotify's internal Event Factory.
- *
- * @deprecated Since Spotify `1.2.17`
- */
- itemUri?: string;
- /**
- * Additional class name to apply to the panel.
- *
- * @deprecated Since Spotify `1.2.12`
- */
- className?: string;
- /**
- * Additional styles to apply to the panel.
- */
- style?: React.CSSProperties;
- /**
- * Children to render inside the panel.
- */
- children?: React.ReactNode;
- };
- type PanelContentProps = {
- /**
- * Additional class name to apply to the panel.
- */
- className?: string;
- /**
- * Children to render inside the panel.
- */
- children?: React.ReactNode;
- };
- type PanelHeaderProps = {
- /**
- * Href for the header link.
- * Can be either a URI for a path within the app, or a URL for an external link.
- */
- link?: string;
- /**
- * Title of the header.
- */
- title?: string;
- /**
- * Panel ID. Used to toggle panel open/closed state.
- */
- panel: number;
- /**
- * Whether or not the panel contains advertisements.
- * @default false
- */
- isAdvert?: boolean;
- /**
- * Actions to render in the header.
- */
- actions?: React.ReactNode | React.ReactNode[];
- /**
- * Function to call when clicking on the close button.
- * Called before the panel is closed.
- */
- onClose?: () => void;
- /**
- * Prevent the panel from closing when clicking on the header close button.
- * @default false
- */
- preventDefaultClose?: boolean;
- /**
- * Function to call when clicking on the header back button.
- * If not provided, the back button will not be rendered.
- */
- onBack?: (event: React.MouseEvent) => void;
- /**
- * Font variant for the header title.
- * @default "balladBold"
- */
- titleVariant?: Variant;
- /**
- * Semantic color name for the header title.
- * @default "textBase"
- */
- titleSemanticColor?: SemanticColor;
- };
type SliderProps = {
/**
* Label for the slider.
@@ -1861,27 +1776,6 @@ declare namespace Spicetify {
* @see Spicetify.ReactComponent.ConfirmDialogProps
*/
const ConfirmDialog: any;
- /**
- * Component to render Spotify-style panel skeleton
- *
- * Props:
- * @see Spicetify.ReactComponent.PanelSkeletonProps
- */
- const PanelSkeleton: any;
- /**
- * Component to render Spotify-style panel content
- *
- * Props:
- * @see Spicetify.ReactComponent.PanelContentProps
- */
- const PanelContent: any;
- /**
- * Component to render Spotify-style panel header
- *
- * Props:
- * @see Spicetify.ReactComponent.PanelHeaderProps
- */
- const PanelHeader: any;
/**
* Component to render Spotify slider
*
@@ -2203,13 +2097,6 @@ declare namespace Spicetify {
dropOriginUri?: string
): (event: React.DragEvent, uris?: string[], label?: string, contextUri?: string, sectionIndex?: number) => void;
- /**
- * React Hook to use panel state
- * @param id ID of the panel to use
- * @return Object with methods of the panel
- */
- function usePanelState(id: number): { toggle: () => void; isActive: boolean };
-
/**
* React Hook to use extracted color from GraphQL
*
@@ -2227,180 +2114,6 @@ declare namespace Spicetify {
function useExtractedColor(uri: string, fallbackColor?: string, variant?: "colorRaw" | "colorLight" | "colorDark"): string;
}
- /**
- * An API wrapper to interact with Spotify's Panel/right sidebar.
- */
- namespace Panel {
- /**
- * Properties that are used by the `registerPanel` function.
- */
- type PanelProps = {
- /**
- * Label of the Panel.
- */
- label?: string;
- /**
- * Children to render inside the Panel.
- * Must be a React Component.
- * Will be passed a `panel` prop with the Panel ID.
- */
- children: React.ReactNode;
- /**
- * Determine if the children passed is a custom Panel.
- * If true, the children will be rendered as is.
- * Note: All passed props except `children` will be ignored if enabled.
- *
- * @default false
- */
- isCustom?: boolean;
- /**
- * Inline styles to apply to the Panel skeleton.
- */
- style?: React.CSSProperties;
- /**
- * Additional class name to apply to the Panel content wrapper.
- */
- wrapperClassname?: string;
- /**
- * Additional class name to apply to the Panel header.
- */
- headerClassname?: string;
- /**
- * Font variant for the Panel header title.
- * @default "balladBold"
- */
- headerVariant?: Variant;
- /**
- * Semantic color name for the Panel header title.
- * @default "textBase"
- */
- headerSemanticColor?: SemanticColor;
- /**
- * Href for the header link.
- * Can be either a URI for a path within the app, or a URL for an external link.
- */
- headerLink?: string;
- /**
- * Additional actions to render in the header.
- * Will be rendered next to the close button.
- */
- headerActions?: React.ReactNode | React.ReactNode[];
- /**
- * Function to call when clicking on the header close button.
- * Called before the panel is closed.
- */
- headerOnClose?: () => void;
- /**
- * Prevent the panel from closing when clicking on the header close button.
- * @default false
- */
- headerPreventDefaultClose?: boolean;
- /**
- * Function to call when clicking on the header back button.
- * If not provided, the back button will not be rendered.
- * @param event Event object
- */
- headerOnBack?: (event: React.MouseEvent) => void;
- };
-
- /**
- * An object of reserved panel IDs used by Spotify.
- */
- const reservedPanelIds: Record;
- /**
- * Collection of React Components used by Spotify in the Panel.
- */
- const Components: {
- /**
- * React Component for the Panel's skeleton.
- *
- * Props:
- * @see Spicetify.ReactComponent.PanelSkeletonProps
- */
- PanelSkeleton: any;
- /**
- * React Component for the Panel's content.
- *
- * Props:
- * @see Spicetify.ReactComponent.PanelContentProps
- */
- PanelContent: any;
- /**
- * React Component for the Panel's header.
- *
- * Props:
- * @see Spicetify.ReactComponent.PanelHeaderProps
- */
- PanelHeader: any;
- };
- /**
- * Check whether or not a Panel with the provided ID is registered.
- * @param id Panel ID to check
- * @return Whether or not a Panel with the provided ID is registered
- */
- function hasPanel(id: number): boolean;
- /**
- * Get the Panel with the provided ID.
- * @param id Panel ID to get
- * @return Panel with the provided ID
- */
- function getPanel(id: number): React.ReactNode | undefined;
- /**
- * Set the Panel with the provided ID.
- * If the ID is not registered, it will be set to `0`.
- * @param id Panel ID to set
- */
- function setPanel(id: number): Promise;
- /**
- * Subscribe to Panel changes.
- * @param callback Callback to call when Panel changes
- */
- function subPanelState(callback: (id: number) => void): void;
- /**
- * Register a new Panel.
- * An ID will be automatically assigned to the Panel.
- *
- * To make it easier and convenient for developers to use the Panel API, this method by default wraps the children passed into a Panel skeleton and content wrapper.
- *
- * If you wish to customize the Panel, you can pass `isCustom` as `true` to disable the default wrapper.
- *
- * @param props Properties of the Panel
- * @return Methods and properties of the Panel
- */
- function registerPanel(props: PanelProps): {
- /**
- * Assigned ID of the Panel.
- */
- id: number;
- /**
- * Function to toggle the Panel open/closed state.
- */
- toggle: () => Promise;
- /**
- * Method to subscribe to the related Panel state.
- * Only fires when the related Panel open/closed state changes.
- */
- onStateChange: (callback: (isActive: boolean) => void) => void;
- /**
- * Boolean to determine if the Panel is open.
- */
- isActive: boolean;
- };
- /**
- * Function to render a Panel of the current ID.
- * If the ID is not registered or is reserved by Spotify, the function will return `null`.
- *
- * Used as a hook for Spotify internal component.
- * @return Panel of the current ID
- */
- function render(): React.ReactNode | null;
- /**
- * ID of the current Panel.
- * @return ID of the current Panel
- */
- const currentPanel: number;
- }
-
/**
* react-flip-toolkit
* @description A lightweight magic-move library for configurable layout transitions.
diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js
index 4d47811bd8..460ae11dc9 100644
--- a/jsHelper/spicetifyWrapper.js
+++ b/jsHelper/spicetifyWrapper.js
@@ -188,14 +188,15 @@ window.Spicetify = {
"_sidebarXItemToClone",
"AppTitle",
"_reservedPanelIds",
- "Panel",
"ReactFlipToolkit",
"classnames",
"ReactQuery",
"Color",
"extractColorPreset",
"ReactDOMServer",
- "Snackbar"
+ "Snackbar",
+ "ContextMenuV2",
+ "ReactJSX"
])
},
{
@@ -257,9 +258,6 @@ window.Spicetify = {
"TextComponent",
"IconComponent",
"ConfirmDialog",
- "PanelContent",
- "PanelSkeleton",
- "PanelHeader",
"Slider",
"RemoteConfigProvider",
"ButtonPrimary",
@@ -274,7 +272,8 @@ window.Spicetify = {
"Route",
"StoreProvider",
"PlatformProvider",
- "Dropdown"
+ "Dropdown",
+ "MenuSubMenuItem"
])
},
{
@@ -285,7 +284,7 @@ window.Spicetify = {
{
objectToCheck: Spicetify.ReactHook,
name: "Spicetify.ReactHook",
- methods: new Set(["DragHandler", "usePanelState", "useExtractedColor"])
+ methods: new Set(["DragHandler", "useExtractedColor"])
}
]);
@@ -309,7 +308,7 @@ window.Spicetify = {
return;
}
- const corsProxyURL = "https://cors-proxy.spicetify.app/";
+ const corsProxyURL = "https://cors-proxy.spicetify.app";
const allowedMethodsMap = {
get: "get",
post: "post",
@@ -321,21 +320,18 @@ window.Spicetify = {
const internalEndpoints = new Set(["sp:", "wg:"]);
const handler = {
- // biome-ignore lint/complexity/useArrowFunction:
- get: function (target, prop, receiver) {
+ get: (target, prop, receiver) => {
const internalFetch = Reflect.get(target, prop, receiver);
if (typeof internalFetch !== "function" || !allowedMethodsSet.has(prop) || Spicetify.Platform.version < "1.2.31") return internalFetch;
- // biome-ignore lint/complexity/useArrowFunction:
return async function (url, body) {
const urlObj = new URL(url);
const isWebAPI = urlObj.hostname === "api.spotify.com";
const isSpClientAPI = urlObj.hostname.includes("spotify.com") && urlObj.hostname.includes("spclient");
const isInternalURL = internalEndpoints.has(urlObj.protocol);
- // biome-ignore lint/style/noArguments:
- if (isInternalURL) return internalFetch.apply(this, arguments);
+ if (isInternalURL) return internalFetch.apply(this, [url, body]);
const shouldUseCORSProxy = !isWebAPI && !isSpClientAPI && !isInternalURL;
@@ -366,7 +362,7 @@ window.Spicetify = {
finalURL += `?${params.toString()}`;
} else options.body = isJson ? JSON.stringify(body) : body;
}
- if (shouldUseCORSProxy) finalURL = `${corsProxyURL}${finalURL}`;
+ if (shouldUseCORSProxy) finalURL = `${corsProxyURL}/${finalURL}`;
const Authorization = `Bearer ${Spicetify.Platform.AuthorizationAPI.getState().token.accessToken}`;
let injectedHeaders = {};
@@ -413,12 +409,12 @@ window.Spicetify = {
})();
(function hotloadWebpackModules() {
- if (!window?.webpackChunkopen) {
+ if (!window?.webpackChunkclient_web) {
setTimeout(hotloadWebpackModules, 50);
return;
}
// Force all webpack modules to load
- const require = webpackChunkopen.push([[Symbol()], {}, re => re]);
+ const require = webpackChunkclient_web.push([[Symbol()], {}, re => re]);
const chunks = require.m ? Object.entries(require.m) : [];
if (!chunks) {
setTimeout(hotloadWebpackModules, 50);
@@ -534,13 +530,6 @@ window.Spicetify = {
m.toString().includes("action") && m.toString().includes("open") && m.toString().includes("trigger") && m.toString().includes("right-click")
),
TooltipWrapper: functionModules.find(m => m.toString().includes("renderInline") && m.toString().includes("showDelay")),
- PanelHeader: functionModules.find(m => m.toString().includes("panel") && m.toString().includes("actions")),
- PanelContent:
- modules.find(m => m?.render?.toString().includes("scrollBarContainer")) ||
- functionModules.find(m => m.toString().includes("scrollBarContainer")),
- PanelSkeleton:
- functionModules.find(m => m.toString().includes("label") && m.toString().includes("aside")) ||
- modules.find(m => m?.render?.toString().includes('"section"')),
ButtonPrimary: modules.find(m => m?.render && m?.displayName === "ButtonPrimary"),
ButtonSecondary: modules.find(m => m?.render && m?.displayName === "ButtonSecondary"),
ButtonTertiary: modules.find(m => m?.render && m?.displayName === "ButtonTertiary"),
@@ -566,8 +555,6 @@ window.Spicetify = {
},
ReactHook: {
DragHandler: functionModules.find(m => m.toString().includes("dataTransfer") && m.toString().includes("data-dragging")),
- // deprecated since 1.2.17
- usePanelState: functionModules.find(m => m.toString().includes("setPanelState")),
useExtractedColor: functionModules.find(
m => m.toString().includes("extracted-color") || (m.toString().includes("colorRaw") && m.toString().includes("useEffect"))
)
@@ -2287,179 +2274,6 @@ Spicetify.Playbar = (() => {
return { Button, Widget };
})();
-// TODO: Remove this with v3 release
-(function waitForPanelAPI() {
- if (!Spicetify.Platform?.PanelAPI || !Spicetify.React || !Spicetify._reservedPanelIds) {
- setTimeout(waitForPanelAPI, 300);
- return;
- }
-
- // Workaround for older versions
- let currentPanelId = 0;
- let fallback = false;
- let refreshTimeout;
- let init = true;
-
- if (!Spicetify.Platform.PanelAPI.getLastCachedPanelState) {
- fallback = true;
- Spicetify.Platform.PanelAPI.subscribeToPanelState(panelId => {
- currentPanelId = panelId;
- });
- }
-
- const contentMap = new Map(
- Object.entries(Spicetify._reservedPanelIds)
- .map(([key, value]) => !Number.isNaN(parseInt(key)) && [parseInt(key), value])
- .filter(Boolean)
- );
-
- // https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
- class ErrorBoundary extends Spicetify.React.Component {
- constructor(props) {
- super(props);
- this.state = { hasError: false };
- }
-
- static getDerivedStateFromError() {
- // Update state so the next render will show the fallback UI.
- return { hasError: true };
- }
-
- componentDidCatch(error, info) {
- const extension =
- Spicetify.Config.extensions.find(ext => error.stack.includes(ext)) ||
- Spicetify.Config.custom_apps.find(app => error.stack.includes(`spicetify-routes-${app}`));
-
- Spicetify.showNotification(
- `Something went wrong in panel ID "${this.props.id}" ${extension ? `of "${extension}"` : ""}, check Console for error log`,
- true
- );
- console.error(error);
- console.error(`Error stack in panel ID "${this.props.id}": ${info.componentStack}`);
- Spicetify.Panel.setPanel(Spicetify.Panel.reservedPanelIds.Disabled);
- }
-
- render() {
- if (this.state.hasError) {
- // `false` not `null`, so it won’t render beyond the null coalescing operator.
- return false;
- }
-
- // Pass the `panel` prop with the current panel ID to the children
- return Spicetify.React.cloneElement(this.props.children, { panel: this.props.id });
- }
- }
-
- Spicetify.Panel = {
- reservedPanelIds: Spicetify._reservedPanelIds,
- Components: {
- PanelSkeleton: Spicetify.ReactComponent.PanelSkeleton,
- PanelContent: Spicetify.ReactComponent.PanelContent,
- PanelHeader: Spicetify.ReactComponent.PanelHeader
- },
- hasPanel: (id, _internal) => {
- // Render is sometimes ran before the wrapper is initialized, so we need to refresh it
- // For some reason it doesn't trigger listeners
- if (_internal && init) {
- clearTimeout(refreshTimeout);
- refreshTimeout = setTimeout(() => {
- Spicetify.Panel.setPanel(id);
- init = false;
- }, 100);
- }
- return contentMap.has(id);
- },
- getPanel: id => contentMap.get(id),
- render: () => {
- const { currentPanel } = Spicetify.Panel;
- return (!Spicetify.Panel.reservedPanelIds[currentPanel] && contentMap.get(Spicetify.Panel.currentPanel)) || null;
- },
- get currentPanel() {
- return fallback ? currentPanelId : Spicetify.Platform.PanelAPI.getLastCachedPanelState();
- },
- setPanel: async id => {
- currentPanelId = id;
- await Spicetify.Platform.PanelAPI.setPanelState(id);
- },
- subPanelState: callback => Spicetify.Platform.PanelAPI.subscribeToPanelState(callback),
- registerPanel: ({
- label,
- children,
- isCustom = false,
- style,
- wrapperClassname,
- headerClassname,
- headerVariant,
- headerSemanticColor,
- headerLink,
- headerActions,
- headerOnClose,
- headerPreventDefaultClose,
- headerOnBack
- }) => {
- const id = [...contentMap.keys()].sort((a, b) => a - b).pop() + 1;
- const content = isCustom
- ? children
- : Spicetify.React.createElement(
- Spicetify.ReactComponent.PanelSkeleton,
- {
- label,
- // Backwards compatibility, no longer needed in Spotify 1.2.12
- className: "Root__right-sidebar",
- style
- },
- Spicetify.React.createElement(
- Spicetify.ReactComponent.PanelContent,
- {
- className: wrapperClassname
- },
- Spicetify.React.createElement(Spicetify.ReactComponent.PanelHeader, {
- title: label,
- panel: id,
- link: headerLink,
- actions: headerActions,
- onClose: headerOnClose,
- onBack: headerOnBack,
- preventDefaultClose: headerPreventDefaultClose,
- className: headerClassname,
- titleVariant: headerVariant,
- titleSemanticColor: headerSemanticColor
- }),
- Spicetify.React.cloneElement(children, { panel: id })
- )
- );
-
- contentMap.set(id, Spicetify.React.createElement(ErrorBoundary, { id }, content));
-
- let isActive = Spicetify.Panel.currentPanel === id;
-
- return {
- id,
- toggle: async () => {
- const { currentPanel } = Spicetify.Panel;
- currentPanelId = currentPanel === id ? 0 : id;
- await Spicetify.Panel.setPanel(currentPanel === id ? 0 : id);
- },
- onStateChange: callback => {
- Spicetify.Panel.subPanelState(panel => {
- const activeState = panel === id;
- if (activeState !== isActive) {
- isActive = activeState;
- callback(isActive);
- }
- });
- },
- get isActive() {
- return Spicetify.Panel.currentPanel === id;
- }
- };
- }
- };
-
- // Eliminate false positives
- Spicetify.Panel.subPanelState(() => clearTimeout(refreshTimeout));
-})();
-
(async function checkForUpdate() {
if (!Spicetify.Config) {
setTimeout(checkForUpdate, 300);
diff --git a/src/apply/apply.go b/src/apply/apply.go
index 553f54feed..71ade3945c 100644
--- a/src/apply/apply.go
+++ b/src/apply/apply.go
@@ -96,30 +96,30 @@ func htmlMod(htmlPath string, flags Flag) {
helperHTML := "\n"
if flags.InjectThemeJS {
- extensionsHTML += `` + "\n"
+ extensionsHTML += "\n"
}
if flags.SidebarConfig {
- helperHTML += `` + "\n"
+ helperHTML += "\n"
}
if flags.HomeConfig {
- helperHTML += `` + "\n"
+ helperHTML += "\n"
}
if flags.ExpFeatures {
- helperHTML += `` + "\n"
+ helperHTML += "\n"
}
if flags.SpicetifyVer != "" {
var extList string
for _, ext := range flags.Extension {
- extList += `"` + ext + `",`
+ extList += fmt.Sprintf(`"%s",`, ext)
}
var customAppList string
for _, app := range flags.CustomApp {
- customAppList += `"` + app + `",`
+ customAppList += fmt.Sprintf(`"%s",`, app)
}
helperHTML += fmt.Sprintf(`` + "\n"
+ extensionsHTML += fmt.Sprintf("\n", v)
} else {
- extensionsHTML += `` + "\n"
+ extensionsHTML += fmt.Sprintf("\n", v)
}
}
@@ -147,9 +147,9 @@ func htmlMod(htmlPath string, flags Flag) {
if err == nil {
for _, extensionFile := range manifest.ExtensionFiles {
if strings.HasSuffix(extensionFile, ".mjs") {
- extensionsHTML += `` + "\n"
+ extensionsHTML += fmt.Sprintf("\n", v, extensionFile)
} else {
- extensionsHTML += `` + "\n"
+ extensionsHTML += fmt.Sprintf("\n", v, extensionFile)
}
}
}
@@ -159,11 +159,15 @@ func htmlMod(htmlPath string, flags Flag) {
utils.Replace(
&content,
`<\!-- spicetify helpers -->`,
- "${0}"+helperHTML)
+ func(submatches ...string) string {
+ return fmt.Sprintf("%s%s", submatches[0], helperHTML)
+ })
utils.Replace(
&content,
`
`, "${0}\n"+tags)
+ utils.Replace(&content, ``, func(submatches ...string) string {
+ return fmt.Sprintf("%s\n%s", submatches[0], tags)
+ })
return content
})
}
return nil
})
-
- fakeZLink(filepath.Join(extractedAppsPath, "zlink"))
}
// StartCSS modifies all CSS files in extractedAppsPath to change
@@ -170,117 +172,265 @@ func StartCSS(extractedAppsPath string) {
}
func colorVariableReplace(content string) string {
- utils.Replace(&content, "#181818", "var(--spice-player)")
- utils.Replace(&content, "#212121", "var(--spice-player)")
+ utils.Replace(&content, "#181818", func(submatches ...string) string {
+ return "var(--spice-player)"
+ })
+ utils.Replace(&content, "#212121", func(submatches ...string) string {
+ return "var(--spice-player)"
+ })
- utils.Replace(&content, "#282828", "var(--spice-card)")
+ utils.Replace(&content, "#282828", func(submatches ...string) string {
+ return "var(--spice-card)"
+ })
- utils.Replace(&content, "#121212", "var(--spice-main)")
- utils.Replace(&content, "#242424", "var(--spice-main-elevated)")
+ utils.Replace(&content, "#121212", func(submatches ...string) string {
+ return "var(--spice-main)"
+ })
+ utils.Replace(&content, "#242424", func(submatches ...string) string {
+ return "var(--spice-main-elevated)"
+ })
- utils.Replace(&content, "#1a1a1a", "var(--spice-highlight)")
- utils.Replace(&content, "#2a2a2a", "var(--spice-highlight-elevated)")
+ utils.Replace(&content, "#1a1a1a", func(submatches ...string) string {
+ return "var(--spice-highlight)"
+ })
+ utils.Replace(&content, "#2a2a2a", func(submatches ...string) string {
+ return "var(--spice-highlight-elevated)"
+ })
- utils.Replace(&content, "#000", "var(--spice-sidebar)")
- utils.Replace(&content, "#000000", "var(--spice-sidebar)")
+ utils.Replace(&content, "#000", func(submatches ...string) string {
+ return "var(--spice-sidebar)"
+ })
+ utils.Replace(&content, "#000000", func(submatches ...string) string {
+ return "var(--spice-sidebar)"
+ })
- utils.Replace(&content, "white;", " var(--spice-text);")
- utils.Replace(&content, "#fff", "var(--spice-text)")
- utils.Replace(&content, "#ffffff", "var(--spice-text)")
- utils.Replace(&content, "#f8f8f8", " var(--spice-text)")
+ utils.Replace(&content, "white;", func(submatches ...string) string {
+ return " var(--spice-text);"
+ })
+ utils.Replace(&content, "#fff", func(submatches ...string) string {
+ return "var(--spice-text)"
+ })
+ utils.Replace(&content, "#ffffff", func(submatches ...string) string {
+ return "var(--spice-text)"
+ })
+ utils.Replace(&content, "#f8f8f8", func(submatches ...string) string {
+ return "var(--spice-text)"
+ })
- utils.Replace(&content, "#b3b3b3", "var(--spice-subtext)")
- utils.Replace(&content, "#a7a7a7", "var(--spice-subtext)")
+ utils.Replace(&content, "#b3b3b3", func(submatches ...string) string {
+ return "var(--spice-subtext)"
+ })
+ utils.Replace(&content, "#a7a7a7", func(submatches ...string) string {
+ return "var(--spice-subtext)"
+ })
- utils.Replace(&content, "#1db954", "var(--spice-button)")
- utils.Replace(&content, "#1877f2", "var(--spice-button)")
+ utils.Replace(&content, "#1db954", func(submatches ...string) string {
+ return "var(--spice-button)"
+ })
+ utils.Replace(&content, "#1877f2", func(submatches ...string) string {
+ return "var(--spice-button)"
+ })
- utils.Replace(&content, "#1ed760", "var(--spice-button-active)")
- utils.Replace(&content, "#1fdf64", "var(--spice-button-active)")
- utils.Replace(&content, "#169c46", "var(--spice-button-active)")
+ utils.Replace(&content, "#1ed760", func(submatches ...string) string {
+ return "var(--spice-button-active)"
+ })
+ utils.Replace(&content, "#1fdf64", func(submatches ...string) string {
+ return "var(--spice-button-active)"
+ })
+ utils.Replace(&content, "#169c46", func(submatches ...string) string {
+ return "var(--spice-button-active)"
+ })
- utils.Replace(&content, "#535353", "var(--spice-button-disabled)")
+ utils.Replace(&content, "#535353", func(submatches ...string) string {
+ return "var(--spice-button-disabled)"
+ })
- utils.Replace(&content, "#333", "var(--spice-tab-active)")
- utils.Replace(&content, "#333333", "var(--spice-tab-active)")
+ utils.Replace(&content, "#333", func(submatches ...string) string {
+ return "var(--spice-tab-active)"
+ })
+ utils.Replace(&content, "#333333", func(submatches ...string) string {
+ return "var(--spice-tab-active)"
+ })
- utils.Replace(&content, "#7f7f7f", "var(--spice-misc)")
+ utils.Replace(&content, "#7f7f7f", func(submatches ...string) string {
+ return "var(--spice-misc)"
+ })
- utils.Replace(&content, "#4687d6", "var(--spice-notification)")
- utils.Replace(&content, "#2e77d0", "var(--spice-notification)")
+ utils.Replace(&content, "#4687d6", func(submatches ...string) string {
+ return "var(--spice-notification)"
+ })
+ utils.Replace(&content, "#2e77d0", func(submatches ...string) string {
+ return "var(--spice-notification)"
+ })
- utils.Replace(&content, "#e22134", "var(--spice-notification-error)")
- utils.Replace(&content, "#cd1a2b", "var(--spice-notification-error)")
+ utils.Replace(&content, "#e22134", func(submatches ...string) string {
+ return "var(--spice-notification-error)"
+ })
+ utils.Replace(&content, "#cd1a2b", func(submatches ...string) string {
+ return "var(--spice-notification-error)"
+ })
- utils.Replace(&content, `rgba\(18,18,18,([\d\.]+)\)`, "rgba(var(--spice-rgb-main),${1})")
- utils.Replace(&content, `rgba\(40,40,40,([\d\.]+)\)`, "rgba(var(--spice-rgb-card),${1})")
- utils.Replace(&content, `rgba\(0,0,0,([\d\.]+)\)`, "rgba(var(--spice-rgb-shadow),${1})")
- utils.Replace(&content, `hsla\(0,0%,100%,\.9\)`, "rgba(var(--spice-rgb-text),.9)")
- utils.Replace(&content, `hsla\(0,0%,100%,([\d\.]+)\)`, "rgba(var(--spice-rgb-selected-row),${1})")
+ utils.Replace(&content, `rgba\(18,18,18,([\d\.]+)\)`, func(submatches ...string) string {
+ return fmt.Sprintf("rgba(var(--spice-main),%s)", submatches[1])
+ })
+ utils.Replace(&content, `rgba\(40,40,40,([\d\.]+)\)`, func(submatches ...string) string {
+ return fmt.Sprintf("rgba(var(--spice-card),%s)", submatches[1])
+ })
+ utils.Replace(&content, `rgba\(0,0,0,([\d\.]+)\)`, func(submatches ...string) string {
+ return fmt.Sprintf("rgba(var(--spice-rgb-shadow),%s)", submatches[1])
+ })
+ utils.Replace(&content, `hsla\(0,0%,100%,\.9\)`, func(submatches ...string) string {
+ return "rgba(var(--spice-rgb-text),.9)"
+ })
+ utils.Replace(&content, `hsla\(0,0%,100%,([\d\.]+)\)`, func(submatches ...string) string {
+ return fmt.Sprintf("rgba(var(--spice-rgb-selected-row),%s)", submatches[1])
+ })
return content
}
func colorVariableReplaceForJS(content string) string {
- utils.Replace(&content, `"#1db954"`, ` getComputedStyle(document.body).getPropertyValue("--spice-button").trim()`)
- utils.Replace(&content, `"#b3b3b3"`, ` getComputedStyle(document.body).getPropertyValue("--spice-subtext").trim()`)
- utils.Replace(&content, `"#ffffff"`, ` getComputedStyle(document.body).getPropertyValue("--spice-text").trim()`)
- utils.Replace(&content, `color:"white"`, `color:"var(--spice-text)"`)
+ utils.Replace(&content, `"#1db954"`, func(submatches ...string) string {
+ return ` getComputedStyle(document.body).getPropertyValue("--spice-button").trim()`
+ })
+ utils.Replace(&content, `"#b3b3b3"`, func(submatches ...string) string {
+ return ` getComputedStyle(document.body).getPropertyValue("--spice-subtext").trim()`
+ })
+ utils.Replace(&content, `"#ffffff"`, func(submatches ...string) string {
+ return ` getComputedStyle(document.body).getPropertyValue("--spice-text").trim()`
+ })
+ utils.Replace(&content, `color:"white"`, func(submatches ...string) string {
+ return `color:"var(--spice-text)"`
+ })
return content
}
func disableSentry(input string) string {
- utils.Replace(&input, `(?:prototype\.)?bindClient(?:=function)?\(\w+\)\{`, "${0}return;")
+ utils.Replace(&input, `(?:prototype\.)?bindClient(?:=function)?\(\w+\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
return input
}
func disableLogging(input string) string {
- utils.Replace(&input, `sp://logging/v3/\w+`, "")
- utils.Replace(&input, `[^"\/]+\/[^"\/]+\/(public\/)?v3\/events`, "")
-
- utils.Replace(&input, `key:"registerEventListeners",value:function\(\)\{`, "${0}return;")
- utils.Replace(&input, `key:"logInteraction",value:function\([\w,]+\)\{`, "${0}return{interactionId:null,pageInstanceId:null};")
- utils.Replace(&input, `key:"logNonAuthInteraction",value:function\([\w,]+\)\{`, "${0}return{interactionId:null,pageInstanceId:null};")
- utils.Replace(&input, `key:"logImpression",value:function\([\w,]+\)\{`, "${0}return;")
- utils.Replace(&input, `key:"logNonAuthImpression",value:function\([\w,]+\)\{`, "${0}return;")
- utils.Replace(&input, `key:"logNavigation",value:function\([\w,]+\)\{`, "${0}return;")
- utils.Replace(&input, `key:"handleBackgroundStates",value:function\(\)\{`, "${0}return;")
- utils.Replace(&input, `key:"createLoggingParams",value:function\([\w,]+\)\{`, "${0}return;")
- utils.Replace(&input, `key:"initSendingEvents",value:function\(\)\{`, "${0}return;")
- utils.Replace(&input, `key:"flush",value:function\(\)\{`, "${0}return;")
- utils.Replace(&input, `(\{key:"send",value:function\([\w,]+\))\{[\d\w\s,{}()[\]\.,!\?=>&|;:_""]+?\}(\},\{key:"hasContext")`, "${1}{return;}${2}")
- utils.Replace(&input, `key:"lastFlush",value:function\(\)\{`, "${0}return;")
-
- utils.Replace(&input, `(\}registerEventListeners\(\))\{.+?\}(unregisterEventListeners)`, "${1}{return;}${2}")
- utils.Replace(&input, `(\}logInteraction\([\w,]+\))\{.+?\}(logImpression)`, "${1}{return{interactionId:null,pageInstanceId:null};}${2}")
- utils.Replace(&input, `(\}logImpression\([\w,]+\))\{.+?\}(logNavigation)`, "${1}{return;}${2}")
- utils.Replace(&input, `(\}logNavigation\([\w,]+\))\{.+?\}(getPageInstanceId|getInteractionId)`, "${1}{return;}${2}")
- utils.Replace(&input, `(\}handleBackgroundStates\(\))\{.+?\}(startNavigation)`, "${1}{return;}${2}")
- utils.Replace(&input, `(\}createLoggingParams\([\w,]+\))\{.+?\}(async pullToLocal)`, "${1}{return;}${2}")
- utils.Replace(&input, `(\}initSendingEvents\(\))\{.+?\}(initializeContexts)`, "${1}{return;}${2}")
- utils.Replace(&input, `(\}flush\(\))\{.+\}(sendEvents)`, "${1}{return;}${2}")
- utils.Replace(&input, `(\}flush\(\w+=!0\))\{.+\}(flushAll)`, "${1}{return;}${2}")
- utils.Replace(&input, `(\}send\([\w,:=!\d{}]+\))\{.+?\}(hasContext)`, "${1}{return;}${2}")
- utils.Replace(&input, `(\}lastFlush\(\))\{.+?\}(flush\(\))`, "${1}{return;}${2}")
+ utils.Replace(&input, `sp://logging/v3/\w+`, func(submatches ...string) string {
+ return ""
+ })
+ utils.Replace(&input, `[^"\/]+\/[^"\/]+\/(public\/)?v3\/events`, func(submatches ...string) string {
+ return ""
+ })
+
+ utils.Replace(&input, `key:"registerEventListeners",value:function\(\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `key:"logInteraction",value:function\([\w,]+\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn{interactionId:null,pageInstanceId:null};", submatches[0])
+ })
+ utils.Replace(&input, `key:"logNonAuthInteraction",value:function\([\w,]+\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn{interactionId:null,pageInstanceId:null};", submatches[0])
+ })
+ utils.Replace(&input, `key:"logImpression",value:function\([\w,]+\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `key:"logNonAuthImpression",value:function\([\w,]+\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `key:"logNavigation",value:function\([\w,]+\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `key:"handleBackgroundStates",value:function\(\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `key:"createLoggingParams",value:function\([\w,]+\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `key:"initSendingEvents",value:function\(\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `key:"flush",value:function\(\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `(\{key:"send",value:function\([\w,]+\))\{[\d\w\s,{}()[\]\.,!\?=>&|;:_""]+?\}(\},\{key:"hasContext")`, func(submatches ...string) string {
+ return fmt.Sprintf("%s{return;}%s", submatches[1], submatches[2])
+ })
+ utils.Replace(&input, `key:"lastFlush",value:function\(\)\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `key:"addItemInEventsStorage",value:function\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+
+ utils.Replace(&input, `registerEventListeners\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `logInteraction\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn {interactionId:null,pageInstanceId:null};", submatches[0])
+ })
+ utils.Replace(&input, `logImpression\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `logNavigation\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `handleBackgroundStates\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `initSendingEvents\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `sendEvents\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `storeEvent\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `lastFlush\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
+ utils.Replace(&input, `addItemInEventsStorage\([^)]*\)\s*\{`, func(submatches ...string) string {
+ return fmt.Sprintf("%sreturn;", submatches[0])
+ })
return input
}
func removeRTL(input string) string {
- //utils.Replace(&input, `}\[dir=ltr\]\s?`, "} ")
- utils.Replace(&input, `html\[dir=ltr\]`, "html")
- utils.Replace(&input, `,\s?\[dir=rtl\].+?(\{.+?\})`, "$1")
- utils.Replace(&input, `[\w\-\.]+\[dir=rtl\].+?\{.+?\}`, "")
-
- utils.Replace(&input, `\}\[lang=ar\].+?\{.+?\}`, "}")
- utils.Replace(&input, `\}\[dir=rtl\].+?\{.+?\}`, "}")
- utils.Replace(&input, `\}html\[dir=rtl\].+?\{.+?\}`, "}")
- utils.Replace(&input, `\}html\[lang=ar\].+?\{.+?\}`, "}")
-
- utils.Replace(&input, `\[lang=ar\].+?\{.+?\}`, "")
- utils.Replace(&input, `html\[dir=rtl\].+?\{.+?\}`, "")
- utils.Replace(&input, `html\[lang=ar\].+?\{.+?\}`, "")
- utils.Replace(&input, `\[dir=rtl\].+?\{.+?\}`, "")
+ utils.Replace(&input, `}\[dir=ltr\]\s?`, func(submatches ...string) string {
+ return "} "
+ })
+ utils.Replace(&input, `html\[dir=ltr\]`, func(submatches ...string) string {
+ return "html"
+ })
+ utils.Replace(&input, `,\s?\[dir=rtl\].+?(\{.+?\})`, func(submatches ...string) string {
+ return submatches[1]
+ })
+ utils.Replace(&input, `[\w\-\.]+\[dir=rtl\].+?\{.+?\}`, func(submatches ...string) string {
+ return ""
+ })
+ utils.Replace(&input, `\}\[lang=ar\].+?\{.+?\}`, func(submatches ...string) string {
+ return "}"
+ })
+ utils.Replace(&input, `\}\[dir=rtl\].+?\{.+?\}`, func(submatches ...string) string {
+ return "}"
+ })
+ utils.Replace(&input, `\}html\[dir=rtl\].+?\{.+?\}`, func(submatches ...string) string {
+ return "}"
+ })
+ utils.Replace(&input, `\}html\[lang=ar\].+?\{.+?\}`, func(submatches ...string) string {
+ return "}"
+ })
+ utils.Replace(&input, `\[lang=ar\].+?\{.+?\}`, func(submatches ...string) string {
+ return ""
+ })
+ utils.Replace(&input, `html\[dir=rtl\].+?\{.+?\}`, func(submatches ...string) string {
+ return ""
+ })
+ utils.Replace(&input, `html\[lang=ar\].+?\{.+?\}`, func(submatches ...string) string {
+ return ""
+ })
+ utils.Replace(&input, `\[dir=rtl\].+?\{.+?\}`, func(submatches ...string) string {
+ return ""
+ })
return input
}
@@ -290,80 +440,93 @@ func exposeAPIs_main(input string) string {
utils.Replace(
&input,
`(?:\w+ |,)([\w$]+)=(\([\w$]+=[\w$]+\.dispatch)`,
- `;globalThis.Spicetify.showNotification=(message,isError=false,msTimeout)=>${1}({message,feedbackType:isError?"ERROR":"NOTICE",msTimeout});const ${1}=${2}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf(`;globalThis.Spicetify.showNotification=(message,isError=false,msTimeout)=>%s({message,feedbackType:isError?"ERROR":"NOTICE",msTimeout});const %s=%s`, submatches[1], submatches[1], submatches[2])
+ })
// Remove list of exclusive shows
utils.Replace(
&input,
`\["spotify:show.+?\]`,
- `[]`)
+ func(submatches ...string) string {
+ return "[]"
+ })
// Remove Star Wars easter eggs since it aggressively
// listens to keystroke, checking URIs at all time
+ // TODO: to fix
utils.Replace(
&input,
`\w+\(\)\.createElement\(\w+,\{onChange:this\.handleSaberStateChange\}\),`,
- "")
+ func(submatches ...string) string {
+ return ""
+ })
utils.Replace(
&input,
`"data-testid":`,
- `"":`)
+ func(submatches ...string) string {
+ return `"":`
+ })
// Spicetify._platform
utils.Replace(
&input,
`(setTitlebarHeight[\w(){}.,&$!=;"" ]+)(\{version:[\w$]+,)`,
- `${1}Spicetify._platform=${2}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("%sSpicetify._platform=%s", submatches[1], submatches[2])
+ })
// Redux store
utils.Replace(
&input,
`(,[\w$]+=)(([$\w,.:=;(){}]+\(\{session:[\w$]+,features:[\w$]+,seoExperiment:[\w$]+\}))`,
- `${1}Spicetify.Platform.ReduxStore=${2}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("%sSpicetify.Platform.ReduxStore=%s", submatches[1], submatches[2])
+ })
// React Component: Platform Provider
utils.Replace(
&input,
`(,[$\w]+=)((function\([\w$]{1}\)\{var [\w$]+=[\w$]+\.platform,[\w$]+=[\w$]+\.children,)|(\(\{platform:[\w$]+,children:[\w$]+\}\)=>\{))`,
- `${1}Spicetify.ReactComponent.PlatformProvider=${2}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("%sSpicetify.ReactComponent.PlatformProvider=%s", submatches[1], submatches[2])
+ })
// Prevent breaking popupLyrics
utils.Replace(
&input,
`document.pictureInPictureElement&&\(\w+.current=[!\w]+,document\.exitPictureInPicture\(\)\),\w+\.current=null`,
- ``)
+ func(submatches ...string) string {
+ return ""
+ })
// GraphQL definitions
utils.Replace(
&input,
`((?:\w+ ?)?[\w$]+=)(\{kind:"Document",definitions:\[\{(?:\w+:[\w"]+,)+name:\{(?:\w+:[\w"]+,?)+value:("\w+"))`,
- `${1}Spicetify.GraphQL.Definitions[${3}]=${2}`)
-
- // Panel API patch
- utils.Replace(
- &input,
- `(switch\(([\w$])\)\{case [\w$.]+BuddyFeed:return [\w$.]+BuddyFeed;(?:case [\w$.]+:return [\w$.]+;)*)default:`,
- `${1}default:return Spicetify.Panel?.hasPanel?.(${2},true)?${2}:0;`)
-
- // Panel component patch
- utils.Replace(
- &input,
- `case [\w$.]+BuddyFeed:(?:return ?|[\w$]+=)[\w$?]*(?:\([\w$.,]+\)\([\w(){},.:]+)?;(?:break;)?(?:case [\w$.]+:(?:return ?|[\w$]+=)[\w$?]*(?:\([\w$.,]+\)\([\w(){},.:]+)?[\w:]*;(?:break;)?)*default:(?:return ?|[\w$]+=)`,
- `${0} Spicetify.Panel?.render()??`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("%sSpicetify.GraphQL.Definitions[%s]=%s", submatches[1], submatches[3], submatches[2])
+ })
utils.Replace(
&input,
`\b\w\s*\(\)\s*[^;,]*enqueueCustomSnackbar:\s*(\w)\s*[^;]*;`,
- `${0}Spicetify.Snackbar.enqueueCustomSnackbar=${1};`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("%sSpicetify.Snackbar.enqueueCustomSnackbar=%s;", submatches[0], submatches[1])
+ })
utils.Replace(
&input,
`\(\({[^}]*,\s*imageSrc`,
- `Spicetify.Snackbar.enqueueImageSnackbar=${0}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("Spicetify.Snackbar.enqueueImageSnackbar=%s", submatches[0])
+ })
// Menu hook
- utils.Replace(&input, `("Menu".+?children:)([\w$][\w$\d]*)`, `${1}[Spicetify.ContextMenuV2.renderItems(),${2}].flat()`)
+ utils.Replace(&input, `("Menu".+?children:)([\w$][\w$\d]*)`, func(submatches ...string) string {
+ return fmt.Sprintf("%s[Spicetify.ContextMenuV2.renderItems(),%s].flat()", submatches[1], submatches[2])
+ })
croppedInput := utils.FindFirstMatch(input, `"context-menu".*value:"contextmenu"`)[0]
@@ -384,7 +547,9 @@ func exposeAPIs_main(input string) string {
target = utils.FindFirstMatch(croppedInput, `triggerRef:([\w_$]+)`)[1]
}
- utils.Replace(&input, `\(0,([\w_$]+)\.jsx\)\([\w_$]+\.[\w_$]+,\{value:"contextmenu"[^\}]+\}\)\}\)`, `(0,${1}.jsx)((Spicetify.ContextMenuV2._context||(Spicetify.ContextMenuV2._context=`+react+`.createContext(null))).Provider,{value:{props:`+menu+`?.props,trigger:`+trigger+`,target:`+target+`},children:${0}})`)
+ utils.Replace(&input, `\(0,([\w_$]+)\.jsx\)\([\w_$]+\.[\w_$]+,\{value:"contextmenu"[^\}]+\}\)\}\)`, func(submatches ...string) string {
+ return fmt.Sprintf("(0,%s.jsx)((Spicetify.ContextMenuV2._context||(Spicetify.ContextMenuV2._context=%s.createContext(null))).Provider,{value:{props:%s?.props,trigger:%s,target:%s},children:%s})", submatches[1], react, menu, trigger, target, submatches[0])
+ })
return input
}
@@ -394,7 +559,9 @@ func exposeAPIs_vendor(input string) string {
utils.Replace(
&input,
`,(\w+)\.prototype\.toAppType`,
- `,(globalThis.Spicetify.URI=${1})${0}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf(`,(globalThis.Spicetify.URI=%s)%s`, submatches[1], submatches[0])
+ })
// URI after 1.2.4
if !strings.Contains(input, "Spicetify.URI") {
@@ -409,13 +576,13 @@ func exposeAPIs_vendor(input string) string {
if URIObj[1] == "" {
URIObj[1] = URIObj[2]
// Class is a self-invoking function
- URI = URI + "()"
+ URI = fmt.Sprintf("%s()", URI)
}
input = strings.Replace(
input,
URI,
- URI+";Spicetify.URI="+URIObj[1]+";",
+ fmt.Sprintf("%s;Spicetify.URI=%s;", URI, URIObj[1]),
1)
}
}
@@ -423,61 +590,52 @@ func exposeAPIs_vendor(input string) string {
utils.ReplaceOnce(
&input,
`\(function\(\w+\)\{return \w+\.variant\?function\(\w+\)\{`,
- `Spicetify._fontStyle=${0}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("Spicetify._fontStyle=%s", submatches[0])
+ })
utils.ReplaceOnce(
&input,
`=(?:\(\w\)=>|function\(\w\)\{)\w+ ?\w=\w\.iconSize`,
- `=Spicetify.ReactComponent.IconComponent${0}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("=Spicetify.ReactComponent.IconComponent%s", submatches[0])
+ })
// Mapping styled-components classes
utils.Replace(
&input,
`(\w+ [\w$_]+)=[\w$_]+\([\w$_]+>>>0\)`,
- `${1}=Spicetify._getStyledClassName(arguments,this)`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("%s=Spicetify._getStyledClassName(arguments,this)", submatches[1])
+ })
// Tippy
utils.Replace(
&input,
- `([\w$_]+)\.setDefaultProps=`,
- `Spicetify.Tippy=${1};${0}`)
+ `([\w\$_]+)\.setDefaultProps=`,
+ func(submatches ...string) string {
+ return fmt.Sprintf("Spicetify.Tippy=%s;%s", submatches[1], submatches[0])
+ })
// Flipper components
utils.Replace(
&input,
`([\w$]+)=((?:function|\()([\w$.,{}()= ]+(?:springConfig|overshootClamping)){2})`,
- `${1}=Spicetify.ReactFlipToolkit.spring=${2}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("%s=Spicetify.ReactFlipToolkit.spring=%s", submatches[1], submatches[2])
+ })
// Snackbar https://github.com/iamhosseindhv/notistack
utils.Replace(
&input,
`\w+\s*=\s*\w\.call\(this,[^)]+\)\s*\|\|\s*this\)\.enqueueSnackbar`,
- `Spicetify.Snackbar=${0}`)
+ func(submatches ...string) string {
+ return fmt.Sprintf("Spicetify.Snackbar=%s", submatches[0])
+ })
return input
}
-// Disable WebUI by redirect to zlink app
-// so when Spotify forces user to use xpui, it loads zlink instead.
-func fakeZLink(dest string) {
- os.MkdirAll(dest, 0700)
- entryFile := filepath.Join(dest, "index.html")
- html := `
-
-`
- manifestFile := filepath.Join(dest, "manifest.json")
- manifest := `
-{
- "BundleIdentifier": "zlink",
- "BundleType": "Application"
-}
-`
- os.WriteFile(entryFile, []byte(html), 0700)
- os.WriteFile(manifestFile, []byte(manifest), 0700)
-}
-
type githubRelease = utils.GithubRelease
func splitVersion(version string) ([3]int, error) {
diff --git a/src/utils/utils.go b/src/utils/utils.go
index 17b3779b3f..2298b7385b 100644
--- a/src/utils/utils.go
+++ b/src/utils/utils.go
@@ -164,18 +164,25 @@ func CopyFile(srcPath, dest string) error {
// Replace uses Regexp to find any matched from `input` with `regexpTerm`
// and replaces them with `replaceTerm` then returns new string.
-func Replace(input *string, regexpTerm string, replaceTerm string) {
- re := regexp.MustCompile(regexpTerm)
- *input = re.ReplaceAllString(*input, replaceTerm)
+func Replace(str *string, pattern string, repl func(submatches ...string) string) {
+ re := regexp.MustCompile(pattern)
+ *str = re.ReplaceAllStringFunc(*str, func(match string) string {
+ submatches := re.FindStringSubmatch(match)
+ return repl(submatches...)
+ })
}
-func ReplaceOnce(input *string, regexpTerm string, replaceTerm string) {
- re := regexp.MustCompile(regexpTerm)
- matches := re.FindAllString(*input, -1)
- if len(matches) > 0 {
- toReplace := re.ReplaceAllString(matches[0], replaceTerm)
- *input = strings.Replace(*input, matches[0], toReplace, 1)
- }
+func ReplaceOnce(str *string, pattern string, repl func(submatches ...string) string) {
+ re := regexp.MustCompile(pattern)
+ firstMatch := true
+ *str = re.ReplaceAllStringFunc(*str, func(match string) string {
+ if firstMatch {
+ firstMatch = false
+ submatches := re.FindStringSubmatch(match)
+ return repl(submatches...)
+ }
+ return match
+ })
}
func FindMatch(input string, regexpTerm string) [][]string {