From bfa331c6249780b9da2275674302a050a94fbc64 Mon Sep 17 00:00:00 2001 From: Thomas Fitzpatrick <22730962+ohitstom@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:05:38 +0100 Subject: [PATCH 001/146] feat(css-map): add selectors for `1.2.40` playbar (#3072) --- css-map.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/css-map.json b/css-map.json index 244085e0da..1e87b606ea 100644 --- a/css-map.json +++ b/css-map.json @@ -1075,6 +1075,8 @@ "N5cWYDvyLrfnyMZuqQHo": "npv-nowPlayingBar-left", "FTi9QEhetf4Q4__5sb4S": "npv-nowPlayingBar-right", "SVGHXIQcH9HYU7uGITw5": "npv-nowPlayingBar-section", + "pn5V0OzovI9p6b8nWq8p": "playback-bar", + "B1vgcMXBqOxgMxXh5j1f": "playback-progressbar-container", "p1ULRzPc4bD8eQ4T_wyp": "playback-progressbar", "DFtdzavKSbEhwKYkPTa6": "playback-progressbar-isInteractive", "JzyZE2R09wq7xtjECDeR": "playlist-inlineSearchBox-clearButton", From bd2cc985a9b4e03a2b29729b2352e076c4d1720d Mon Sep 17 00:00:00 2001 From: Ava <98136801+duckysilicon@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:14:58 +0700 Subject: [PATCH 002/146] chore(cmd): correct minor grammar mistake (#3073) --- src/cmd/cmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/cmd.go b/src/cmd/cmd.go index 4ac5eb4564..7256806f76 100644 --- a/src/cmd/cmd.go +++ b/src/cmd/cmd.go @@ -281,6 +281,6 @@ func CheckUpdate(version string) { utils.PrintInfo("Spicetify up-to-date") } else { utils.PrintWarning("New version available: v" + latestTag + " (currently on: v" + version + ")") - utils.PrintWarning(`Run "spicetify update" or using package manager to update spicetify`) + utils.PrintWarning(`Run "spicetify update" or use a package manager to update spicetify`) } } From da1df2c59456accf58dd7f536354e2bb381f785c Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 19 Jun 2024 00:17:36 +0200 Subject: [PATCH 003/146] ci: add attestations for the release builds (#3074) --- .github/workflows/build.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36b2719d87..3a58726362 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,6 +25,10 @@ jobs: if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi release: + permissions: + id-token: write + contents: write + attestations: write name: Release strategy: matrix: @@ -62,6 +66,13 @@ jobs: GOARCH: ${{ matrix.arch }} CGO_ENABLED: 0 + - name: Attest output + uses: actions/attest-build-provenance@v1 + if: env.IS_UNIX == 'true' || env.IS_WIN == 'true' + with: + subject-path: "./spicetify${{ matrix.os == 'windows' && '.exe' || '' }}" + subject-name: "spicetify v${{ env.TAG }} (${{ matrix.os }}, ${{ (matrix.os == 'windows' && matrix.arch == 'amd64' && 'x64') || (matrix.os == 'windows' && matrix.arch == '386' && 'x32') || matrix.arch }})" + - name: 7z - .tar if: env.IS_UNIX == 'true' uses: edgarrc/action-7z@v1 From ef9f72d648c37fb7cb54a85a8e78492b896d5f42 Mon Sep 17 00:00:00 2001 From: darkthemer <111078063+darkthemer@users.noreply.github.com> Date: Sat, 22 Jun 2024 22:36:18 +0800 Subject: [PATCH 004/146] feat(css-map): add more 1.2.40 classes (#3079) --- css-map.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/css-map.json b/css-map.json index 1e87b606ea..8fa68c6d14 100644 --- a/css-map.json +++ b/css-map.json @@ -130,19 +130,30 @@ "VUXMMFKWudUWE1kIXZoS": "link-subtle", "iKgf4UDhbRTHxmZSuAEc": "lyrics-lyrics-adLeaderboardIsEnabled", "L9xhJOJnV2OL5Chm3Jew": "lyrics-lyrics-background", + "o4GE4jG5_QICak2JK_bn": "lyrics-lyrics-background", "tr8V5eHsUaIkOYVw7eSG": "lyrics-lyrics-container", + "FUYNhisXTCmbzt9IDxnT": "lyrics-lyrics-container", "Q2RPoHcoxygOoPLXLMww": "lyrics-lyrics-contentContainer", + "gqaWFmQeKNYnYD5gRv3x": "lyrics-lyrics-contentContainer", "esRByMgBY3TiENAsbDHA": "lyrics-lyrics-contentWrapper", + "_Wna90no0o0dta47Heiw": "lyrics-lyrics-contentWrapper", "iPBJpp5EVkRE24N6vmGA": "lyrics-lyrics-coverTopBar", "SaEkeiyzAoXnWVSDiTR7": "lyrics-lyrics-vocalRemoval", "arY01KDGhWNgzlAHlhpd": "lyrics-lyricsContent-active", + "EhKgYshvOwpSrTv399Mw": "lyrics-lyricsContent-active", "iq4cgi0YEKr6DGaTtzUj": "lyrics-lyricsContent-description", "MEjuIn9iTBQbnCqHpkoQ": "lyrics-lyricsContent-highlight", + "aeO5D7ulxy19q4qNBrkk": "lyrics-lyricsContent-highlight", "_LKG3z7SnerR0eigPCoK": "lyrics-lyricsContent-isInteractive", + "vapgYYF2HMEeLJuOWGq5": "lyrics-lyricsContent-isInteractive", "NiCdLCpp3o2z6nBrayOn": "lyrics-lyricsContent-lyric", + "nw6rbs8R08fpPn7RWW2w": "lyrics-lyricsContent-lyric", "kGR_hu4tdj9PnUlSPaRL": "lyrics-lyricsContent-provider", + "LomBcMvfM8AEmZGquAdj": "lyrics-lyricsContent-provider", "A3ohAQNHsDIMv2EM3Ytp": "lyrics-lyricsContent-text", + "BXlQFspJp_jq9SKhUSP3": "lyrics-lyricsContent-text", "HxblHEsl2WX2yhubfVIc": "lyrics-lyricsContent-unsynced", + "SruqsAzX8rUtY2isUZDF": "lyrics-lyricsContent-unsynced", "E4q8ogfdWtye7YgotBlN": "main-actionBar-ActionBar", "eSg4ntPU2KQLfpLGXAww": "main-actionBar-ActionBarRow", "CoLO4pdSl8LGWyVZA00t": "main-actionBarBackground-background", @@ -934,6 +945,7 @@ "dZPmmYYhskhqHJCAruvI": "main-trackList-trackListHeaderRow", "ePPpO_NuGDUxVRTw7y6W": "main-trackList-trackListHeaderRow", "qJOhHoRcFhHJpEQ2CwFT": "main-trackList-trackListHeaderStuck", + "_2ajKWDiy6YvJu5wo8I1g": "main-trackList-trackListHeaderStuck", "h4HgbO_Uu1JYg5UGANeQ": "main-trackList-trackListRow", "IjYxRc5luMiDPhKhZVUH": "main-trackList-trackListRow", "wTUruPetkKdWAR1dd6w4": "main-trackList-trackListRowGrid", @@ -1076,6 +1088,7 @@ "FTi9QEhetf4Q4__5sb4S": "npv-nowPlayingBar-right", "SVGHXIQcH9HYU7uGITw5": "npv-nowPlayingBar-section", "pn5V0OzovI9p6b8nWq8p": "playback-bar", + "IPbBrI6yF4zhaizFmrg6": "playback-bar__progress-time-elapsed", "B1vgcMXBqOxgMxXh5j1f": "playback-progressbar-container", "p1ULRzPc4bD8eQ4T_wyp": "playback-progressbar", "DFtdzavKSbEhwKYkPTa6": "playback-progressbar-isInteractive", From abe53f6058585ddd041380f2728d0ec9b2f54b57 Mon Sep 17 00:00:00 2001 From: Vedant <83997633+vedantmgoyal9@users.noreply.github.com> Date: Sat, 29 Jun 2024 19:08:12 +0530 Subject: [PATCH 005/146] ci(build): update `winget-releaser` to `latest` (#3087) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a58726362..f4f5e9422e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,7 +110,7 @@ jobs: url: https://vps.itsmeow.dev/spicetify-update method: GET - name: Update Winget package - uses: vedantmgoyal2009/winget-releaser@v2 + uses: vedantmgoyal9/winget-releaser@main with: identifier: Spicetify.Spicetify installers-regex: '-windows-\w+\.zip$' From 3207240d6d854b9d50971ebb96590b80e223579a Mon Sep 17 00:00:00 2001 From: em Date: Wed, 3 Jul 2024 22:41:21 +0200 Subject: [PATCH 006/146] fix: adapt `Cosmos` and `ConfirmDialog` for `1.2.41` (#3089) --- jsHelper/spicetifyWrapper.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 13808aba2b..1a83b2cbe1 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -310,11 +310,13 @@ window.Spicetify = { }; (function addProxyCosmos() { - if (!Spicetify.Player.origin?._cosmos) { + if (!Spicetify.Player.origin?._cosmos && !Spicetify.Platform?.Registry) { setTimeout(addProxyCosmos, 50); return; } + const _cosmos = Spicetify.Player.origin?._cosmos ?? Spicetify.Platform?.Registry._map.get(Symbol.for("Cosmos")).instance; + const corsProxyURL = "https://cors-proxy.spicetify.app"; const allowedMethodsMap = { get: "get", @@ -394,7 +396,7 @@ window.Spicetify = { } }; - Spicetify.Player.origin._cosmos = new Proxy(Spicetify.Player.origin._cosmos, handler); + Spicetify.Player.origin._cosmos = new Proxy(_cosmos, handler); Object.defineProperty(Spicetify, "CosmosAsync", { get: () => { return Spicetify.Player.origin?._cosmos; @@ -697,6 +699,13 @@ window.Spicetify = { } }); + const confirmDialogChunk = chunks.find( + ([, value]) => value.toString().includes("confirmDialog") && value.toString().includes("shouldCloseOnEsc") && value.toString().includes("isOpen") + ); + if (!Spicetify.ReactComponent?.ConfirmDialog && confirmDialogChunk) { + Spicetify.ReactComponent.ConfirmDialog = Object.values(require(confirmDialogChunk[0])).find(m => typeof m === "object"); + } + const contextMenuChunk = chunks.find(([, value]) => value.toString().includes("toggleContextMenu")); if (contextMenuChunk) { Spicetify.ReactComponent.ContextMenu = Object.values(require(contextMenuChunk[0])).find(m => typeof m === "function"); From 0771018f9e757b37a144106c26fdcaea898c524e Mon Sep 17 00:00:00 2001 From: em Date: Thu, 4 Jul 2024 20:01:05 +0200 Subject: [PATCH 007/146] feat(css-map): map `GenericModal` for `1.2.41` (#3092) --- css-map.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/css-map.json b/css-map.json index 8fa68c6d14..a333e39ec0 100644 --- a/css-map.json +++ b/css-map.json @@ -126,6 +126,8 @@ "WomzHWnDO_yFyjnkd49P": "desktopmodals-versionStatus-content", "R83hOohwVshnd6bEkDO4": "desktopmodals-versionStatus-copyButton", "YJMECPbMHWgMUs8RFdcV": "folder-folderPage-sectionWrapper", + "zogFp9G1AEqb8AKOd5B0": "GenericModal", + "I3zkdnuhFFrZ1Rr1BJhb": "GenericModal__overlay", "cUwQnQoE3OqXqSYLT0hv": "link-subtle", "VUXMMFKWudUWE1kIXZoS": "link-subtle", "iKgf4UDhbRTHxmZSuAEc": "lyrics-lyrics-adLeaderboardIsEnabled", From 7c1f287e7a0b39f9fdb3926a152ff872164f3c2d Mon Sep 17 00:00:00 2001 From: Sanooj Es Date: Sun, 7 Jul 2024 07:55:23 +0530 Subject: [PATCH 008/146] feat(css-map): add player controls (#3094) --- css-map.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css-map.json b/css-map.json index a333e39ec0..3a8a85180c 100644 --- a/css-map.json +++ b/css-map.json @@ -1094,6 +1094,10 @@ "B1vgcMXBqOxgMxXh5j1f": "playback-progressbar-container", "p1ULRzPc4bD8eQ4T_wyp": "playback-progressbar", "DFtdzavKSbEhwKYkPTa6": "playback-progressbar-isInteractive", + "gItY2hnfCB4TsDJCkPiO": "player-controls", + "XrZ1iHVHAPMya3jkB2sa": "player-controls__buttons", + "NKUrT1GciYXAEEUtagN1": "player-controls__left", + "Qt226Z4rBQs53aedRQBQ": "player-controls__right", "JzyZE2R09wq7xtjECDeR": "playlist-inlineSearchBox-clearButton", "FeWwGSRANj36qpOBoxdx": "playlist-inlineSearchBox-filterInput", "YAYCVnYpPvmYV4JyTmn5": "playlist-inlineSearchBox-filterInputContainer", From 7a2fc97d6546e594fb3c24453f369143e86ecf00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:28:59 +0100 Subject: [PATCH 009/146] chore(deps): bump golang.org/x/net from 0.26.0 to 0.27.0 (#3098) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 55fe1034ff..5bcbc1d808 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.13 - golang.org/x/net v0.26.0 + golang.org/x/net v0.27.0 ) require ( github.com/mattn/go-isatty v0.0.16 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index 609acbccbf..ba201ee399 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 757b5402b113df97543aa07dd6aaefed0b126756 Mon Sep 17 00:00:00 2001 From: em Date: Sat, 13 Jul 2024 18:53:49 +0200 Subject: [PATCH 010/146] ci(build): do not execute `trigger-release` if tag is `v3.x.x` --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4f5e9422e..2f6933da73 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,6 +102,7 @@ jobs: trigger-release: name: Trigger Homebrew/AUR Release runs-on: ubuntu-latest + if: ${{ !startsWith(github.ref, 'refs/tags/v3') }} needs: release steps: - name: Update AUR package From 3356b8565707c1320dcd4f70f399284134d84836 Mon Sep 17 00:00:00 2001 From: em Date: Sat, 13 Jul 2024 21:20:23 +0200 Subject: [PATCH 011/146] ci: run on `main` and `*/main/**` (#3106) --- .github/workflows/build.yml | 11 +++++++++-- .github/workflows/linter.yml | 10 +++++++++- .github/workflows/lintpr.yml | 5 +---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f6933da73..e57328bcd3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,14 @@ name: Build -on: [pull_request, push] +on: + pull_request: + branches: + - main + - "*/main/**" + push: + branches: + - main + - "*/main/**" jobs: build: @@ -102,7 +110,6 @@ jobs: trigger-release: name: Trigger Homebrew/AUR Release runs-on: ubuntu-latest - if: ${{ !startsWith(github.ref, 'refs/tags/v3') }} needs: release steps: - name: Update AUR package diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 3c13645511..35b39971fc 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,6 +1,14 @@ name: Code quality -on: [push, pull_request] +on: + pull_request: + branches: + - main + - "*/main/**" + push: + branches: + - main + - "*/main/**" jobs: linter: diff --git a/.github/workflows/lintpr.yml b/.github/workflows/lintpr.yml index 41ab79a99a..2f7852cbd4 100644 --- a/.github/workflows/lintpr.yml +++ b/.github/workflows/lintpr.yml @@ -2,10 +2,7 @@ name: Lint Pull Request on: pull_request_target: - types: - - opened - - edited - - synchronize + types: [opened, edited, synchronize] jobs: lintpr: From 372ddedf87cd5ed45779e384ccd1770111565e2e Mon Sep 17 00:00:00 2001 From: em Date: Sun, 14 Jul 2024 23:14:02 +0200 Subject: [PATCH 012/146] fix(wrapper): resolve missing APIs from `Registry` on `1.2.42` (#3107) --- jsHelper/spicetifyWrapper.js | 64 ++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 1a83b2cbe1..7edf69bcf8 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -309,13 +309,55 @@ window.Spicetify = { Platform: {} }; +(function waitForPlatform() { + if (!Spicetify._platform) { + setTimeout(waitForPlatform, 50); + return; + } + const { _platform } = Spicetify; + for (const key of Object.keys(_platform)) { + if (key.startsWith("get") && typeof _platform[key] === "function") { + Spicetify.Platform[key.slice(3)] = _platform[key](); + } else { + Spicetify.Platform[key] = _platform[key]; + } + } + + if (!Spicetify.Platform.Registry) Spicetify.Events.platformLoaded.fire(); +})(); + +(function addMissingPlatformAPIs() { + if (!Spicetify.Platform?.version && !Spicetify.Platform?.Registry) { + setTimeout(addMissingPlatformAPIs, 50); + return; + } + const version = Spicetify.Platform.version.split(".").map(i => Number.parseInt(i)); + if (version[0] === 1 && version[1] === 2 && version[2] < 38) return; + + for (const [key, _] of Spicetify.Platform.Registry._map.entries()) { + if (typeof key?.description !== "string" || !key?.description.endsWith("API")) continue; + const symbolName = key.description; + if (Object.hasOwn(Spicetify.Platform, symbolName)) continue; + const resolvedAPI = Spicetify.Platform.Registry.resolve(key); + if (!resolvedAPI) { + console.warn(`[spicetifyWrapper] Failed to resolve PlatformAPI from Registry: ${symbolName}`); + continue; + } + + Spicetify.Platform[symbolName] = resolvedAPI; + console.debug(`[spicetifyWrapper] Resolved PlatformAPI from Registry: ${symbolName}`); + } + + if (Spicetify.Events.platformLoaded.callbacks.length) Spicetify.Events.platformLoaded.fire(); +})(); + (function addProxyCosmos() { if (!Spicetify.Player.origin?._cosmos && !Spicetify.Platform?.Registry) { setTimeout(addProxyCosmos, 50); return; } - const _cosmos = Spicetify.Player.origin?._cosmos ?? Spicetify.Platform?.Registry._map.get(Symbol.for("Cosmos")).instance; + const _cosmos = Spicetify.Player.origin?._cosmos ?? Spicetify.Platform?.Registry.resolve(Symbol.for("Cosmos")); const corsProxyURL = "https://cors-proxy.spicetify.app"; const allowedMethodsMap = { @@ -332,7 +374,9 @@ window.Spicetify = { 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; + if (typeof internalFetch !== "function" || !allowedMethodsSet.has(prop)) return internalFetch; + const version = Spicetify.Platform.version.split(".").map(i => Number.parseInt(i)); + if (version[0] === 1 && version[1] === 2 && version[2] < 31) return internalFetch; return async function (url, body) { const urlObj = new URL(url); @@ -404,22 +448,6 @@ window.Spicetify = { }); })(); -(function waitForPlatform() { - if (!Spicetify._platform) { - setTimeout(waitForPlatform, 50); - return; - } - const { _platform } = Spicetify; - for (const key of Object.keys(_platform)) { - if (key.startsWith("get") && typeof _platform[key] === "function") { - Spicetify.Platform[key.slice(3)] = _platform[key](); - } else { - Spicetify.Platform[key] = _platform[key]; - } - } - Spicetify.Events.platformLoaded.fire(); -})(); - (function hotloadWebpackModules() { if (!window?.webpackChunkclient_web) { setTimeout(hotloadWebpackModules, 50); From ecf46810d80170dde87b2593f3bb7685d997b67c Mon Sep 17 00:00:00 2001 From: em Date: Sun, 14 Jul 2024 23:21:21 +0200 Subject: [PATCH 013/146] ci: fix `release` step on created release --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e57328bcd3..c85f6ed06f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,6 +9,8 @@ on: branches: - main - "*/main/**" + release: + types: [published] jobs: build: @@ -43,7 +45,7 @@ jobs: os: ["linux", "darwin", "windows"] arch: ["amd64", "arm64", "386"] runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') + if: startsWith(github.ref, 'refs/tags/v2') needs: build steps: From ef624c5dddf5c5d975483854f036cb6e0b0e16a0 Mon Sep 17 00:00:00 2001 From: em Date: Sat, 27 Jul 2024 01:33:47 +0200 Subject: [PATCH 014/146] fix(trashbin): look for different element (#3116) --- Extensions/trashbin.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Extensions/trashbin.js b/Extensions/trashbin.js index ec2478535d..3fea44ac1f 100644 --- a/Extensions/trashbin.js +++ b/Extensions/trashbin.js @@ -5,7 +5,9 @@ /// (function TrashBin() { - const skipBackBtn = document.querySelector(".main-skipBackButton-button"); + const skipBackBtn = + document.querySelector(".main-skipBackButton-button") ?? + document.querySelector(".player-controls__left > button[data-encore-id='buttonTertiary']"); if (!Spicetify.Player.data || !Spicetify.LocalStorage || !skipBackBtn) { setTimeout(TrashBin, 1000); return; From cbf39cfa9d1bfa2517bb10865e3d07e8a4a7576d Mon Sep 17 00:00:00 2001 From: em Date: Fri, 2 Aug 2024 17:41:02 +0200 Subject: [PATCH 015/146] fix(fontStyle): adapt regex & create one for fonts for `1.2.43` (#3121) --- jsHelper/spicetifyWrapper.js | 5 +---- src/preprocess/preprocess.go | 7 ++++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 7edf69bcf8..1fec2f1e45 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -1468,10 +1468,7 @@ Spicetify.SVGIcons = { setTimeout(appendAllFontStyle, 1000); return; } - const fontList = Spicetify._fontStyle - .toString() - .match(/"\w+"/g) - .map(font => font.replaceAll('"', "")); + const fontList = (Spicetify._fontStyleList?.toString() ?? Spicetify._fontStyle.toString()).match(/"\w+"/g).map(font => font.replace(/"/g, "")); const fontStyle = document.createElement("style"); fontStyle.className = "spicetify-font"; for (const font of fontList) { diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 61bd6ff1e7..7ea8081882 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -616,9 +616,14 @@ func exposeAPIs_vendor(input string) string { } } + // for >=1.2.43 + utils.ReplaceOnce(&input, `,(\w+)=(function\(\w+\)\{[^}]*?case"ballad"[^}]*?\})`, func(submatches ...string) string { + return fmt.Sprintf(",%s=Spicetify._fontStyleList=%s", submatches[1], submatches[2]) + }) + utils.ReplaceOnce( &input, - `\(function\(\w+\)\{return \w+\.\$?variant\?function\(\w+\)\{`, + `\(function\(\w+\)\{return \w+\.\$?variant\?(\s*\w+\(\w+\.\$variant\)|function\(\w+\)\{)`, func(submatches ...string) string { return fmt.Sprintf("Spicetify._fontStyle=%s", submatches[0]) }) From f564dfe46388c3ea75a8de407723ab83def66925 Mon Sep 17 00:00:00 2001 From: em Date: Sat, 3 Aug 2024 03:29:18 +0200 Subject: [PATCH 016/146] feat: remove `_fontStyle` and lint the code (#3123) --- CustomApps/lyrics-plus/OptionsMenu.js | 82 +-- CustomApps/lyrics-plus/Pages.js | 144 +++--- CustomApps/lyrics-plus/PlaybarButton.js | 4 +- CustomApps/lyrics-plus/ProviderGenius.js | 8 +- CustomApps/lyrics-plus/ProviderLRCLIB.js | 10 +- CustomApps/lyrics-plus/ProviderMusixmatch.js | 30 +- CustomApps/lyrics-plus/ProviderNetease.js | 36 +- CustomApps/lyrics-plus/Providers.js | 42 +- CustomApps/lyrics-plus/Settings.js | 160 +++--- CustomApps/lyrics-plus/TabBar.js | 30 +- CustomApps/lyrics-plus/Translator.js | 8 +- CustomApps/lyrics-plus/Utils.js | 30 +- CustomApps/lyrics-plus/index.js | 141 +++--- CustomApps/new-releases/Card.js | 62 +-- CustomApps/new-releases/Icons.js | 24 +- CustomApps/new-releases/Settings.js | 84 ++-- CustomApps/new-releases/index.js | 74 +-- CustomApps/reddit/Card.js | 42 +- CustomApps/reddit/Icons.js | 24 +- CustomApps/reddit/OptionsMenu.js | 18 +- CustomApps/reddit/Settings.js | 10 +- CustomApps/reddit/SortBox.js | 20 +- CustomApps/reddit/TabBar.js | 34 +- CustomApps/reddit/index.js | 61 +-- Extensions/autoSkipExplicit.js | 2 +- Extensions/bookmark.js | 57 ++- Extensions/fullAppDisplay.js | 108 ++-- Extensions/keyboardShortcut.js | 16 +- Extensions/loopyLoop.js | 6 +- Extensions/popupLyrics.js | 102 ++-- Extensions/shuffle+.js | 72 +-- Extensions/trashbin.js | 12 +- Extensions/webnowplaying.js | 2 +- biome.json | 6 +- globals.d.ts | 7 - jsHelper/expFeatures.js | 16 +- jsHelper/homeConfig.js | 23 +- jsHelper/sidebarConfig.js | 24 +- jsHelper/spicetifyWrapper.js | 499 +++++++++---------- src/preprocess/preprocess.go | 12 - 40 files changed, 1049 insertions(+), 1093 deletions(-) diff --git a/CustomApps/lyrics-plus/OptionsMenu.js b/CustomApps/lyrics-plus/OptionsMenu.js index a6d0dea2bb..30e986599d 100644 --- a/CustomApps/lyrics-plus/OptionsMenu.js +++ b/CustomApps/lyrics-plus/OptionsMenu.js @@ -4,10 +4,10 @@ const OptionsMenuItemIcon = react.createElement( width: 16, height: 16, viewBox: "0 0 16 16", - fill: "currentColor" + fill: "currentColor", }, react.createElement("path", { - d: "M13.985 2.383L5.127 12.754 1.388 8.375l-.658.77 4.397 5.149 9.618-11.262z" + d: "M13.985 2.383L5.127 12.754 1.388 8.375l-.658.77 4.397 5.149 9.618-11.262z", }) ); @@ -17,7 +17,7 @@ const OptionsMenuItem = react.memo(({ onSelect, value, isSelected }) => { { onClick: onSelect, icon: isSelected ? OptionsMenuItemIcon : null, - trailingIcon: isSelected ? OptionsMenuItemIcon : null + trailingIcon: isSelected ? OptionsMenuItemIcon : null, }, value ); @@ -49,24 +49,24 @@ const OptionsMenu = react.memo(({ options, onSelect, selected, defaultValue, bol // Close menu on item click menuRef.current?.click(); }, - isSelected: selected?.key === key + isSelected: selected?.key === key, }) ) ), trigger: "click", action: "toggle", - renderInline: false + renderInline: false, }, react.createElement( "button", { className: "optionsMenu-dropBox", - ref: menuRef + ref: menuRef, }, react.createElement( "span", { - className: bold ? "main-type-mestoBold" : "main-type-mesto" + className: bold ? "main-type-mestoBold" : "main-type-mesto", }, selected?.value || defaultValue ), @@ -76,10 +76,10 @@ const OptionsMenu = react.memo(({ options, onSelect, selected, defaultValue, bol height: "16", width: "16", fill: "currentColor", - viewBox: "0 0 16 16" + viewBox: "0 0 16 16", }, react.createElement("path", { - d: "M3 6l5 5.794L13 6z" + d: "M3 6l5 5.794L13 6z", }) ) ) @@ -89,7 +89,7 @@ const OptionsMenu = react.memo(({ options, onSelect, selected, defaultValue, bol const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { const items = useMemo(() => { let sourceOptions = { - none: "None" + none: "None", }; const languageOptions = { @@ -97,7 +97,7 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { "zh-hans": "Chinese (Simplified)", "zh-hant": "Chinese (Traditional)", ja: "Japanese", - ko: "Korean" + ko: "Korean", }; let modeOptions = {}; @@ -105,14 +105,14 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { if (hasTranslation.musixmatch) { sourceOptions = { ...sourceOptions, - musixmatchTranslation: "English (Musixmatch)" + musixmatchTranslation: "English (Musixmatch)", }; } if (hasTranslation.netease) { sourceOptions = { ...sourceOptions, - neteaseTranslation: "Chinese (Netease)" + neteaseTranslation: "Chinese (Netease)", }; } @@ -122,14 +122,14 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { furigana: "Furigana", romaji: "Romaji", hiragana: "Hiragana", - katakana: "Katakana" + katakana: "Katakana", }; break; } case "korean": { modeOptions = { hangul: "Hangul", - romaja: "Romaja" + romaja: "Romaja", }; break; } @@ -137,7 +137,7 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { modeOptions = { cn: "Simplified Chinese", hk: "Traditional Chinese (Hong Kong)", - tw: "Traditional Chinese (Taiwan)" + tw: "Traditional Chinese (Taiwan)", }; break; } @@ -149,21 +149,21 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { key: "translate:translated-lyrics-source", type: ConfigSelection, options: sourceOptions, - renderInline: true + renderInline: true, }, { desc: "Language Override", key: "translate:detect-language-override", type: ConfigSelection, options: languageOptions, - renderInline: true + renderInline: true, }, { desc: "Display Mode", key: `translation-mode:${friendlyLanguage}`, type: ConfigSelection, options: modeOptions, - renderInline: true + renderInline: true, }, { desc: "Convert", @@ -171,8 +171,8 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { type: ConfigSlider, trigger: "click", action: "toggle", - renderInline: true - } + renderInline: true, + }, ]; }, [friendlyLanguage]); @@ -182,8 +182,8 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { const event = new CustomEvent("lyrics-plus", { detail: { type: "translation-menu", - items - } + items, + }, }); document.dispatchEvent(event); }, [friendlyLanguage]); @@ -191,12 +191,12 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { return react.createElement( Spicetify.ReactComponent.TooltipWrapper, { - label: "Conversion" + label: "Conversion", }, react.createElement( "div", { - className: "lyrics-tooltip-wrapper" + className: "lyrics-tooltip-wrapper", }, react.createElement( Spicetify.ReactComponent.ContextMenu, @@ -212,17 +212,17 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { CONFIG.visual[name] = value; localStorage.setItem(`${APP_NAME}:visual:${name}`, value); lyricContainerUpdate?.(); - } + }, }) ), trigger: "click", action: "toggle", - renderInline: true + renderInline: true, }, react.createElement( "button", { - className: "lyrics-config-button" + className: "lyrics-config-button", }, react.createElement( "p1", @@ -230,7 +230,7 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { width: 16, height: 16, viewBox: "0 0 16 10.3", - fill: "currentColor" + fill: "currentColor", }, "⇄" ) @@ -244,12 +244,12 @@ const AdjustmentsMenu = react.memo(({ mode }) => { return react.createElement( Spicetify.ReactComponent.TooltipWrapper, { - label: "Adjustments" + label: "Adjustments", }, react.createElement( "div", { - className: "lyrics-tooltip-wrapper" + className: "lyrics-tooltip-wrapper", }, react.createElement( Spicetify.ReactComponent.ContextMenu, @@ -266,7 +266,7 @@ const AdjustmentsMenu = react.memo(({ mode }) => { type: ConfigAdjust, min: fontSizeLimit.min, max: fontSizeLimit.max, - step: fontSizeLimit.step + step: fontSizeLimit.step, }, { desc: "Track delay", @@ -275,37 +275,37 @@ const AdjustmentsMenu = react.memo(({ mode }) => { min: Number.NEGATIVE_INFINITY, max: Number.POSITIVE_INFINITY, step: 250, - when: () => mode === SYNCED || mode === KARAOKE + when: () => mode === SYNCED || mode === KARAOKE, }, { desc: "Compact", key: "synced-compact", type: ConfigSlider, - when: () => mode === SYNCED || mode === KARAOKE + when: () => mode === SYNCED || mode === KARAOKE, }, { desc: "Dual panel", key: "dual-genius", type: ConfigSlider, - when: () => mode === GENIUS - } + when: () => mode === GENIUS, + }, ], onChange: (name, value) => { CONFIG.visual[name] = value; localStorage.setItem(`${APP_NAME}:visual:${name}`, value); name === "delay" && localStorage.setItem(`lyrics-delay:${Spicetify.Player.data.item.uri}`, value); lyricContainerUpdate?.(); - } + }, }) ), trigger: "click", action: "toggle", - renderInline: true + renderInline: true, }, react.createElement( "button", { - className: "lyrics-config-button" + className: "lyrics-config-button", }, react.createElement( "svg", @@ -313,10 +313,10 @@ const AdjustmentsMenu = react.memo(({ mode }) => { width: 16, height: 16, viewBox: "0 0 16 10.3", - fill: "currentColor" + fill: "currentColor", }, react.createElement("path", { - d: "M 10.8125,0 C 9.7756347,0 8.8094481,0.30798341 8,0.836792 7.1905519,0.30798341 6.2243653,0 5.1875,0 2.3439941,0 0,2.3081055 0,5.15625 0,8.0001222 2.3393555,10.3125 5.1875,10.3125 6.2243653,10.3125 7.1905519,10.004517 8,9.4757081 8.8094481,10.004517 9.7756347,10.3125 10.8125,10.3125 13.656006,10.3125 16,8.0043944 16,5.15625 16,2.3123779 13.660644,0 10.8125,0 Z M 8,2.0146484 C 8.2629394,2.2503662 8.4963378,2.5183106 8.6936034,2.8125 H 7.3063966 C 7.5036622,2.5183106 7.7370606,2.2503662 8,2.0146484 Z M 6.619995,4.6875 C 6.6560059,4.3625487 6.7292481,4.0485841 6.8350831,3.75 h 2.3298338 c 0.1059572,0.2985841 0.1790772,0.6125487 0.21521,0.9375 z M 9.380005,5.625 C 9.3439941,5.9499512 9.2707519,6.2639159 9.1649169,6.5625 H 6.8350831 C 6.7291259,6.2639159 6.6560059,5.9499512 6.6198731,5.625 Z M 5.1875,9.375 c -2.3435059,0 -4.25,-1.8925781 -4.25,-4.21875 0,-2.3261719 1.9064941,-4.21875 4.25,-4.21875 0.7366944,0 1.4296875,0.1899414 2.0330809,0.5233154 C 6.2563478,2.3981934 5.65625,3.7083741 5.65625,5.15625 c 0,1.4478759 0.6000978,2.7580566 1.5643309,3.6954347 C 6.6171875,9.1850584 5.9241944,9.375 5.1875,9.375 Z M 8,8.2978516 C 7.7370606,8.0621337 7.5036622,7.7938231 7.3063966,7.4996337 H 8.6936034 C 8.4963378,7.7938231 8.2629394,8.0621338 8,8.2978516 Z M 10.8125,9.375 C 10.075806,9.375 9.3828125,9.1850584 8.7794191,8.8516847 9.7436522,7.9143066 10.34375,6.6041259 10.34375,5.15625 10.34375,3.7083741 9.7436522,2.3981934 8.7794191,1.4608154 9.3828125,1.1274414 10.075806,0.9375 10.8125,0.9375 c 2.343506,0 4.25,1.8925781 4.25,4.21875 0,2.3261719 -1.906494,4.21875 -4.25,4.21875 z m 0,0" + d: "M 10.8125,0 C 9.7756347,0 8.8094481,0.30798341 8,0.836792 7.1905519,0.30798341 6.2243653,0 5.1875,0 2.3439941,0 0,2.3081055 0,5.15625 0,8.0001222 2.3393555,10.3125 5.1875,10.3125 6.2243653,10.3125 7.1905519,10.004517 8,9.4757081 8.8094481,10.004517 9.7756347,10.3125 10.8125,10.3125 13.656006,10.3125 16,8.0043944 16,5.15625 16,2.3123779 13.660644,0 10.8125,0 Z M 8,2.0146484 C 8.2629394,2.2503662 8.4963378,2.5183106 8.6936034,2.8125 H 7.3063966 C 7.5036622,2.5183106 7.7370606,2.2503662 8,2.0146484 Z M 6.619995,4.6875 C 6.6560059,4.3625487 6.7292481,4.0485841 6.8350831,3.75 h 2.3298338 c 0.1059572,0.2985841 0.1790772,0.6125487 0.21521,0.9375 z M 9.380005,5.625 C 9.3439941,5.9499512 9.2707519,6.2639159 9.1649169,6.5625 H 6.8350831 C 6.7291259,6.2639159 6.6560059,5.9499512 6.6198731,5.625 Z M 5.1875,9.375 c -2.3435059,0 -4.25,-1.8925781 -4.25,-4.21875 0,-2.3261719 1.9064941,-4.21875 4.25,-4.21875 0.7366944,0 1.4296875,0.1899414 2.0330809,0.5233154 C 6.2563478,2.3981934 5.65625,3.7083741 5.65625,5.15625 c 0,1.4478759 0.6000978,2.7580566 1.5643309,3.6954347 C 6.6171875,9.1850584 5.9241944,9.375 5.1875,9.375 Z M 8,8.2978516 C 7.7370606,8.0621337 7.5036622,7.7938231 7.3063966,7.4996337 H 8.6936034 C 8.4963378,7.7938231 8.2629394,8.0621338 8,8.2978516 Z M 10.8125,9.375 C 10.075806,9.375 9.3828125,9.1850584 8.7794191,8.8516847 9.7436522,7.9143066 10.34375,6.6041259 10.34375,5.15625 10.34375,3.7083741 9.7436522,2.3981934 8.7794191,1.4608154 9.3828125,1.1274414 10.075806,0.9375 10.8125,0.9375 c 2.343506,0 4.25,1.8925781 4.25,4.21875 0,2.3261719 -1.906494,4.21875 -4.25,4.21875 z m 0,0", }) ) ) diff --git a/CustomApps/lyrics-plus/Pages.js b/CustomApps/lyrics-plus/Pages.js index b84307ad17..98dbe44187 100755 --- a/CustomApps/lyrics-plus/Pages.js +++ b/CustomApps/lyrics-plus/Pages.js @@ -11,7 +11,7 @@ const CreditFooter = react.memo(({ provider, copyright }) => { "p", { className: "lyrics-lyricsContainer-Provider main-type-mesto", - dir: "auto" + dir: "auto", }, credit.join(" • ") ) @@ -28,8 +28,8 @@ const IdlingIndicator = ({ isActive, progress, delay }) => { style: { "--position-index": 0, "--animation-index": 1, - "--indicator-delay": `${delay}ms` - } + "--indicator-delay": `${delay}ms`, + }, }, react.createElement("div", { className: `lyrics-idling-indicator__circle ${progress >= 0.05 ? "active" : ""}` }), react.createElement("div", { className: `lyrics-idling-indicator__circle ${progress >= 0.33 ? "active" : ""}` }), @@ -40,10 +40,10 @@ const IdlingIndicator = ({ isActive, progress, delay }) => { const emptyLine = { startTime: 0, endTime: 0, - text: [] + text: [], }; -const useTrackPosition = callback => { +const useTrackPosition = (callback) => { const callbackRef = useRef(); callbackRef.current = callback; @@ -69,8 +69,8 @@ const KaraokeLine = ({ text, isActive, position, startTime }) => { { className: `lyrics-lyricsContainer-Karaoke-Word${isWordActive ? " lyrics-lyricsContainer-Karaoke-WordActive" : ""}`, style: { - "--word-duration": `${time}ms` - } + "--word-duration": `${time}ms`, + }, }, word ); @@ -94,7 +94,7 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara () => [emptyLine, emptyLine, ...lyrics].map((line, i) => ({ ...line, - lineNumber: i + lineNumber: i, })), [lyrics] ); @@ -127,22 +127,22 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara "div", { className: "lyrics-lyricsContainer-SyncedLyricsPage", - ref: lyricContainerEle + ref: lyricContainerEle, }, react.createElement( "div", { className: "lyrics-lyricsContainer-SyncedLyrics", style: { - "--offset": `${offset}px` + "--offset": `${offset}px`, }, - key: lyricsId + key: lyricsId, }, activeLines.map(({ text, lineNumber, startTime }, i) => { if (i === 1 && activeLineIndex === 1) { return react.createElement(IdlingIndicator, { progress: position / activeLines[2].startTime, - delay: activeLines[2].startTime / 3 + delay: activeLines[2].startTime / 3, }); } @@ -176,22 +176,22 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara cursor: "pointer", "--position-index": animationIndex, "--animation-index": (animationIndex < 0 ? 0 : animationIndex) + 1, - "--blur-index": Math.abs(animationIndex) + "--blur-index": Math.abs(animationIndex), }, key: lineNumber, dir: "auto", ref, - onClick: event => { + onClick: (event) => { if (startTime) { Spicetify.Player.seek(startTime); } }, - onContextMenu: event => { + onContextMenu: (event) => { event.preventDefault(); Spicetify.Platform.ClipboardAPI.copy(rawLyrics) .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - } + }, }, !isKara ? text : react.createElement(KaraokeLine, { text, startTime, position, isActive }) ); @@ -199,7 +199,7 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara ), react.createElement(CreditFooter, { provider, - copyright + copyright, }) ); }); @@ -210,7 +210,7 @@ class SearchBar extends react.Component { this.state = { hidden: true, atNode: 0, - foundNodes: [] + foundNodes: [], }; this.container = null; } @@ -233,7 +233,7 @@ class SearchBar extends react.Component { this.container.blur(); this.setState({ hidden: true }); }; - this.loopThroughCallback = event => { + this.loopThroughCallback = (event) => { if (!this.state.foundNodes.length) { return; } @@ -278,7 +278,7 @@ class SearchBar extends react.Component { const walker = document.createTreeWalker( lyricsPage, NodeFilter.SHOW_TEXT, - node => { + (node) => { if (node.textContent.toLowerCase().includes(value)) { return NodeFilter.FILTER_ACCEPT; } @@ -316,13 +316,13 @@ class SearchBar extends react.Component { return react.createElement( "div", { - className: `lyrics-Searchbar${this.state.hidden ? " hidden" : ""}` + className: `lyrics-Searchbar${this.state.hidden ? " hidden" : ""}`, }, react.createElement("input", { - ref: c => { + ref: (c) => { this.container = c; }, - onChange: this.getNodeFromInput.bind(this) + onChange: this.getNodeFromInput.bind(this), }), react.createElement("svg", { width: 16, @@ -330,13 +330,13 @@ class SearchBar extends react.Component { viewBox: "0 0 16 16", fill: "currentColor", dangerouslySetInnerHTML: { - __html: Spicetify.SVGIcons.search - } + __html: Spicetify.SVGIcons.search, + }, }), react.createElement( "span", { - hidden: this.state.foundNodes.length === 0 + hidden: this.state.foundNodes.length === 0, }, `${this.state.atNode + 1}/${this.state.foundNodes.length}` ), @@ -344,8 +344,8 @@ class SearchBar extends react.Component { className: "lyrics-Searchbar-highlight", style: { "--search-highlight-top": `${y}px`, - "--search-highlight-height": `${height}px` - } + "--search-highlight-height": `${height}px`, + }, }) ); } @@ -394,7 +394,7 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa activeLineRef.current.scrollIntoView({ behavior: "smooth", block: "center", - inline: "nearest" + inline: "nearest", }); intialScroll[0] = true; } @@ -405,17 +405,17 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa { className: "lyrics-lyricsContainer-UnsyncedLyricsPage", key: lyricsId, - ref: pageRef + ref: pageRef, }, react.createElement("p", { - className: "lyrics-lyricsContainer-LyricsUnsyncedPadding" + className: "lyrics-lyricsContainer-LyricsUnsyncedPadding", }), padded.map(({ text, startTime }, i) => { if (i === 0) { return react.createElement(IdlingIndicator, { isActive: activeLineIndex === 0, progress: position / padded[1].startTime, - delay: padded[1].startTime / 3 + delay: padded[1].startTime / 3, }); } @@ -425,46 +425,46 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa { className: `lyrics-lyricsContainer-LyricsLine${i <= activeLineIndex ? " lyrics-lyricsContainer-LyricsLine-active" : ""}`, style: { - cursor: "pointer" + cursor: "pointer", }, dir: "auto", ref: isActive ? activeLineRef : null, - onClick: event => { + onClick: (event) => { if (startTime) { Spicetify.Player.seek(startTime); } }, - onContextMenu: event => { + onContextMenu: (event) => { event.preventDefault(); Spicetify.Platform.ClipboardAPI.copy(rawLyrics) .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - } + }, }, !isKara ? text : react.createElement(KaraokeLine, { text, startTime, position, isActive }) ); }), react.createElement("p", { - className: "lyrics-lyricsContainer-LyricsUnsyncedPadding" + className: "lyrics-lyricsContainer-LyricsUnsyncedPadding", }), react.createElement(CreditFooter, { provider, - copyright + copyright, }), react.createElement(SearchBar, null) ); }); const UnsyncedLyricsPage = react.memo(({ lyrics, provider, copyright }) => { - const rawLyrics = lyrics.map(lyrics => (typeof lyrics.text !== "object" ? lyrics.text : lyrics.text?.props?.children?.[0])).join("\n"); + const rawLyrics = lyrics.map((lyrics) => (typeof lyrics.text !== "object" ? lyrics.text : lyrics.text?.props?.children?.[0])).join("\n"); return react.createElement( "div", { - className: "lyrics-lyricsContainer-UnsyncedLyricsPage" + className: "lyrics-lyricsContainer-UnsyncedLyricsPage", }, react.createElement("p", { - className: "lyrics-lyricsContainer-LyricsUnsyncedPadding" + className: "lyrics-lyricsContainer-LyricsUnsyncedPadding", }), lyrics.map(({ text }) => { return react.createElement( @@ -472,22 +472,22 @@ const UnsyncedLyricsPage = react.memo(({ lyrics, provider, copyright }) => { { className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", dir: "auto", - onContextMenu: event => { + onContextMenu: (event) => { event.preventDefault(); Spicetify.Platform.ClipboardAPI.copy(rawLyrics) .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - } + }, }, text ); }), react.createElement("p", { - className: "lyrics-lyricsContainer-LyricsUnsyncedPadding" + className: "lyrics-lyricsContainer-LyricsUnsyncedPadding", }), react.createElement(CreditFooter, { provider, - copyright + copyright, }), react.createElement(SearchBar, null) ); @@ -501,7 +501,7 @@ noteDivider.innerHTML = ` { +noteTextContainer.onclick = (event) => { event.preventDefault(); event.stopPropagation(); }; @@ -523,7 +523,7 @@ function showNote(parent, note) { noteContainer.scrollIntoView({ behavior: "smooth", block: "center", - inline: "nearest" + inline: "nearest", }); }, 50); } @@ -550,11 +550,11 @@ const GeniusPage = react.memo( } else { id = id[1]; } - ProviderGenius.getNote(id).then(note => { + ProviderGenius.getNote(id).then((note) => { notes[id] = note; link.classList.add("fetched"); }); - link.onclick = event => { + link.onclick = (event) => { event.preventDefault(); if (!notes[id]) return; showNote(link, notes[id]); @@ -568,19 +568,19 @@ const GeniusPage = react.memo( react.createElement(VersionSelector, { items: versions, index: versionIndex, callback: onVersionChange }), react.createElement("div", { className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", - ref: c => { + ref: (c) => { container = c; }, dangerouslySetInnerHTML: { - __html: lyrics + __html: lyrics, }, - onContextMenu: event => { + onContextMenu: (event) => { event.preventDefault(); const copylyrics = lyrics.replace(/
/g, "\n").replace(/<[^>]*>/g, ""); Spicetify.Platform.ClipboardAPI.copy(copylyrics) .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - } + }, }) ); @@ -594,19 +594,19 @@ const GeniusPage = react.memo( react.createElement(VersionSelector, { items: versions, index: versionIndex2, callback: onVersionChange2 }), react.createElement("div", { className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", - ref: c => { + ref: (c) => { container2 = c; }, dangerouslySetInnerHTML: { - __html: lyrics2 + __html: lyrics2, }, - onContextMenu: event => { + onContextMenu: (event) => { event.preventDefault(); const copylyrics = lyrics.replace(/
/g, "\n").replace(/<[^>]*>/g, ""); Spicetify.Platform.ClipboardAPI.copy(copylyrics) .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - } + }, }) ); mainContainer.push(lyricsEl2); @@ -615,15 +615,15 @@ const GeniusPage = react.memo( return react.createElement( "div", { - className: "lyrics-lyricsContainer-UnsyncedLyricsPage" + className: "lyrics-lyricsContainer-UnsyncedLyricsPage", }, react.createElement("p", { - className: "lyrics-lyricsContainer-LyricsUnsyncedPadding main-type-ballad" + className: "lyrics-lyricsContainer-LyricsUnsyncedPadding main-type-ballad", }), react.createElement("div", { className: shouldSplit ? "split" : "" }, mainContainer), react.createElement(CreditFooter, { provider, - copyright + copyright, }), react.createElement(SearchBar, null) ); @@ -636,7 +636,7 @@ const LoadingIcon = react.createElement( width: "200px", height: "200px", viewBox: "0 0 100 100", - preserveAspectRatio: "xMidYMid" + preserveAspectRatio: "xMidYMid", }, react.createElement( "circle", @@ -646,7 +646,7 @@ const LoadingIcon = react.createElement( r: "0", fill: "none", stroke: "currentColor", - "stroke-width": "2" + "stroke-width": "2", }, react.createElement("animate", { attributeName: "r", @@ -656,7 +656,7 @@ const LoadingIcon = react.createElement( keyTimes: "0;1", keySplines: "0 0.2 0.8 1", calcMode: "spline", - begin: "0s" + begin: "0s", }), react.createElement("animate", { attributeName: "opacity", @@ -666,7 +666,7 @@ const LoadingIcon = react.createElement( keyTimes: "0;1", keySplines: "0.2 0 0.8 1", calcMode: "spline", - begin: "0s" + begin: "0s", }) ), react.createElement( @@ -677,7 +677,7 @@ const LoadingIcon = react.createElement( r: "0", fill: "none", stroke: "currentColor", - "stroke-width": "2" + "stroke-width": "2", }, react.createElement("animate", { attributeName: "r", @@ -687,7 +687,7 @@ const LoadingIcon = react.createElement( keyTimes: "0;1", keySplines: "0 0.2 0.8 1", calcMode: "spline", - begin: "-0.5s" + begin: "-0.5s", }), react.createElement("animate", { attributeName: "opacity", @@ -697,7 +697,7 @@ const LoadingIcon = react.createElement( keyTimes: "0;1", keySplines: "0.2 0 0.8 1", calcMode: "spline", - begin: "-0.5s" + begin: "-0.5s", }) ) ); @@ -709,15 +709,15 @@ const VersionSelector = react.memo(({ items, index, callback }) => { return react.createElement( "div", { - className: "lyrics-versionSelector" + className: "lyrics-versionSelector", }, react.createElement( "select", { - onChange: event => { + onChange: (event) => { callback(items, event.target.value); }, - value: index + value: index, }, items.map((a, i) => { return react.createElement("option", { value: i }, a.title); @@ -729,10 +729,10 @@ const VersionSelector = react.memo(({ items, index, callback }) => { height: "16", width: "16", fill: "currentColor", - viewBox: "0 0 16 16" + viewBox: "0 0 16 16", }, react.createElement("path", { - d: "M3 6l5 5.794L13 6z" + d: "M3 6l5 5.794L13 6z", }) ) ); diff --git a/CustomApps/lyrics-plus/PlaybarButton.js b/CustomApps/lyrics-plus/PlaybarButton.js index 1d3465a648..c1bbcd4373 100644 --- a/CustomApps/lyrics-plus/PlaybarButton.js +++ b/CustomApps/lyrics-plus/PlaybarButton.js @@ -28,11 +28,11 @@ style.classList.add("lyrics-plus:visual:playbar-button"); if (Spicetify.LocalStorage.get("lyrics-plus:visual:playbar-button") === "true") setPlaybarButton(); - window.addEventListener("lyrics-plus", event => { + window.addEventListener("lyrics-plus", (event) => { if (event.detail?.name === "playbar-button") event.detail.value ? setPlaybarButton() : removePlaybarButton(); }); - Spicetify.Platform.History.listen(location => { + Spicetify.Platform.History.listen((location) => { button.active = location.pathname === "/lyrics-plus"; }); diff --git a/CustomApps/lyrics-plus/ProviderGenius.js b/CustomApps/lyrics-plus/ProviderGenius.js index 1e92647669..35e8b7170a 100644 --- a/CustomApps/lyrics-plus/ProviderGenius.js +++ b/CustomApps/lyrics-plus/ProviderGenius.js @@ -51,14 +51,14 @@ const ProviderGenius = (() => { return new Promise((resolve, reject) => { const request = JSON.stringify({ method: "GET", - uri: url + uri: url, }); window.sendCosmosRequest({ request, persistent: false, onSuccess: resolve, - onFailure: reject + onFailure: reject, }); }); } @@ -109,9 +109,9 @@ const ProviderGenius = (() => { const geniusSearch = await Spicetify.CosmosAsync.get(url); - hits = geniusSearch.response.sections[0].hits.map(item => ({ + hits = geniusSearch.response.sections[0].hits.map((item) => ({ title: item.result.full_title, - url: item.result.url + url: item.result.url, })); if (!hits.length) { diff --git a/CustomApps/lyrics-plus/ProviderLRCLIB.js b/CustomApps/lyrics-plus/ProviderLRCLIB.js index 9add2b2cfd..0ff4576ced 100644 --- a/CustomApps/lyrics-plus/ProviderLRCLIB.js +++ b/CustomApps/lyrics-plus/ProviderLRCLIB.js @@ -6,23 +6,23 @@ const ProviderLRCLIB = (() => { track_name: info.title, artist_name: info.artist, album_name: info.album, - duration: durr + duration: durr, }; const finalURL = `${baseURL}?${Object.keys(params) - .map(key => `${key}=${encodeURIComponent(params[key])}`) + .map((key) => `${key}=${encodeURIComponent(params[key])}`) .join("&")}`; const body = await fetch(finalURL, { headers: { - "user-agent": `spicetify v${Spicetify.Config.version} (https://github.com/spicetify/cli)` - } + "user-agent": `spicetify v${Spicetify.Config.version} (https://github.com/spicetify/cli)`, + }, }); if (body.status !== 200) { return { error: "Request error: Track wasn't found", - uri: info.uri + uri: info.uri, }; } diff --git a/CustomApps/lyrics-plus/ProviderMusixmatch.js b/CustomApps/lyrics-plus/ProviderMusixmatch.js index f18522e543..bee377b9a5 100644 --- a/CustomApps/lyrics-plus/ProviderMusixmatch.js +++ b/CustomApps/lyrics-plus/ProviderMusixmatch.js @@ -1,7 +1,7 @@ const ProviderMusixmatch = (() => { const headers = { authority: "apic-desktop.musixmatch.com", - cookie: "x-mxm-token-guid=" + cookie: "x-mxm-token-guid=", }; async function findLyrics(info) { @@ -18,13 +18,13 @@ const ProviderMusixmatch = (() => { track_spotify_id: info.uri, q_duration: durr, f_subtitle_length: Math.floor(durr), - usertoken: CONFIG.providers.musixmatch.token + usertoken: CONFIG.providers.musixmatch.token, }; const finalURL = baseURL + Object.keys(params) - .map(key => `${key}=${encodeURIComponent(params[key])}`) + .map((key) => `${key}=${encodeURIComponent(params[key])}`) .join("&"); let body = await Spicetify.CosmosAsync.get(finalURL, null, headers); @@ -34,13 +34,13 @@ const ProviderMusixmatch = (() => { if (body["matcher.track.get"].message.header.status_code !== 200) { return { error: `Requested error: ${body["matcher.track.get"].message.header.mode}`, - uri: info.uri + uri: info.uri, }; } if (body["track.lyrics.get"]?.message?.body?.lyrics?.restricted) { return { error: "Unfortunately we're not authorized to show these lyrics.", - uri: info.uri + uri: info.uri, }; } @@ -63,13 +63,13 @@ const ProviderMusixmatch = (() => { f_subtitle_length: meta.track.track_length, q_duration: meta.track.track_length, commontrack_id: meta.track.commontrack_id, - usertoken: CONFIG.providers.musixmatch.token + usertoken: CONFIG.providers.musixmatch.token, }; const finalURL = baseURL + Object.keys(params) - .map(key => `${key}=${encodeURIComponent(params[key])}`) + .map((key) => `${key}=${encodeURIComponent(params[key])}`) .join("&"); let result = await Spicetify.CosmosAsync.get(finalURL, null, headers); @@ -80,7 +80,7 @@ const ProviderMusixmatch = (() => { result = result.message.body; - const parsedKaraoke = JSON.parse(result.richsync.richsync_body).map(line => { + const parsedKaraoke = JSON.parse(result.richsync.richsync_body).map((line) => { const startTime = line.ts * 1000; const endTime = line.te * 1000; const words = line.l; @@ -94,12 +94,12 @@ const ProviderMusixmatch = (() => { return { word: wordText, - time + time, }; }); return { startTime, - text + text, }; }); @@ -125,9 +125,9 @@ const ProviderMusixmatch = (() => { return null; } - return JSON.parse(subtitle.subtitle_body).map(line => ({ + return JSON.parse(subtitle.subtitle_body).map((line) => ({ text: line.text || "♪", - startTime: line.time.total * 1000 + startTime: line.time.total * 1000, })); } @@ -152,7 +152,7 @@ const ProviderMusixmatch = (() => { if (!lyrics) { return null; } - return lyrics.split("\n").map(text => ({ text })); + return lyrics.split("\n").map((text) => ({ text })); } return null; @@ -167,13 +167,13 @@ const ProviderMusixmatch = (() => { const params = { track_id, - usertoken: CONFIG.providers.musixmatch.token + usertoken: CONFIG.providers.musixmatch.token, }; const finalURL = baseURL + Object.keys(params) - .map(key => `${key}=${encodeURIComponent(params[key])}`) + .map((key) => `${key}=${encodeURIComponent(params[key])}`) .join("&"); let result = await Spicetify.CosmosAsync.get(finalURL, null, headers); diff --git a/CustomApps/lyrics-plus/ProviderNetease.js b/CustomApps/lyrics-plus/ProviderNetease.js index fb3ef56442..75a0d83c9a 100644 --- a/CustomApps/lyrics-plus/ProviderNetease.js +++ b/CustomApps/lyrics-plus/ProviderNetease.js @@ -1,6 +1,6 @@ const ProviderNetease = (() => { const requestHeader = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0" + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0", }; async function findLyrics(info) { @@ -19,9 +19,9 @@ const ProviderNetease = (() => { // normalized expected album name const neAlbumName = Utils.normalize(info.album); const expectedAlbumName = Utils.containsHanCharacter(neAlbumName) ? await Utils.toSimplifiedChinese(neAlbumName) : neAlbumName; - let itemId = items.findIndex(val => Utils.normalize(val.album.name) === expectedAlbumName); - if (itemId === -1) itemId = items.findIndex(val => Math.abs(info.duration - val.duration) < 3000); - if (itemId === -1) itemId = items.findIndex(val => val.name === cleanTitle); + let itemId = items.findIndex((val) => Utils.normalize(val.album.name) === expectedAlbumName); + if (itemId === -1) itemId = items.findIndex((val) => Math.abs(info.duration - val.duration) < 3000); + if (itemId === -1) itemId = items.findIndex((val) => val.name === cleanTitle); if (itemId === -1) throw "Cannot find track"; return await Spicetify.CosmosAsync.get(lyricURL + items[itemId].id, null, requestHeader); @@ -31,7 +31,7 @@ const ProviderNetease = (() => { "\\s?作?\\s*词|\\s?作?\\s*曲|\\s?编\\s*曲?|\\s?监\\s*制?", ".*编写|.*和音|.*和声|.*合声|.*提琴|.*录|.*工程|.*工作室|.*设计|.*剪辑|.*制作|.*发行|.*出品|.*后期|.*混音|.*缩混", "原唱|翻唱|题字|文案|海报|古筝|二胡|钢琴|吉他|贝斯|笛子|鼓|弦乐", - "lrc|publish|vocal|guitar|program|produce|write|mix" + "lrc|publish|vocal|guitar|program|produce|write|mix", ]; const creditInfoRegExp = new RegExp(`^(${creditInfo.join("|")}).*(:|:)`, "i"); @@ -51,7 +51,7 @@ const ProviderNetease = (() => { return { text: line }; } - const textIndex = matchResult.findIndex(slice => !slice.endsWith("]")); + const textIndex = matchResult.findIndex((slice) => !slice.endsWith("]")); let text = ""; if (textIndex > -1) { @@ -73,7 +73,7 @@ const ProviderNetease = (() => { if (components[i + 1] === " ") continue; result.push({ word: `${components[i + 1]} `, - time: Number.parseInt(components[i]) + time: Number.parseInt(components[i]), }); } return result; @@ -86,9 +86,9 @@ const ProviderNetease = (() => { return null; } - const lines = lyricStr.split(/\r?\n/).map(line => line.trim()); + const lines = lyricStr.split(/\r?\n/).map((line) => line.trim()); const karaoke = lines - .map(line => { + .map((line) => { const { time, text } = parseTimestamp(line); if (!time || !text) return null; @@ -99,7 +99,7 @@ const ProviderNetease = (() => { return { startTime: start, // endTime: start + durr, - text: breakdownLine(text) + text: breakdownLine(text), }; } return null; @@ -121,9 +121,9 @@ const ProviderNetease = (() => { return null; } - const lines = lyricStr.split(/\r?\n/).map(line => line.trim()); + const lines = lyricStr.split(/\r?\n/).map((line) => line.trim()); const lyrics = lines - .map(line => { + .map((line) => { const { time, text } = parseTimestamp(line); if (text === "纯音乐, 请欣赏") noLyrics = true; if (!time || !text) return null; @@ -133,7 +133,7 @@ const ProviderNetease = (() => { if (!Number.isNaN(min) && !Number.isNaN(sec) && !containCredits(text)) { return { startTime: (min * 60 + sec) * 1000, - text: text || "" + text: text || "", }; } return null; @@ -153,9 +153,9 @@ const ProviderNetease = (() => { return null; } - const lines = lyricStr.split(/\r?\n/).map(line => line.trim()); + const lines = lyricStr.split(/\r?\n/).map((line) => line.trim()); const translation = lines - .map(line => { + .map((line) => { const { time, text } = parseTimestamp(line); if (!time || !text) return null; @@ -164,7 +164,7 @@ const ProviderNetease = (() => { if (!Number.isNaN(min) && !Number.isNaN(sec) && !containCredits(text)) { return { startTime: (min * 60 + sec) * 1000, - text: text || "" + text: text || "", }; } return null; @@ -185,9 +185,9 @@ const ProviderNetease = (() => { return null; } - const lines = lyricStr.split(/\r?\n/).map(line => line.trim()); + const lines = lyricStr.split(/\r?\n/).map((line) => line.trim()); const lyrics = lines - .map(line => { + .map((line) => { const parsed = parseTimestamp(line); if (parsed.text === "纯音乐, 请欣赏") noLyrics = true; if (!parsed.text || containCredits(parsed.text)) return null; diff --git a/CustomApps/lyrics-plus/Providers.js b/CustomApps/lyrics-plus/Providers.js index 5a65d8512e..22224983a8 100644 --- a/CustomApps/lyrics-plus/Providers.js +++ b/CustomApps/lyrics-plus/Providers.js @@ -1,12 +1,12 @@ const Providers = { - spotify: async info => { + spotify: async (info) => { const result = { uri: info.uri, karaoke: null, synced: null, unsynced: null, provider: "Spotify", - copyright: null + copyright: null, }; const baseURL = "https://spclient.wg.spotify.com/color-lyrics/v2/track/"; @@ -25,14 +25,14 @@ const Providers = { const lines = lyrics.lines; if (lyrics.syncType === "LINE_SYNCED") { - result.synced = lines.map(line => ({ + result.synced = lines.map((line) => ({ startTime: line.startTimeMs, - text: line.words + text: line.words, })); result.unsynced = result.synced; } else { - result.unsynced = lines.map(line => ({ - text: line.words + result.unsynced = lines.map((line) => ({ + text: line.words, })); } @@ -40,7 +40,7 @@ const Providers = { return result; }, - musixmatch: async info => { + musixmatch: async (info) => { const result = { error: null, uri: info.uri, @@ -49,7 +49,7 @@ const Providers = { unsynced: null, musixmatchTranslation: null, provider: "Musixmatch", - copyright: null + copyright: null, }; let list; @@ -81,16 +81,16 @@ const Providers = { const translation = await ProviderMusixmatch.getTranslation(list); if ((synced || unsynced) && translation) { const baseLyrics = synced ?? unsynced; - result.musixmatchTranslation = baseLyrics.map(line => ({ + result.musixmatchTranslation = baseLyrics.map((line) => ({ ...line, - text: translation.find(t => t.matchedLine === line.text)?.translation ?? line.text, - originalText: line.text + text: translation.find((t) => t.matchedLine === line.text)?.translation ?? line.text, + originalText: line.text, })); } return result; }, - netease: async info => { + netease: async (info) => { const result = { uri: info.uri, karaoke: null, @@ -98,7 +98,7 @@ const Providers = { unsynced: null, neteaseTranslation: null, provider: "Netease", - copyright: null + copyright: null, }; let list; @@ -128,14 +128,14 @@ const Providers = { return result; }, - lrclib: async info => { + lrclib: async (info) => { const result = { uri: info.uri, karaoke: null, synced: null, unsynced: null, provider: "lrclib", - copyright: null + copyright: null, }; let list; @@ -159,7 +159,7 @@ const Providers = { return result; }, - genius: async info => { + genius: async (info) => { const { lyrics, versions } = await ProviderGenius.fetchLyrics(info); let versionIndex2 = 0; @@ -181,16 +181,16 @@ const Providers = { versions, versionIndex: 0, genius2, - versionIndex2 + versionIndex2, }; }, - local: info => { + local: (info) => { let result = { uri: info.uri, karaoke: null, synced: null, unsynced: null, - provider: "local" + provider: "local", }; try { @@ -202,12 +202,12 @@ const Providers = { result = { ...result, - ...lyrics + ...lyrics, }; } catch { result.error = "No lyrics"; } return result; - } + }, }; diff --git a/CustomApps/lyrics-plus/Settings.js b/CustomApps/lyrics-plus/Settings.js index b1ba00b471..4d4df0d18c 100644 --- a/CustomApps/lyrics-plus/Settings.js +++ b/CustomApps/lyrics-plus/Settings.js @@ -3,7 +3,7 @@ const ButtonSVG = ({ icon, active = true, onClick }) => { "button", { className: `switch${active ? "" : " disabled"}`, - onClick + onClick, }, react.createElement("svg", { width: 16, @@ -11,8 +11,8 @@ const ButtonSVG = ({ icon, active = true, onClick }) => { viewBox: "0 0 16 16", fill: "currentColor", dangerouslySetInnerHTML: { - __html: icon - } + __html: icon, + }, }) ); }; @@ -23,7 +23,7 @@ const SwapButton = ({ icon, disabled, onClick }) => { { className: "switch small", onClick, - disabled + disabled, }, react.createElement("svg", { width: 10, @@ -31,8 +31,8 @@ const SwapButton = ({ icon, disabled, onClick }) => { viewBox: "0 0 16 16", fill: "currentColor", dangerouslySetInnerHTML: { - __html: icon - } + __html: icon, + }, }) ); }; @@ -61,7 +61,7 @@ const CacheButton = () => { localStorage.removeItem("lyrics-plus:local-lyrics"); setCount(0); }, - disabled: !count + disabled: !count, }, text ); @@ -73,7 +73,7 @@ const RefreshTokenButton = ({ setTokenCallback }) => { useEffect(() => { if (buttonText === "Refreshing token...") { Spicetify.CosmosAsync.get("https://apic-desktop.musixmatch.com/ws/1.1/token.get?app_id=web-desktop-app-v1.0", null, { - authority: "apic-desktop.musixmatch.com" + authority: "apic-desktop.musixmatch.com", }) .then(({ message: response }) => { if (response.header.status_code === 200 && response.body.user_token) { @@ -86,7 +86,7 @@ const RefreshTokenButton = ({ setTokenCallback }) => { console.error("Failed to refresh token", response); } }) - .catch(error => { + .catch((error) => { setButtonText("Failed to refresh token"); console.error("Failed to refresh token", error); }); @@ -100,7 +100,7 @@ const RefreshTokenButton = ({ setTokenCallback }) => { onClick: () => { setButtonText("Refreshing token..."); }, - disabled: buttonText !== "Refresh token" + disabled: buttonText !== "Refresh token", }, buttonText ); @@ -118,24 +118,24 @@ const ConfigSlider = ({ name, defaultValue, onChange = () => {} }) => { return react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "label", { - className: "col description" + className: "col description", }, name ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement(ButtonSVG, { icon: Spicetify.SVGIcons.check, active, - onClick: toggleState + onClick: toggleState, }) ) ); @@ -145,7 +145,7 @@ const ConfigSelection = ({ name, defaultValue, options, onChange = () => {} }) = const [value, setValue] = useState(defaultValue); const setValueCallback = useCallback( - event => { + (event) => { let value = event.target.value; if (!Number.isNaN(Number(value))) { value = Number.parseInt(value); @@ -165,32 +165,32 @@ const ConfigSelection = ({ name, defaultValue, options, onChange = () => {} }) = return react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "label", { - className: "col description" + className: "col description", }, name ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement( "select", { className: "main-dropDown-dropDown", value, - onChange: setValueCallback + onChange: setValueCallback, }, - Object.keys(options).map(item => + Object.keys(options).map((item) => react.createElement( "option", { - value: item + value: item, }, options[item] ) @@ -204,7 +204,7 @@ const ConfigInput = ({ name, defaultValue, onChange = () => {} }) => { const [value, setValue] = useState(defaultValue); const setValueCallback = useCallback( - event => { + (event) => { const value = event.target.value; setValue(value); onChange(value); @@ -215,23 +215,23 @@ const ConfigInput = ({ name, defaultValue, onChange = () => {} }) => { return react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "label", { - className: "col description" + className: "col description", }, name ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement("input", { value, - onChange: setValueCallback + onChange: setValueCallback, }) ) ); @@ -253,36 +253,36 @@ const ConfigAdjust = ({ name, defaultValue, step, min, max, onChange = () => {} return react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "label", { - className: "col description" + className: "col description", }, name ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement(SwapButton, { icon: ``, onClick: () => adjust(-1), - disabled: value === min + disabled: value === min, }), react.createElement( "p", { - className: "adjust-value" + className: "adjust-value", }, value ), react.createElement(SwapButton, { icon: Spicetify.SVGIcons.plus2px, onClick: () => adjust(1), - disabled: value === max + disabled: value === max, }) ) ); @@ -314,24 +314,24 @@ const ConfigHotkey = ({ name, defaultValue, onChange = () => {} }) => { return react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "label", { - className: "col description" + className: "col description", }, name ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement("input", { value, onFocus: record, - onBlur: finishRecord + onBlur: finishRecord, }) ) ); @@ -353,7 +353,7 @@ const ServiceOption = ({ item, onToggle, onSwap, isFirst = false, isLast = false const [active, setActive] = useState(item.on); const setTokenCallback = useCallback( - token => { + (token) => { setToken(token); onTokenChange(item.name, token); }, @@ -373,51 +373,51 @@ const ServiceOption = ({ item, onToggle, onSwap, isFirst = false, isLast = false react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "h3", { - className: "col description" + className: "col description", }, item.name ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement(ServiceAction, { item, - setTokenCallback + setTokenCallback, }), react.createElement(SwapButton, { icon: Spicetify.SVGIcons["chart-up"], onClick: () => onSwap(item.name, -1), - disabled: isFirst + disabled: isFirst, }), react.createElement(SwapButton, { icon: Spicetify.SVGIcons["chart-down"], onClick: () => onSwap(item.name, 1), - disabled: isLast + disabled: isLast, }), react.createElement(ButtonSVG, { icon: Spicetify.SVGIcons.check, active, - onClick: toggleActive + onClick: toggleActive, }) ) ), react.createElement("span", { dangerouslySetInnerHTML: { - __html: item.desc - } + __html: item.desc, + }, }), item.token !== undefined && react.createElement("input", { placeholder: `Place your ${item.name} token here`, value: token, - onChange: event => setTokenCallback(event.target.value) + onChange: (event) => setTokenCallback(event.target.value), }) ); }; @@ -428,7 +428,7 @@ const ServiceList = ({ itemsList, onListChange = () => {}, onToggle = () => {}, const onSwap = useCallback( (name, direction) => { - const curPos = items.findIndex(val => val === name); + const curPos = items.findIndex((val) => val === name); const newPos = curPos + direction; [items[curPos], items[newPos]] = [items[newPos], items[curPos]]; onListChange(items); @@ -447,7 +447,7 @@ const ServiceList = ({ itemsList, onListChange = () => {}, onToggle = () => {}, isLast: index === maxIndex, onSwap, onTokenChange, - onToggle + onToggle, }); }); }; @@ -459,7 +459,7 @@ const OptionList = ({ type, items, onChange }) => { useEffect(() => { if (!type) return; - const eventListener = event => { + const eventListener = (event) => { if (event.detail?.type !== type) return; setItemList(event.detail.items); }; @@ -468,7 +468,7 @@ const OptionList = ({ type, items, onChange }) => { return () => document.removeEventListener("lyrics-plus", eventListener); }, []); - return itemList.map(item => { + return itemList.map((item) => { if (!item || (item.when && !item.when())) { return; } @@ -482,16 +482,16 @@ const OptionList = ({ type, items, onChange }) => { ...item, name: item.desc, defaultValue: CONFIG.visual[item.key], - onChange: value => { + onChange: (value) => { onChangeItem(item.key, value); forceUpdate({}); - } + }, }), item.info && react.createElement("span", { dangerouslySetInnerHTML: { - __html: item.info - } + __html: item.info, + }, }) ); }); @@ -501,7 +501,7 @@ function openConfig() { const configContainer = react.createElement( "div", { - id: `${APP_NAME}-config-container` + id: `${APP_NAME}-config-container`, }, react.createElement("h2", null, "Options"), react.createElement(OptionList, { @@ -510,7 +510,7 @@ function openConfig() { desc: "Playbar button", key: "playbar-button", info: "Replace Spotify's lyrics button with Lyrics Plus.", - type: ConfigSlider + type: ConfigSlider, }, { desc: "Global delay", @@ -519,7 +519,7 @@ function openConfig() { type: ConfigAdjust, min: -10000, max: 10000, - step: 250 + step: 250, }, { desc: "Font size", @@ -528,7 +528,7 @@ function openConfig() { type: ConfigAdjust, min: fontSizeLimit.min, max: fontSizeLimit.max, - step: fontSizeLimit.step + step: fontSizeLimit.step, }, { desc: "Alignment", @@ -537,64 +537,64 @@ function openConfig() { options: { left: "Left", center: "Center", - right: "Right" - } + right: "Right", + }, }, { desc: "Fullscreen hotkey", key: "fullscreen-key", - type: ConfigHotkey + type: ConfigHotkey, }, { desc: "Compact synced: Lines to show before", key: "lines-before", type: ConfigSelection, - options: [0, 1, 2, 3, 4] + options: [0, 1, 2, 3, 4], }, { desc: "Compact synced: Lines to show after", key: "lines-after", type: ConfigSelection, - options: [0, 1, 2, 3, 4] + options: [0, 1, 2, 3, 4], }, { desc: "Compact synced: Fade-out blur", key: "fade-blur", - type: ConfigSlider + type: ConfigSlider, }, { desc: "Noise overlay", key: "noise", - type: ConfigSlider + type: ConfigSlider, }, { desc: "Colorful background", key: "colorful", - type: ConfigSlider + type: ConfigSlider, }, { desc: "Background color", key: "background-color", type: ConfigInput, - when: () => !CONFIG.visual.colorful + when: () => !CONFIG.visual.colorful, }, { desc: "Active text color", key: "active-color", type: ConfigInput, - when: () => !CONFIG.visual.colorful + when: () => !CONFIG.visual.colorful, }, { desc: "Inactive text color", key: "inactive-color", type: ConfigInput, - when: () => !CONFIG.visual.colorful + when: () => !CONFIG.visual.colorful, }, { desc: "Highlight text background", key: "highlight-color", type: ConfigInput, - when: () => !CONFIG.visual.colorful + when: () => !CONFIG.visual.colorful, }, { desc: "Text convertion: Japanese Detection threshold (Advanced)", @@ -603,7 +603,7 @@ function openConfig() { type: ConfigAdjust, min: thresholdSizeLimit.min, max: thresholdSizeLimit.max, - step: thresholdSizeLimit.step + step: thresholdSizeLimit.step, }, { desc: "Text convertion: Traditional-Simplified Detection threshold (Advanced)", @@ -612,8 +612,8 @@ function openConfig() { type: ConfigAdjust, min: thresholdSizeLimit.min, max: thresholdSizeLimit.max, - step: thresholdSizeLimit.step - } + step: thresholdSizeLimit.step, + }, ], onChange: (name, value) => { CONFIG.visual[name] = value; @@ -624,16 +624,16 @@ function openConfig() { detail: { type: "config", name: name, - value: value - } + value: value, + }, }); window.dispatchEvent(configChange); - } + }, }), react.createElement("h2", null, "Providers"), react.createElement(ServiceList, { itemsList: CONFIG.providersOrder, - onListChange: list => { + onListChange: (list) => { CONFIG.providersOrder = list; localStorage.setItem(`${APP_NAME}:services-order`, JSON.stringify(list)); }, @@ -645,13 +645,13 @@ function openConfig() { onTokenChange: (name, value) => { CONFIG.providers[name].token = value; localStorage.setItem(`${APP_NAME}:provider:${name}:token`, value); - } + }, }) ); Spicetify.PopupModal.display({ title: "Lyrics Plus", content: configContainer, - isLarge: true + isLarge: true, }); } diff --git a/CustomApps/lyrics-plus/TabBar.js b/CustomApps/lyrics-plus/TabBar.js index 4a0b6d55ac..b807edeb27 100644 --- a/CustomApps/lyrics-plus/TabBar.js +++ b/CustomApps/lyrics-plus/TabBar.js @@ -14,7 +14,7 @@ class TabBarItem extends react.Component { className: "lyrics-tabBar-headerItem", onClick: this.onSelect.bind(this), onDoubleClick: this.onLock.bind(this), - onContextMenu: this.onLock.bind(this) + onContextMenu: this.onLock.bind(this), }, react.createElement( "a", @@ -22,12 +22,12 @@ class TabBarItem extends react.Component { "aria-current": "page", className: `lyrics-tabBar-headerItemLink ${this.props.item.active ? "lyrics-tabBar-active" : ""}`, draggable: "false", - href: "" + href: "", }, react.createElement( "span", { - className: "main-type-mestoBold" + className: "main-type-mestoBold", }, this.props.item.value ) @@ -37,7 +37,7 @@ class TabBarItem extends react.Component { } const TabBarMore = react.memo(({ items, switchTo, lockIn }) => { - const activeItem = items.find(item => item.active); + const activeItem = items.find((item) => item.active); function onLock(event) { event.preventDefault(); @@ -50,14 +50,14 @@ const TabBarMore = react.memo(({ items, switchTo, lockIn }) => { { className: `lyrics-tabBar-headerItem ${activeItem ? "lyrics-tabBar-active" : ""}`, onDoubleClick: onLock, - onContextMenu: onLock + onContextMenu: onLock, }, react.createElement(OptionsMenu, { options: items, onSelect: switchTo, selected: activeItem, defaultValue: "More", - bold: true + bold: true, }) ); }); @@ -86,7 +86,7 @@ const TopBarContent = ({ links, activeLink, lockLink, switchCallback, lockCallba lockLink, switchCallback, lockCallback, - windowSize + windowSize, }) ); }; @@ -96,7 +96,7 @@ const TabBarContext = ({ children }) => { react.createElement( "div", { - className: "main-topBar-topbarContent" + className: "main-topBar-topbarContent", }, children ), @@ -172,31 +172,31 @@ const TabBar = react.memo(({ links, activeLink, lockLink, switchCallback, lockCa return react.createElement( "nav", { - className: "lyrics-tabBar lyrics-tabBar-nav" + className: "lyrics-tabBar lyrics-tabBar-nav", }, react.createElement( "ul", { className: "lyrics-tabBar-header", - ref: tabBarRef + ref: tabBarRef, }, react.createElement("li", { - className: "lyrics-tabBar-headerItem" + className: "lyrics-tabBar-headerItem", }), options .filter((_, id) => !droplistItem.includes(id)) - .map(item => + .map((item) => react.createElement(TabBarItem, { item, switchTo: switchCallback, - lockIn: lockCallback + lockIn: lockCallback, }) ), droplistItem.length || childrenSizes.length === 0 ? react.createElement(TabBarMore, { - items: droplistItem.map(i => options[i]).filter(Boolean), + items: droplistItem.map((i) => options[i]).filter(Boolean), switchTo: switchCallback, - lockIn: lockCallback + lockIn: lockCallback, }) : null ) diff --git a/CustomApps/lyrics-plus/Translator.js b/CustomApps/lyrics-plus/Translator.js index 4b0853afae..b012379d06 100644 --- a/CustomApps/lyrics-plus/Translator.js +++ b/CustomApps/lyrics-plus/Translator.js @@ -10,7 +10,7 @@ class Translator { this.finished = { ja: false, ko: false, - zh: false + zh: false, }; this.applyKuromojiFix(); @@ -106,7 +106,7 @@ class Translator { return this.kuroshiro.convert(text, { to: target, - mode: mode + mode: mode, }); } @@ -128,7 +128,7 @@ class Translator { const converter = this.OpenCC.Converter({ from: from, - to: target + to: target, }); return converter(text); @@ -141,6 +141,6 @@ class Translator { * @returns {Promise} */ static async #sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } } diff --git a/CustomApps/lyrics-plus/Utils.js b/CustomApps/lyrics-plus/Utils.js index 92959ff938..bd3961f587 100644 --- a/CustomApps/lyrics-plus/Utils.js +++ b/CustomApps/lyrics-plus/Utils.js @@ -9,7 +9,7 @@ const Utils = { const rgb = { r: Math.round(((colorInt >> 16) & 0xff) / div), g: Math.round(((colorInt >> 8) & 0xff) / div), - b: Math.round((colorInt & 0xff) / div) + b: Math.round((colorInt & 0xff) / div), }; return `rgb(${rgb.r},${rgb.g},${rgb.b})`; }, @@ -88,7 +88,7 @@ const Utils = { return s.replace(/\s-\s.*/, ""); }, capitalize(s) { - return s.replace(/^(\w)/, $1 => $1.toUpperCase()); + return s.replace(/^(\w)/, ($1) => $1.toUpperCase()); }, detectLanguage(lyrics) { if (!Array.isArray(lyrics)) return; @@ -96,7 +96,7 @@ const Utils = { // Should return IETF BCP 47 language tags. // This should detect the song's main language. // Remember there is a possibility of a song referencing something in another language and the lyrics show it in that native language! - const rawLyrics = lyrics.map(line => line.text).join(" "); + const rawLyrics = lyrics.map((line) => line.text).join(" "); const kanaRegex = /[\u3001-\u3003]|[\u3005\u3007]|[\u301d-\u301f]|[\u3021-\u3035]|[\u3038-\u303a]|[\u3040-\u30ff]|[\uff66-\uff9f]/gu; const hangulRegex = /(\S*[\u3131-\u314e|\u314f-\u3163|\uac00-\ud7a3]+\S*)/g; @@ -112,17 +112,17 @@ const Utils = { if (!cjkMatch) return; - const kanaCount = cjkMatch.filter(glyph => kanaRegex.test(glyph)).length; - const hanziCount = cjkMatch.filter(glyph => hanziRegex.test(glyph)).length; - const simpCount = cjkMatch.filter(glyph => simpRegex.test(glyph)).length; - const tradCount = cjkMatch.filter(glyph => tradRegex.test(glyph)).length; + const kanaCount = cjkMatch.filter((glyph) => kanaRegex.test(glyph)).length; + const hanziCount = cjkMatch.filter((glyph) => hanziRegex.test(glyph)).length; + const simpCount = cjkMatch.filter((glyph) => simpRegex.test(glyph)).length; + const tradCount = cjkMatch.filter((glyph) => tradRegex.test(glyph)).length; const kanaPercentage = kanaCount / cjkMatch.length; const hanziPercentage = hanziCount / cjkMatch.length; const simpPercentage = simpCount / cjkMatch.length; const tradPercentage = tradCount / cjkMatch.length; - if (cjkMatch.filter(glyph => hangulRegex.test(glyph)).length !== 0) { + if (cjkMatch.filter((glyph) => hangulRegex.test(glyph)).length !== 0) { return "ko"; } @@ -138,7 +138,7 @@ const Utils = { for (let i = 0; i < lyricsToTranslate.length; i++) { const lyric = { startTime: lyricsToTranslate[i].startTime || 0, - text: this.rubyTextToReact(translatedLines[i]) + text: this.rubyTextToReact(translatedLines[i]), }; state[stateName].push(lyric); } @@ -154,7 +154,7 @@ const Utils = { for (const time in synced) { dataSouce[item.startTime] = { ...dataSouce[item.startTime], - text: item.text + text: item.text, }; } @@ -162,7 +162,7 @@ const Utils = { const item = dataSouce[time]; const lyric = { startTime: time || 0, - text: this.rubyTextToOriginalReact(item.translate || item.text, item.text || item.translate) + text: this.rubyTextToOriginalReact(item.translate || item.text, item.text || item.translate), }; data.push(lyric); } @@ -201,7 +201,7 @@ const Utils = { formatTextWithTimestamps(text, startTime = 0) { if (text.props?.children) { return text.props.children - .map(child => { + .map((child) => { if (typeof child === "string") { return child; } @@ -214,7 +214,7 @@ const Utils = { if (Array.isArray(text)) { let wordTime = startTime; return text - .map(word => { + .map((word) => { wordTime += word.time; return `${word.word}<${this.formatTime(wordTime)}>`; }) @@ -224,7 +224,7 @@ const Utils = { }, convertParsedToLRC(lyrics) { return lyrics - .map(line => { + .map((line) => { if (!line.startTime) return line.text; return `[${this.formatTime(line.startTime)}]${this.formatTextWithTimestamps(line.text, line.startTime)}`; }) @@ -292,5 +292,5 @@ const Utils = { return lyrics .replace(/ | /g, "") // Remove space .replace(/[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~?!,。、《》【】「」]/g, ""); // Remove punctuation - } + }, }; diff --git a/CustomApps/lyrics-plus/index.js b/CustomApps/lyrics-plus/index.js index d447212c13..f703e16a35 100644 --- a/CustomApps/lyrics-plus/index.js +++ b/CustomApps/lyrics-plus/index.js @@ -53,44 +53,44 @@ const CONFIG = { "synced-compact": getConfig("lyrics-plus:visual:synced-compact"), "dual-genius": getConfig("lyrics-plus:visual:dual-genius"), "global-delay": Number(localStorage.getItem("lyrics-plus:visual:global-delay")) || 0, - delay: 0 + delay: 0, }, providers: { musixmatch: { on: getConfig("lyrics-plus:provider:musixmatch:on"), desc: "Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. If you have problems with retrieving lyrics, try refreshing the token by clicking Refresh Token button.", token: localStorage.getItem("lyrics-plus:provider:musixmatch:token") || "21051986b9886beabe1ce01c3ce94c96319411f8f2c122676365e3", - modes: [KARAOKE, SYNCED, UNSYNCED] + modes: [KARAOKE, SYNCED, UNSYNCED], }, spotify: { on: getConfig("lyrics-plus:provider:spotify:on"), desc: "Lyrics sourced from official Spotify API.", - modes: [SYNCED, UNSYNCED] + modes: [SYNCED, UNSYNCED], }, netease: { on: getConfig("lyrics-plus:provider:netease:on"), desc: "Crowdsourced lyrics provider ran by Chinese developers and users.", - modes: [KARAOKE, SYNCED, UNSYNCED] + modes: [KARAOKE, SYNCED, UNSYNCED], }, lrclib: { on: getConfig("lyrics-plus:provider:lrclib:on"), desc: "Lyrics sourced from lrclib.net. Supports both synced and unsynced lyrics. LRCLIB is a free and open-source lyrics provider.", - modes: [SYNCED, UNSYNCED] + modes: [SYNCED, UNSYNCED], }, genius: { on: spotifyVersion >= "1.2.31" ? false : getConfig("lyrics-plus:provider:genius:on"), desc: "Provide unsynced lyrics with insights from artists themselves. Genius is disabled and cannot be used as a provider on 1.2.31 and higher.", - modes: [GENIUS] + modes: [GENIUS], }, local: { on: getConfig("lyrics-plus:provider:local:on"), desc: "Provide lyrics from cache/local files loaded from previous Spotify sessions.", - modes: [KARAOKE, SYNCED, UNSYNCED] - } + modes: [KARAOKE, SYNCED, UNSYNCED], + }, }, providersOrder: localStorage.getItem("lyrics-plus:services-order"), modes: ["karaoke", "synced", "unsynced", "genius"], - locked: localStorage.getItem("lyrics-plus:lock-mode") || "-1" + locked: localStorage.getItem("lyrics-plus:lock-mode") || "-1", }; try { @@ -118,7 +118,7 @@ const emptyState = { unsynced: null, genius: null, genius2: null, - currentLyrics: null + currentLyrics: null, }; let lyricContainerUpdate; @@ -152,7 +152,7 @@ class LyricsContainer extends react.Component { provider: "", colors: { background: "", - inactive: "" + inactive: "", }, tempo: "0.25s", explicitMode: -1, @@ -163,7 +163,7 @@ class LyricsContainer extends react.Component { versionIndex2: 0, isFullscreen: false, isFADMode: false, - isCached: false + isCached: false, }; this.currentTrackUri = ""; this.nextTrackUri = ""; @@ -191,7 +191,7 @@ class LyricsContainer extends react.Component { artist: meta.artist_name, title: meta.title, uri: track.uri, - image: meta.image_url + image: meta.image_url, }; } @@ -205,7 +205,7 @@ class LyricsContainer extends react.Component { vibrant = Number.parseInt(hex.replace("#", ""), 16); } catch { const colors = await Spicetify.CosmosAsync.get(`https://spclient.wg.spotify.com/colorextractor/v1/extract-presets?uri=${uri}&format=json`); - vibrant = colors.entries[0].color_swatches.find(color => color.preset === "VIBRANT_NON_ALARMING").color; + vibrant = colors.entries[0].color_swatches.find((color) => color.preset === "VIBRANT_NON_ALARMING").color; } } catch { vibrant = 8747370; @@ -214,8 +214,8 @@ class LyricsContainer extends react.Component { this.setState({ colors: { background: Utils.convertIntToRGB(vibrant), - inactive: Utils.convertIntToRGB(vibrant, 3) - } + inactive: Utils.convertIntToRGB(vibrant, 3), + }, }); } @@ -234,7 +234,7 @@ class LyricsContainer extends react.Component { period = Math.round(period * 100) / 100; this.setState({ - tempo: `${String(period)}s` + tempo: `${String(period)}s`, }); } @@ -283,9 +283,10 @@ class LyricsContainer extends react.Component { } if (finalData.musixmatchTranslation && typeof finalData.musixmatchTranslation[0].startTime === "undefined" && finalData.synced) { - finalData.musixmatchTranslation = finalData.synced.map(line => ({ + finalData.musixmatchTranslation = finalData.synced.map((line) => ({ ...line, - text: finalData.musixmatchTranslation.find(l => Utils.processLyrics(l.originalText) === Utils.processLyrics(line.text))?.text ?? line.text + text: + finalData.musixmatchTranslation.find((l) => Utils.processLyrics(l.originalText) === Utils.processLyrics(line.text))?.text ?? line.text, })); } @@ -440,10 +441,10 @@ class LyricsContainer extends react.Component { ["romaji", "spaced", "romaji"], ["hiragana", "furigana", "furigana"], ["hiragana", "normal", "hiragana"], - ["katakana", "normal", "katakana"] + ["katakana", "normal", "katakana"], ]) { if (language !== "ja") continue; - Promise.all(lyrics.map(lyric => this.translator.romajifyText(lyric.text, params[0], params[1]))).then(results => { + Promise.all(lyrics.map((lyric) => this.translator.romajifyText(lyric.text, params[0], params[1]))).then((results) => { const result = results.join("\n"); Utils.processTranslatedLyrics(result, lyrics, { state: this.state, stateName: params[2] }); showNotification(200); @@ -453,10 +454,10 @@ class LyricsContainer extends react.Component { for (const params of [ ["hangul", "hangul"], - ["romaja", "romaja"] + ["romaja", "romaja"], ]) { if (language !== "ko") continue; - Promise.all(lyrics.map(lyric => this.translator.convertToRomaja(lyric.text, params[1]))).then(results => { + Promise.all(lyrics.map((lyric) => this.translator.convertToRomaja(lyric.text, params[1]))).then((results) => { const result = results.join("\n"); Utils.processTranslatedLyrics(result, lyrics, { state: this.state, stateName: params[1] }); showNotification(200); @@ -469,10 +470,10 @@ class LyricsContainer extends react.Component { ["cn", "tw"], ["t", "cn"], ["t", "hk"], - ["t", "tw"] + ["t", "tw"], ]) { if (!language.includes("zh") || (language === "zh-hans" && params[0] === "t") || (language === "zh-hant" && params[0] === "cn")) continue; - Promise.all(lyrics.map(lyric => this.translator.convertChinese(lyric.text, params[0], params[1]))).then(results => { + Promise.all(lyrics.map((lyric) => this.translator.convertChinese(lyric.text, params[0], params[1]))).then((results) => { const result = results.join("\n"); Utils.processTranslatedLyrics(result, lyrics, { state: this.state, stateName: params[1] }); showNotification(200); @@ -490,13 +491,13 @@ class LyricsContainer extends react.Component { this.setState({ ...emptyLine, genius2: this.state.genius2, - isLoading: true + isLoading: true, }); const lyrics = await ProviderGenius.fetchLyricsVersion(items, index); this.setState({ genius: lyrics, versionIndex: index, - isLoading: false + isLoading: false, }); } } @@ -506,22 +507,22 @@ class LyricsContainer extends react.Component { this.setState({ ...emptyLine, genius: this.state.genius, - isLoading: true + isLoading: true, }); const lyrics = await ProviderGenius.fetchLyricsVersion(items, index); this.setState({ genius2: lyrics, versionIndex2: index, - isLoading: false + isLoading: false, }); } } saveLocalLyrics(uri, lyrics) { if (lyrics.genius) { - lyrics.unsynced = lyrics.genius.split("
").map(lyc => { + lyrics.unsynced = lyrics.genius.split("
").map((lyc) => { return { - text: lyc.replace(/<[^>]*>/g, "") + text: lyc.replace(/<[^>]*>/g, ""), }; }); lyrics.genius = null; @@ -548,13 +549,13 @@ class LyricsContainer extends react.Component { return; } - reader.onload = e => { + reader.onload = (e) => { try { const localLyrics = Utils.parseLocalLyrics(e.target.result); const parsedKeys = Object.keys(localLyrics) - .filter(key => localLyrics[key]) - .map(key => key[0].toUpperCase() + key.slice(1)) - .map(key => `${key}`); + .filter((key) => localLyrics[key]) + .map((key) => key[0].toUpperCase() + key.slice(1)) + .map((key) => `${key}`); if (!parsedKeys.length) { Spicetify.showNotification("Nothing to load", true); @@ -572,7 +573,7 @@ class LyricsContainer extends react.Component { } }; - reader.onerror = e => { + reader.onerror = (e) => { console.error(e); Spicetify.showNotification("Failed to read file", true); }; @@ -617,7 +618,7 @@ class LyricsContainer extends react.Component { this.configButton = new Spicetify.Menu.Item("Lyrics Plus config", false, openConfig, "lyrics"); this.configButton.register(); - this.onFontSizeChange = event => { + this.onFontSizeChange = (event) => { if (!event.ctrlKey) return; const dir = event.deltaY < 0 ? 1 : -1; let temp = CONFIG.visual["font-size"] + dir * fontSizeLimit.step; @@ -644,7 +645,7 @@ class LyricsContainer extends react.Component { } this.setState({ - isFullscreen: isEnabled + isFullscreen: isEnabled, }); }; this.mousetrap.reset(); @@ -661,7 +662,7 @@ class LyricsContainer extends react.Component { updateVisualOnConfigChange() { this.availableModes = CONFIG.modes.filter((_, id) => { - return Object.values(CONFIG.providers).some(p => p.on && p.modes.includes(id)); + return Object.values(CONFIG.providers).some((p) => p.on && p.modes.includes(id)); }); if (!CONFIG.visual.colorful) { @@ -670,7 +671,7 @@ class LyricsContainer extends react.Component { "--lyrics-color-inactive": CONFIG.visual["inactive-color"], "--lyrics-color-background": CONFIG.visual["background-color"], "--lyrics-highlight-background": CONFIG.visual["highlight-color"], - "--lyrics-background-noise": CONFIG.visual.noise ? "var(--background-noise)" : "unset" + "--lyrics-background-noise": CONFIG.visual.noise ? "var(--background-noise)" : "unset", }; } @@ -678,7 +679,7 @@ class LyricsContainer extends react.Component { ...this.styleVariables, "--lyrics-align-text": CONFIG.visual.alignment, "--lyrics-font-size": `${CONFIG.visual["font-size"]}px`, - "--animation-tempo": this.state.tempo + "--animation-tempo": this.state.tempo, }; this.mousetrap.reset(); @@ -737,7 +738,7 @@ class LyricsContainer extends react.Component { "--lyrics-color-inactive": this.state.colors.inactive, "--lyrics-color-background": this.state.colors.background || "transparent", "--lyrics-highlight-background": this.state.colors.inactive, - "--lyrics-background-noise": CONFIG.visual.noise ? "var(--background-noise)" : "unset" + "--lyrics-background-noise": CONFIG.visual.noise ? "var(--background-noise)" : "unset", }; } @@ -745,7 +746,7 @@ class LyricsContainer extends react.Component { ...this.styleVariables, "--lyrics-align-text": CONFIG.visual.alignment, "--lyrics-font-size": `${CONFIG.visual["font-size"]}px`, - "--animation-tempo": this.state.tempo + "--animation-tempo": this.state.tempo, }; let mode = -1; @@ -785,21 +786,21 @@ class LyricsContainer extends react.Component { trackUri: this.state.uri, lyrics: this.state.karaoke, provider: this.state.provider, - copyright: this.state.copyright + copyright: this.state.copyright, }); } else if (mode === SYNCED && this.state.synced) { activeItem = react.createElement(CONFIG.visual["synced-compact"] ? SyncedLyricsPage : SyncedExpandedLyricsPage, { trackUri: this.state.uri, lyrics: CONFIG.visual.translate && translatedLyrics ? translatedLyrics : this.state.currentLyrics, provider: this.state.provider, - copyright: this.state.copyright + copyright: this.state.copyright, }); } else if (mode === UNSYNCED && this.state.unsynced) { activeItem = react.createElement(UnsyncedLyricsPage, { trackUri: this.state.uri, lyrics: CONFIG.visual.translate && translatedLyrics ? translatedLyrics : this.state.currentLyrics, provider: this.state.provider, - copyright: this.state.copyright + copyright: this.state.copyright, }); } else if (mode === GENIUS && this.state.genius) { activeItem = react.createElement(GeniusPage, { @@ -813,7 +814,7 @@ class LyricsContainer extends react.Component { onVersionChange: this.onVersionChange.bind(this), lyrics2: this.state.genius2, versionIndex2: this.state.versionIndex2, - onVersionChange2: this.onVersionChange2.bind(this) + onVersionChange2: this.onVersionChange2.bind(this), }); } } @@ -822,12 +823,12 @@ class LyricsContainer extends react.Component { activeItem = react.createElement( "div", { - className: "lyrics-lyricsContainer-LyricsUnavailablePage" + className: "lyrics-lyricsContainer-LyricsUnavailablePage", }, react.createElement( "span", { - className: "lyrics-lyricsContainer-LyricsUnavailableMessage" + className: "lyrics-lyricsContainer-LyricsUnavailableMessage", }, this.state.isLoading ? LoadingIcon : "(• _ • )" ) @@ -843,32 +844,32 @@ class LyricsContainer extends react.Component { fadLyricsContainer ? " fad-enabled" : "" }`, style: this.styleVariables, - ref: el => { + ref: (el) => { if (!el) return; el.onmousewheel = this.onFontSizeChange; - } + }, }, react.createElement("div", { - className: "lyrics-lyricsContainer-LyricsBackground" + className: "lyrics-lyricsContainer-LyricsBackground", }), react.createElement( "div", { - className: "lyrics-config-button-container" + className: "lyrics-config-button-container", }, showTranslationButton && react.createElement(TranslationMenu, { friendlyLanguage, hasTranslation: { musixmatch: this.state.musixmatchTranslation !== null, - netease: this.state.neteaseTranslation !== null - } + netease: this.state.neteaseTranslation !== null, + }, }), react.createElement(AdjustmentsMenu, { mode }), react.createElement( Spicetify.ReactComponent.TooltipWrapper, { - label: this.state.isCached ? "Lyrics cached" : "Cache lyrics" + label: this.state.isCached ? "Lyrics cached" : "Cache lyrics", }, react.createElement( "button", @@ -883,7 +884,7 @@ class LyricsContainer extends react.Component { this.saveLocalLyrics(this.currentTrackUri, { synced, unsynced, karaoke, genius }); Spicetify.showNotification("Lyrics cached"); - } + }, }, react.createElement("svg", { width: 16, @@ -891,15 +892,15 @@ class LyricsContainer extends react.Component { viewBox: "0 0 16 16", fill: "currentColor", dangerouslySetInnerHTML: { - __html: Spicetify.SVGIcons[this.state.isCached ? "downloaded" : "download"] - } + __html: Spicetify.SVGIcons[this.state.isCached ? "downloaded" : "download"], + }, }) ) ), react.createElement( Spicetify.ReactComponent.TooltipWrapper, { - label: "Load lyrics from file" + label: "Load lyrics from file", }, react.createElement( "button", @@ -907,7 +908,7 @@ class LyricsContainer extends react.Component { className: "lyrics-config-button", onClick: () => { document.getElementById("lyrics-file-input").click(); - } + }, }, react.createElement("input", { type: "file", @@ -915,8 +916,8 @@ class LyricsContainer extends react.Component { accept: ".lrc,.txt", onChange: this.processLyricsFromFile.bind(this), style: { - display: "none" - } + display: "none", + }, }), react.createElement("svg", { width: 16, @@ -924,8 +925,8 @@ class LyricsContainer extends react.Component { viewBox: "0 0 16 16", fill: "currentColor", dangerouslySetInnerHTML: { - __html: Spicetify.SVGIcons["plus-alt"] - } + __html: Spicetify.SVGIcons["plus-alt"], + }, }) ) ) @@ -936,15 +937,15 @@ class LyricsContainer extends react.Component { links: this.availableModes, activeLink: CONFIG.modes[mode], lockLink: CONFIG.modes[this.state.lockMode], - switchCallback: label => { - const mode = CONFIG.modes.findIndex(a => a === label); + switchCallback: (label) => { + const mode = CONFIG.modes.findIndex((a) => a === label); if (mode !== this.state.mode) { this.setState({ explicitMode: mode }); this.state.provider !== "local" && this.fetchLyrics(Spicetify.Player.data.item, mode); } }, - lockCallback: label => { - let mode = CONFIG.modes.findIndex(a => a === label); + lockCallback: (label) => { + let mode = CONFIG.modes.findIndex((a) => a === label); if (mode === this.state.lockMode) { mode = -1; } @@ -952,7 +953,7 @@ class LyricsContainer extends react.Component { this.fetchLyrics(Spicetify.Player.data.item, mode); CONFIG.locked = mode; localStorage.setItem("lyrics-plus:lock-mode", mode); - } + }, }) ); diff --git a/CustomApps/new-releases/Card.js b/CustomApps/new-releases/Card.js index c731b8b7ca..fe14f84c3f 100644 --- a/CustomApps/new-releases/Card.js +++ b/CustomApps/new-releases/Card.js @@ -3,7 +3,7 @@ function DraggableComponent({ uri, title, children }) { return dragHandler ? react.cloneElement(children, { onDragStart: dragHandler, - draggable: "true" + draggable: "true", }) : children; } @@ -40,19 +40,19 @@ class Card extends react.Component { leading: Spicetify.ReactComponent.Snackbar.styledImage({ src: this.props.imageURL, imageHeight: "24px", - imageWidth: "24px" + imageWidth: "24px", }), center: Spicetify.React.createElement("div", { dangerouslySetInnerHTML: { - __html: `Dismissed ${this.title}.` - } + __html: `Dismissed ${this.title}.`, + }, }), trailing: Spicetify.ReactComponent.Snackbar.ctaText({ ctaText: "Undo", - onCtaClick: () => removeCards(this.props.uri, "undo") - }) - }) - }) + onCtaClick: () => removeCards(this.props.uri, "undo"), + }), + }), + }), }) : Spicetify.showNotification(`Dismissed ${this.title} from
${this.artist.name}`); @@ -69,37 +69,37 @@ class Card extends react.Component { return react.createElement( Spicetify.ReactComponent.RightClickMenu || "div", { - menu: react.createElement(this.menuType, { uri: this.uri }) + menu: react.createElement(this.menuType, { uri: this.uri }), }, react.createElement( "div", { className: "main-card-card", - onClick: event => { + onClick: (event) => { History.push(this.href); event.preventDefault(); - } + }, }, react.createElement( DraggableComponent, { uri: this.uri, - title: this.title + title: this.title, }, react.createElement( "div", { - className: "main-card-draggable" + className: "main-card-draggable", }, react.createElement( "div", { - className: "main-card-imageContainer" + className: "main-card-imageContainer", }, react.createElement( "div", { - className: "main-cardImage-imageWrapper" + className: "main-cardImage-imageWrapper", }, react.createElement( "div", @@ -109,14 +109,14 @@ class Card extends react.Component { draggable: "false", loading: "lazy", src: this.imageURL, - className: "main-image-image main-cardImage-image" + className: "main-image-image main-cardImage-image", }) ) ), react.createElement( "div", { - className: "main-card-PlayButtonContainer" + className: "main-card-PlayButtonContainer", }, react.createElement( "div", @@ -124,7 +124,7 @@ class Card extends react.Component { className: "main-playButton-PlayButton main-playButton-primary", "aria-label": Spicetify.Locale.get("play"), style: { "--size": "40px" }, - onClick: this.play.bind(this) + onClick: this.play.bind(this), }, react.createElement( "button", @@ -139,11 +139,11 @@ class Card extends react.Component { role: "img", width: "24", viewBox: "0 0 24 24", - "aria-hidden": "true" + "aria-hidden": "true", }, react.createElement("polygon", { points: "21.57 12 5.98 3 5.98 21 21.57 12", - fill: "currentColor" + fill: "currentColor", }) ) ) @@ -157,7 +157,7 @@ class Card extends react.Component { "button", { className: "main-card-closeButton", - onClick: this.closeButtonClicked.bind(this) + onClick: this.closeButtonClicked.bind(this), }, react.createElement( "svg", @@ -166,12 +166,12 @@ class Card extends react.Component { height: "16", viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg", - className: "main-card-closeButton-svg" + className: "main-card-closeButton-svg", }, react.createElement("path", { d: "M2.47 2.47a.75.75 0 0 1 1.06 0L8 6.94l4.47-4.47a.75.75 0 1 1 1.06 1.06L9.06 8l4.47 4.47a.75.75 0 1 1-1.06 1.06L8 9.06l-4.47 4.47a.75.75 0 0 1-1.06-1.06L6.94 8 2.47 3.53a.75.75 0 0 1 0-1.06Z", fill: "var(--spice-text)", - fillRule: "evenodd" + fillRule: "evenodd", }) ) ) @@ -180,7 +180,7 @@ class Card extends react.Component { react.createElement( "div", { - className: "main-card-cardMetadata" + className: "main-card-cardMetadata", }, react.createElement( "a", @@ -189,12 +189,12 @@ class Card extends react.Component { title: this.title, className: "main-cardHeader-link", dir: "auto", - href: this.href + href: this.href, }, react.createElement( "div", { - className: "main-cardHeader-text main-type-balladBold" + className: "main-cardHeader-text main-type-balladBold", }, this.title ) @@ -203,7 +203,7 @@ class Card extends react.Component { react.createElement( "div", { - className: "main-cardSubHeader-root main-type-mestoBold new-releases-cardSubHeader" + className: "main-cardSubHeader-root main-type-mestoBold new-releases-cardSubHeader", }, react.createElement("span", null, detail.join(" • ")) ), @@ -211,25 +211,25 @@ class Card extends react.Component { DraggableComponent, { uri: this.artist.uri, - title: this.artist.name + title: this.artist.name, }, react.createElement( "a", { className: "main-cardSubHeader-root main-type-mesto new-releases-cardSubHeader", href: this.artistHref, - onClick: event => { + onClick: (event) => { History.push(this.artistHref); event.stopPropagation(); event.preventDefault(); - } + }, }, react.createElement("span", null, this.artist.name) ) ) ), react.createElement("div", { - className: "main-card-cardLink" + className: "main-card-cardLink", }) ) ) diff --git a/CustomApps/new-releases/Icons.js b/CustomApps/new-releases/Icons.js index e9d00bc1f1..2d10ab0c9f 100644 --- a/CustomApps/new-releases/Icons.js +++ b/CustomApps/new-releases/Icons.js @@ -4,7 +4,7 @@ const LoadingIcon = react.createElement( width: "100px", height: "100px", viewBox: "0 0 100 100", - preserveAspectRatio: "xMidYMid" + preserveAspectRatio: "xMidYMid", }, react.createElement( "circle", @@ -14,7 +14,7 @@ const LoadingIcon = react.createElement( r: "0", fill: "none", stroke: "currentColor", - "stroke-width": "2" + "stroke-width": "2", }, react.createElement("animate", { attributeName: "r", @@ -24,7 +24,7 @@ const LoadingIcon = react.createElement( keyTimes: "0;1", keySplines: "0 0.2 0.8 1", calcMode: "spline", - begin: "0s" + begin: "0s", }), react.createElement("animate", { attributeName: "opacity", @@ -34,7 +34,7 @@ const LoadingIcon = react.createElement( keyTimes: "0;1", keySplines: "0.2 0 0.8 1", calcMode: "spline", - begin: "0s" + begin: "0s", }) ), react.createElement( @@ -45,7 +45,7 @@ const LoadingIcon = react.createElement( r: "0", fill: "none", stroke: "currentColor", - "stroke-width": "2" + "stroke-width": "2", }, react.createElement("animate", { attributeName: "r", @@ -55,7 +55,7 @@ const LoadingIcon = react.createElement( keyTimes: "0;1", keySplines: "0 0.2 0.8 1", calcMode: "spline", - begin: "-0.5s" + begin: "-0.5s", }), react.createElement("animate", { attributeName: "opacity", @@ -65,7 +65,7 @@ const LoadingIcon = react.createElement( keyTimes: "0;1", keySplines: "0.2 0 0.8 1", calcMode: "spline", - begin: "-0.5s" + begin: "-0.5s", }) ) ); @@ -75,15 +75,15 @@ class LoadMoreIcon extends react.Component { return react.createElement( "div", { - onClick: this.props.onClick + onClick: this.props.onClick, }, react.createElement( "p", { style: { fontSize: 100, - lineHeight: "65px" - } + lineHeight: "65px", + }, }, "»" ), @@ -91,8 +91,8 @@ class LoadMoreIcon extends react.Component { "span", { style: { - fontSize: 20 - } + fontSize: 20, + }, }, "Load more" ) diff --git a/CustomApps/new-releases/Settings.js b/CustomApps/new-releases/Settings.js index 9b4f57558d..775ecab60a 100644 --- a/CustomApps/new-releases/Settings.js +++ b/CustomApps/new-releases/Settings.js @@ -3,7 +3,7 @@ const ButtonSVG = ({ icon, active = true, onClick }) => { "button", { className: `switch${active ? "" : " disabled"}`, - onClick + onClick, }, react.createElement("svg", { width: 16, @@ -11,8 +11,8 @@ const ButtonSVG = ({ icon, active = true, onClick }) => { viewBox: "0 0 16 16", fill: "currentColor", dangerouslySetInnerHTML: { - __html: icon - } + __html: icon, + }, }) ); }; @@ -22,7 +22,7 @@ const ButtonText = ({ text, active = true, onClick }) => { "button", { className: `text${active ? "" : " disabled"}`, - onClick + onClick, }, text ); @@ -40,24 +40,24 @@ const ConfigSlider = ({ name, defaultValue, onChange = () => {} }) => { return react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "label", { - className: "col description" + className: "col description", }, name ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement(ButtonSVG, { icon: Spicetify.SVGIcons.check, active, - onClick: toggleState + onClick: toggleState, }) ) ); @@ -67,7 +67,7 @@ const ConfigSelection = ({ name, defaultValue, options, onChange = () => {} }) = const [value, setValue] = useState(defaultValue); const setValueCallback = useCallback( - event => { + (event) => { const value = event.target.value; setValue(value); onChange(value); @@ -78,31 +78,31 @@ const ConfigSelection = ({ name, defaultValue, options, onChange = () => {} }) = return react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "label", { - className: "col description" + className: "col description", }, name ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement( "select", { value, - onChange: setValueCallback + onChange: setValueCallback, }, - Object.keys(options).map(item => + Object.keys(options).map((item) => react.createElement( "option", { - value: item + value: item, }, options[item] ) @@ -116,7 +116,7 @@ const ConfigInput = ({ name, defaultValue, onChange = () => {} }) => { const [value, setValue] = useState(defaultValue); const setValueCallback = useCallback( - event => { + (event) => { const value = event.target.value; setValue(value); onChange(value); @@ -127,23 +127,23 @@ const ConfigInput = ({ name, defaultValue, onChange = () => {} }) => { return react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "label", { - className: "col description" + className: "col description", }, name ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement("input", { value, - onChange: setValueCallback + onChange: setValueCallback, }) ) ); @@ -151,7 +151,7 @@ const ConfigInput = ({ name, defaultValue, onChange = () => {} }) => { const OptionList = ({ items, onChange }) => { const [_, setItems] = useState(items); - return items.map(item => { + return items.map((item) => { if (!item.when()) { return; } @@ -159,10 +159,10 @@ const OptionList = ({ items, onChange }) => { name: item.desc, defaultValue: item.defaultValue, options: item.options, - onChange: value => { + onChange: (value) => { onChange(item.key, value); setItems([...items]); - } + }, }); }); }; @@ -171,7 +171,7 @@ function openConfig() { const configContainer = react.createElement( "div", { - id: `${APP_NAME}-config-container` + id: `${APP_NAME}-config-container`, }, react.createElement(OptionList, { items: [ @@ -184,65 +184,65 @@ function openConfig() { 30: "30 days", 60: "60 days", 90: "90 days", - 120: "120 days" + 120: "120 days", }, - when: () => true + when: () => true, }, { desc: "Date locale", key: "locale", defaultValue: CONFIG.locale, type: ConfigInput, - when: () => true + when: () => true, }, { desc: "Relative date", key: "relative", defaultValue: CONFIG.relative, type: ConfigSlider, - when: () => true + when: () => true, }, { desc: "Show type", key: "visual:type", defaultValue: CONFIG.visual.type, type: ConfigSlider, - when: () => true + when: () => true, }, { desc: "Show track count", key: "visual:count", defaultValue: CONFIG.visual.count, type: ConfigSlider, - when: () => true + when: () => true, }, { desc: "Fetch new podcast", key: "podcast", defaultValue: CONFIG.podcast, type: ConfigSlider, - when: () => true + when: () => true, }, { desc: "Fetch new music", key: "music", defaultValue: CONFIG.music, type: ConfigSlider, - when: () => true + when: () => true, }, { desc: Spicetify.Locale.get("artist.albums"), key: "album", defaultValue: CONFIG.album, type: ConfigSlider, - when: () => CONFIG.music + when: () => CONFIG.music, }, { desc: Spicetify.Locale.get("artist.singles"), key: "single-ep", defaultValue: CONFIG["single-ep"], type: ConfigSlider, - when: () => CONFIG.music + when: () => CONFIG.music, }, /* { desc: Spicetify.Locale.get("artist.appears-on"), @@ -256,8 +256,8 @@ function openConfig() { key: "compilations", defaultValue: CONFIG.compilations, type: ConfigSlider, - when: () => CONFIG.music - } + when: () => CONFIG.music, + }, ], onChange: (name, value) => { const subs = name.split(":"); @@ -268,28 +268,28 @@ function openConfig() { CONFIG[name] = value; } localStorage.setItem(`${APP_NAME}:${name}`, value); - } + }, }), react.createElement( "div", { - className: "setting-row" + className: "setting-row", }, react.createElement( "label", { - className: "col description" + className: "col description", }, "Dismissed releases" ), react.createElement( "div", { - className: "col action" + className: "col action", }, react.createElement(ButtonText, { text: Spicetify.Locale.get("equalizer.reset"), - onClick: removeCards.bind(this, null, "reset") + onClick: removeCards.bind(this, null, "reset"), }) ) ) @@ -297,6 +297,6 @@ function openConfig() { Spicetify.PopupModal.display({ title: Spicetify.Locale.get("new_releases"), - content: configContainer + content: configContainer, }); } diff --git a/CustomApps/new-releases/index.js b/CustomApps/new-releases/index.js index f336911372..7e6b4e19ff 100644 --- a/CustomApps/new-releases/index.js +++ b/CustomApps/new-releases/index.js @@ -8,7 +8,7 @@ const { React: { useState, useEffect, useCallback }, ReactDOM: reactDOM, Platform: { History }, - CosmosAsync + CosmosAsync, } = Spicetify; // Define a function called "render" to specify app entry point @@ -27,7 +27,7 @@ const APP_NAME = "new-releases"; const CONFIG = { visual: { type: getConfig("new-releases:visual:type", true), - count: getConfig("new-releases:visual:count", true) + count: getConfig("new-releases:visual:count", true), }, podcast: getConfig("new-releases:podcast", false), music: getConfig("new-releases:music", true), @@ -37,7 +37,7 @@ const CONFIG = { compilations: getConfig("new-releases:compilations", false), range: localStorage.getItem("new-releases:range") || "30", locale: localStorage.getItem("new-releases:locale") || navigator.language, - relative: getConfig("new-releases:relative", false) + relative: getConfig("new-releases:relative", false), }; let dismissed; @@ -61,10 +61,10 @@ let limitInMs = CONFIG.range * DAY_DIVIDER; const dateFormat = { year: "numeric", month: "short", - day: "2-digit" + day: "2-digit", }; const relativeDateFormat = { - numeric: "auto" + numeric: "auto", }; let separatedByDate = {}; let dateList = []; @@ -76,20 +76,20 @@ class Grid extends react.Component { super(); this.state = { cards: [], - rest: true + rest: true, }; } updatePostsVisual() { gridList = []; for (const date of dateList) { - if (separatedByDate[date].every(card => dismissed.includes(card.props.uri))) continue; + if (separatedByDate[date].every((card) => dismissed.includes(card.props.uri))) continue; gridList.push( react.createElement( "div", { - className: "new-releases-header" + className: "new-releases-header", }, react.createElement("h2", null, date) ), @@ -100,12 +100,12 @@ class Grid extends react.Component { style: { "--min-container-width": "180px", "--column-count": "auto-fill", - "--grid-gap": "18px" - } + "--grid-gap": "18px", + }, }, separatedByDate[date] - .filter(card => !dismissed.includes(card.props.uri)) - .map(card => react.createElement(Card, { ...card.props, key: card.props.uri })) + .filter((card) => !dismissed.includes(card.props.uri)) + .map((card) => react.createElement(Card, { ...card.props, key: card.props.uri })) ) ); } @@ -121,7 +121,7 @@ class Grid extends react.Component { case "undo": if (!dismissed[0]) Spicetify.showNotification("Nothing to undo", true); else Spicetify.showNotification("Undone dismissal"); - dismissed = id ? dismissed.filter(item => item !== id) : dismissed.slice(0, -1); + dismissed = id ? dismissed.filter((item) => item !== id) : dismissed.slice(0, -1); break; default: dismissed.push(id); @@ -177,13 +177,13 @@ class Grid extends react.Component { } for (const date of dateList) { - if (separatedByDate[date].every(card => dismissed.includes(card.props.uri))) continue; + if (separatedByDate[date].every((card) => dismissed.includes(card.props.uri))) continue; gridList.push( react.createElement( "div", { - className: "new-releases-header" + className: "new-releases-header", }, react.createElement("h2", null, date) ), @@ -194,10 +194,10 @@ class Grid extends react.Component { style: { "--min-container-width": "180px", "--column-count": "auto-fill", - "--grid-gap": "18px" - } + "--grid-gap": "18px", + }, }, - separatedByDate[date].filter(card => !dismissed.includes(card.props.uri)) + separatedByDate[date].filter((card) => !dismissed.includes(card.props.uri)) ) ); } @@ -240,26 +240,26 @@ class Grid extends react.Component { return react.createElement( "section", { - className: "contentSpacing" + className: "contentSpacing", }, react.createElement( "div", { - className: "new-releases-header" + className: "new-releases-header", }, react.createElement("h1", null, Spicetify.Locale.get("new_releases")), react.createElement( "div", { - className: "new-releases-controls-container" + className: "new-releases-controls-container", }, react.createElement(ButtonText, { text: Spicetify.Locale.get("playlist.extender.refresh"), - onClick: this.reload.bind(this) + onClick: this.reload.bind(this), }), react.createElement(ButtonText, { text: "undo", // no locale for this - onClick: this.removeCards.bind(this, null, "undo") + onClick: this.removeCards.bind(this, null, "undo"), }) ) ), @@ -281,22 +281,22 @@ async function getArtistEverything(artist) { uri: artist.uri, offset: 0, // Limit 100 since GraphQL has resource limit - limit: 100 + limit: 100, }); if (errors) throw errors; - const releases = data?.artistUnion.discography.all.items.flatMap(r => r.releases.items); + const releases = data?.artistUnion.discography.all.items.flatMap((r) => r.releases.items); const items = []; const types = [ - [CONFIG.album, releases.filter(r => r.type === "ALBUM"), Spicetify.Locale.get("album")], + [CONFIG.album, releases.filter((r) => r.type === "ALBUM"), Spicetify.Locale.get("album")], // Appears on has a separate GraphQL query but does not provide enough information (release date), which requires recursively making requests for each album // [CONFIG["appears-on"], releases.appears_on?.releases, Spicetify.Locale.get("artist.appears-on")], - [CONFIG.compilations, releases.filter(r => r.type === "COMPILATION"), Spicetify.Locale.get("compilation")], + [CONFIG.compilations, releases.filter((r) => r.type === "COMPILATION"), Spicetify.Locale.get("compilation")], [ CONFIG["single-ep"], - releases.filter(r => r.type === "SINGLE" || r.type === "EP"), - `${Spicetify.Locale.get("single")}/${Spicetify.Locale.get("ep")}` - ] + releases.filter((r) => r.type === "SINGLE" || r.type === "EP"), + `${Spicetify.Locale.get("single")}/${Spicetify.Locale.get("ep")}`, + ], ]; for (const type of types) { if (type[0] && type[1]) { @@ -318,7 +318,7 @@ async function getPodcastList() { async function getPodcastRelease(uri) { const body = await CosmosAsync.get(`sp://core-show/v1/shows/${uri}?responseFormat=protobufJson`, { - policy: { list: { link: true, name: true, publishDate: true } } + policy: { list: { link: true, name: true, publishDate: true } }, }); return body.items; } @@ -331,11 +331,11 @@ function metaFromTrack(artist, track) { title: track.name, artist: { name: artist.name, - uri: artist.uri + uri: artist.uri, }, imageURL: track.coverArt.sources.reduce((prev, curr) => (prev.width > curr.width ? prev : curr)).url, time, - trackCount: track.tracks.totalCount + trackCount: track.tracks.totalCount, }; } return null; @@ -353,8 +353,8 @@ async function fetchTracks() { const artistList = await getArtistList(); Spicetify.showNotification(`Fetching releases from ${artistList.length} artists`); - const requests = artistList.map(async obj => { - return await getArtistEverything(obj).catch(err => { + const requests = artistList.map(async (obj) => { + return await getArtistEverything(obj).catch((err) => { console.debug("Could not fetch all releases", err); console.debug(`Missing releases from ${count()} artists`); }); @@ -385,11 +385,11 @@ async function fetchPodcasts() { title: track.episodeMetadata.name, artist: { name: podcast.name, - uri: podcast.link + uri: podcast.link, }, imageURL: podcast.covers.standardLink, time, - type: itemTypeStr + type: itemTypeStr, }); } } diff --git a/CustomApps/reddit/Card.js b/CustomApps/reddit/Card.js index 0256990b25..f36022e271 100644 --- a/CustomApps/reddit/Card.js +++ b/CustomApps/reddit/Card.js @@ -33,17 +33,17 @@ class Card extends react.Component { getSubtitle() { let subtitle; if ((this.uriType === URI.Type.ALBUM || this.uriType === URI.Type.TRACK) && Array.isArray(this.subtitle)) { - subtitle = this.subtitle.map(artist => { + subtitle = this.subtitle.map((artist) => { const artistHref = URI.fromString(artist.uri).toURLPath(true); return react.createElement( "a", { href: artistHref, - onClick: event => { + onClick: (event) => { event.preventDefault(); event.stopPropagation(); History.push(artistHref); - } + }, }, react.createElement("span", null, artist.name) ); @@ -55,7 +55,7 @@ class Card extends react.Component { "div", { className: `${this.visual.longDescription ? "reddit-longDescription " : ""}main-cardSubHeader-root main-type-mesto reddit-cardSubHeader`, - as: "div" + as: "div", }, react.createElement("span", null, this.subtitle) ); @@ -63,7 +63,7 @@ class Card extends react.Component { return react.createElement( "div", { - className: "reddit-cardSubHeader main-type-mesto" + className: "reddit-cardSubHeader main-type-mesto", }, subtitle ); @@ -75,7 +75,7 @@ class Card extends react.Component { "div", { className: "main-cardSubHeader-root main-type-mestoBold reddit-cardSubHeader", - as: "div" + as: "div", }, react.createElement("span", null, Spicetify.Locale.get("user.followers", this.followersCount)) ); @@ -90,32 +90,32 @@ class Card extends react.Component { return react.createElement( Spicetify.ReactComponent.RightClickMenu || "div", { - menu: react.createElement(this.menuType, { uri: this.uri }) + menu: react.createElement(this.menuType, { uri: this.uri }), }, react.createElement( "div", { className: "main-card-card", - onClick: event => { + onClick: (event) => { History.push(this.href); event.preventDefault(); - } + }, }, react.createElement( "div", { className: "main-card-draggable", - draggable: "true" + draggable: "true", }, react.createElement( "div", { - className: "main-card-imageContainer" + className: "main-card-imageContainer", }, react.createElement( "div", { - className: "main-cardImage-imageWrapper" + className: "main-cardImage-imageWrapper", }, react.createElement( "div", @@ -125,14 +125,14 @@ class Card extends react.Component { draggable: "false", loading: "lazy", src: this.imageURL, - className: "main-image-image main-cardImage-image" + className: "main-image-image main-cardImage-image", }) ) ), react.createElement( "div", { - className: "main-card-PlayButtonContainer" + className: "main-card-PlayButtonContainer", }, react.createElement( "div", @@ -140,7 +140,7 @@ class Card extends react.Component { className: "main-playButton-PlayButton main-playButton-primary", "aria-label": Spicetify.Locale.get("play"), style: { "--size": "40px" }, - onClick: this.play.bind(this) + onClick: this.play.bind(this), }, react.createElement( "button", @@ -155,11 +155,11 @@ class Card extends react.Component { role: "img", width: "24", viewBox: "0 0 24 24", - "aria-hidden": "true" + "aria-hidden": "true", }, react.createElement("polygon", { points: "21.57 12 5.98 3 5.98 21 21.57 12", - fill: "currentColor" + fill: "currentColor", }) ) ) @@ -170,7 +170,7 @@ class Card extends react.Component { react.createElement( "div", { - className: "main-card-cardMetadata" + className: "main-card-cardMetadata", }, react.createElement( "a", @@ -179,13 +179,13 @@ class Card extends react.Component { title: this.title, className: "main-cardHeader-link", dir: "auto", - href: this.href + href: this.href, }, react.createElement( "div", { className: "main-cardHeader-text main-type-balladBold", - as: "div" + as: "div", }, this.title ) @@ -195,7 +195,7 @@ class Card extends react.Component { "div", { className: "main-cardSubHeader-root main-type-mestoBold reddit-cardSubHeader", - as: "div" + as: "div", }, react.createElement("span", null, detail.join(" ‒ ")) ), diff --git a/CustomApps/reddit/Icons.js b/CustomApps/reddit/Icons.js index 9016d724fe..f349c61ed8 100644 --- a/CustomApps/reddit/Icons.js +++ b/CustomApps/reddit/Icons.js @@ -6,7 +6,7 @@ class LoadingIcon extends react.Component { width: "100px", height: "100px", viewBox: "0 0 100 100", - preserveAspectRatio: "xMidYMid" + preserveAspectRatio: "xMidYMid", }, react.createElement( "circle", @@ -16,7 +16,7 @@ class LoadingIcon extends react.Component { r: "0", fill: "none", stroke: "currentColor", - "stroke-width": "2" + "stroke-width": "2", }, react.createElement("animate", { attributeName: "r", @@ -26,7 +26,7 @@ class LoadingIcon extends react.Component { keyTimes: "0;1", keySplines: "0 0.2 0.8 1", calcMode: "spline", - begin: "0s" + begin: "0s", }), react.createElement("animate", { attributeName: "opacity", @@ -36,7 +36,7 @@ class LoadingIcon extends react.Component { keyTimes: "0;1", keySplines: "0.2 0 0.8 1", calcMode: "spline", - begin: "0s" + begin: "0s", }) ), react.createElement( @@ -47,7 +47,7 @@ class LoadingIcon extends react.Component { r: "0", fill: "none", stroke: "currentColor", - "stroke-width": "2" + "stroke-width": "2", }, react.createElement("animate", { attributeName: "r", @@ -57,7 +57,7 @@ class LoadingIcon extends react.Component { keyTimes: "0;1", keySplines: "0 0.2 0.8 1", calcMode: "spline", - begin: "-0.5s" + begin: "-0.5s", }), react.createElement("animate", { attributeName: "opacity", @@ -67,7 +67,7 @@ class LoadingIcon extends react.Component { keyTimes: "0;1", keySplines: "0.2 0 0.8 1", calcMode: "spline", - begin: "-0.5s" + begin: "-0.5s", }) ) ); @@ -79,15 +79,15 @@ class LoadMoreIcon extends react.Component { return react.createElement( "div", { - onClick: this.props.onClick + onClick: this.props.onClick, }, react.createElement( "p", { style: { fontSize: 100, - lineHeight: "65px" - } + lineHeight: "65px", + }, }, "»" ), @@ -95,8 +95,8 @@ class LoadMoreIcon extends react.Component { "span", { style: { - fontSize: 20 - } + fontSize: 20, + }, }, "Load more" ) diff --git a/CustomApps/reddit/OptionsMenu.js b/CustomApps/reddit/OptionsMenu.js index b18fe60303..5f4ad345be 100644 --- a/CustomApps/reddit/OptionsMenu.js +++ b/CustomApps/reddit/OptionsMenu.js @@ -4,10 +4,10 @@ const OptionsMenuItemIcon = react.createElement( width: 16, height: 16, viewBox: "0 0 16 16", - fill: "currentColor" + fill: "currentColor", }, react.createElement("path", { - d: "M13.985 2.383L5.127 12.754 1.388 8.375l-.658.77 4.397 5.149 9.618-11.262z" + d: "M13.985 2.383L5.127 12.754 1.388 8.375l-.658.77 4.397 5.149 9.618-11.262z", }) ); @@ -17,7 +17,7 @@ const OptionsMenuItem = react.memo(({ onSelect, value, isSelected }) => { { onClick: onSelect, icon: isSelected ? OptionsMenuItemIcon : null, - trailingIcon: isSelected ? OptionsMenuItemIcon : null + trailingIcon: isSelected ? OptionsMenuItemIcon : null, }, value ); @@ -49,24 +49,24 @@ const OptionsMenu = react.memo(({ options, onSelect, selected, defaultValue, bol // Close menu on item click menuRef.current?.click(); }, - isSelected: selected?.key === key + isSelected: selected?.key === key, }) ) ), trigger: "click", action: "toggle", - renderInline: true + renderInline: true, }, react.createElement( "button", { className: "optionsMenu-dropBox", - ref: menuRef + ref: menuRef, }, react.createElement( "span", { - className: bold ? "main-type-mestoBold" : "main-type-mesto" + className: bold ? "main-type-mestoBold" : "main-type-mesto", }, selected?.value || defaultValue ), @@ -76,10 +76,10 @@ const OptionsMenu = react.memo(({ options, onSelect, selected, defaultValue, bol height: "16", width: "16", fill: "currentColor", - viewBox: "0 0 16 16" + viewBox: "0 0 16 16", }, react.createElement("path", { - d: "M3 6l5 5.794L13 6z" + d: "M3 6l5 5.794L13 6z", }) ) ) diff --git a/CustomApps/reddit/Settings.js b/CustomApps/reddit/Settings.js index 3848eb21d4..112caec47a 100644 --- a/CustomApps/reddit/Settings.js +++ b/CustomApps/reddit/Settings.js @@ -4,7 +4,7 @@ function openConfig() { if (configContainer) { Spicetify.PopupModal.display({ title: "Reddit", - content: configContainer + content: configContainer, }); return; } @@ -48,7 +48,7 @@ function openConfig() { function posCallback(el, dir) { const id = el.dataset.id; - const curPos = CONFIG.services.findIndex(val => val === id); + const curPos = CONFIG.services.findIndex((val) => val === id); const newPos = curPos + dir; if (CONFIG.services.length > 1) { @@ -64,7 +64,7 @@ function openConfig() { function removeCallback(el) { const id = el.dataset.id; - CONFIG.services = CONFIG.services.filter(s => s !== id); + CONFIG.services = CONFIG.services.filter((s) => s !== id); CONFIG.servicesElement[id].remove(); localStorage.setItem("reddit:services", JSON.stringify(CONFIG.services)); @@ -79,7 +79,7 @@ function openConfig() { const serviceInput = document.createElement("input"); serviceInput.placeholder = "Add new subreddit"; - serviceInput.onkeydown = event => { + serviceInput.onkeydown = (event) => { if (event.key !== "Enter") { return; } @@ -111,7 +111,7 @@ function openConfig() { Spicetify.PopupModal.display({ title: "Reddit", - content: configContainer + content: configContainer, }); } diff --git a/CustomApps/reddit/SortBox.js b/CustomApps/reddit/SortBox.js index 16715c172b..2e056f3b91 100644 --- a/CustomApps/reddit/SortBox.js +++ b/CustomApps/reddit/SortBox.js @@ -6,7 +6,7 @@ class SortBox extends react.Component { { key: "new", value: "New" }, { key: "top", value: "Top" }, { key: "rising", value: "Rising" }, - { key: "controversial", value: "Controversial" } + { key: "controversial", value: "Controversial" }, ]; this.sortTimeOptions = [ { key: "hour", value: "Hour" }, @@ -14,34 +14,34 @@ class SortBox extends react.Component { { key: "week", value: "Week" }, { key: "month", value: "Month" }, { key: "year", value: "Year" }, - { key: "all", value: "All" } + { key: "all", value: "All" }, ]; } render() { - const sortBySelected = this.sortByOptions.filter(a => a.key === sortConfig.by)[0]; - const sortTimeSelected = this.sortTimeOptions.filter(a => a.key === sortConfig.time)[0]; + const sortBySelected = this.sortByOptions.filter((a) => a.key === sortConfig.by)[0]; + const sortTimeSelected = this.sortTimeOptions.filter((a) => a.key === sortConfig.time)[0]; return react.createElement( "div", { - className: "reddit-sort-bar" + className: "reddit-sort-bar", }, react.createElement( "div", { - className: "reddit-sort-container" + className: "reddit-sort-container", }, react.createElement(OptionsMenu, { options: this.sortByOptions, - onSelect: by => this.props.onChange(by, null), - selected: sortBySelected + onSelect: (by) => this.props.onChange(by, null), + selected: sortBySelected, }), !!sortConfig.by.match(/top|controversial/) && react.createElement(OptionsMenu, { options: this.sortTimeOptions, - onSelect: time => this.props.onChange(null, time), - selected: sortTimeSelected + onSelect: (time) => this.props.onChange(null, time), + selected: sortTimeSelected, }) ) ); diff --git a/CustomApps/reddit/TabBar.js b/CustomApps/reddit/TabBar.js index 37731c896a..bb146873be 100644 --- a/CustomApps/reddit/TabBar.js +++ b/CustomApps/reddit/TabBar.js @@ -4,10 +4,10 @@ class TabBarItem extends react.Component { "li", { className: "reddit-tabBar-headerItem", - onClick: event => { + onClick: (event) => { event.preventDefault(); this.props.switchTo(this.props.item.key); - } + }, }, react.createElement( "a", @@ -15,12 +15,12 @@ class TabBarItem extends react.Component { "aria-current": "page", className: `reddit-tabBar-headerItemLink ${this.props.item.active ? "reddit-tabBar-active" : ""}`, draggable: "false", - href: "" + href: "", }, react.createElement( "span", { - className: "main-type-mestoBold" + className: "main-type-mestoBold", }, this.props.item.value ) @@ -30,19 +30,19 @@ class TabBarItem extends react.Component { } const TabBarMore = react.memo(({ items, switchTo }) => { - const activeItem = items.find(item => item.active); + const activeItem = items.find((item) => item.active); return react.createElement( "li", { - className: `reddit-tabBar-headerItem ${activeItem ? "reddit-tabBar-active" : ""}` + className: `reddit-tabBar-headerItem ${activeItem ? "reddit-tabBar-active" : ""}`, }, react.createElement(OptionsMenu, { options: items, onSelect: switchTo, selected: activeItem, defaultValue: "More", - bold: true + bold: true, }) ); }); @@ -69,7 +69,7 @@ const TopBarContent = ({ links, activeLink, switchCallback }) => { links, activeLink, windowSize, - switchCallback + switchCallback, }) ); }; @@ -79,7 +79,7 @@ const TabBarContext = ({ children }) => { react.createElement( "div", { - className: "main-topBar-topbarContent" + className: "main-topBar-topbarContent", }, children ), @@ -93,7 +93,7 @@ const TabBar = react.memo(({ links, activeLink, switchCallback, windowSize = Num const [availableSpace, setAvailableSpace] = useState(0); const [droplistItem, setDroplistItems] = useState([]); - const options = links.map(key => { + const options = links.map((key) => { const active = key === activeLink; return { key, value: key, active }; }); @@ -107,7 +107,7 @@ const TabBar = react.memo(({ links, activeLink, switchCallback, windowSize = Num if (!tabBarRef.current) return; const children = Array.from(tabBarRef.current.children); - const tabbarItemSizes = children.map(child => child.clientWidth); + const tabbarItemSizes = children.map((child) => child.clientWidth); setChildrenSizes(tabbarItemSizes); }, [links]); @@ -147,26 +147,26 @@ const TabBar = react.memo(({ links, activeLink, switchCallback, windowSize = Num return react.createElement( "nav", { - className: "reddit-tabBar reddit-tabBar-nav" + className: "reddit-tabBar reddit-tabBar-nav", }, react.createElement( "ul", { className: "reddit-tabBar-header", - ref: tabBarRef + ref: tabBarRef, }, options .filter((_, id) => !droplistItem.includes(id)) - .map(item => + .map((item) => react.createElement(TabBarItem, { item, - switchTo: switchCallback + switchTo: switchCallback, }) ), droplistItem.length || childrenSizes.length === 0 ? react.createElement(TabBarMore, { - items: droplistItem.map(i => options[i]).filter(Boolean), - switchTo: switchCallback + items: droplistItem.map((i) => options[i]).filter(Boolean), + switchTo: switchCallback, }) : null ) diff --git a/CustomApps/reddit/index.js b/CustomApps/reddit/index.js index 8af0cdc4f4..b5e0d7ef9b 100644 --- a/CustomApps/reddit/index.js +++ b/CustomApps/reddit/index.js @@ -9,7 +9,7 @@ const reactDOM = Spicetify.ReactDOM; const { URI, React: { useState, useEffect, useCallback }, - Platform: { History } + Platform: { History }, } = Spicetify; // Define a function called "render" to specify app entry point @@ -23,10 +23,10 @@ const CONFIG = { type: localStorage.getItem("reddit:type") === "true", upvotes: localStorage.getItem("reddit:upvotes") === "true", followers: localStorage.getItem("reddit:followers") === "true", - longDescription: localStorage.getItem("reddit:longDescription") === "true" + longDescription: localStorage.getItem("reddit:longDescription") === "true", }, services: localStorage.getItem("reddit:services") || `["spotify","makemeaplaylist","SpotifyPlaylists","music","edm","popheads"]`, - lastService: localStorage.getItem("reddit:last-service") + lastService: localStorage.getItem("reddit:last-service"), }; try { @@ -44,7 +44,7 @@ if (!CONFIG.lastService || !CONFIG.services.includes(CONFIG.lastService)) { } const sortConfig = { by: localStorage.getItem("reddit:sort-by") || "top", - time: localStorage.getItem("reddit:sort-time") || "month" + time: localStorage.getItem("reddit:sort-time") || "month", }; let cardList = []; let endOfList = false; @@ -58,7 +58,7 @@ let gridUpdatePostsVisual; const typesLocale = { album: Spicetify.Locale.get("album"), song: Spicetify.Locale.get("song"), - playlist: Spicetify.Locale.get("playlist") + playlist: Spicetify.Locale.get("playlist"), }; class Grid extends react.Component { @@ -71,7 +71,7 @@ class Grid extends react.Component { cards: [], tabs: CONFIG.services, rest: true, - endOfList: endOfList + endOfList: endOfList, }; } @@ -103,7 +103,7 @@ class Grid extends react.Component { this.setState({ cards: [], rest: false, - endOfList: false + endOfList: false, }); endOfList = false; @@ -112,12 +112,12 @@ class Grid extends react.Component { updateTabs() { this.setState({ - tabs: [...CONFIG.services] + tabs: [...CONFIG.services], }); } updatePostsVisual() { - cardList = cardList.map(card => { + cardList = cardList.map((card) => { return react.createElement(Card, card.props); }); this.setState({ cards: [...cardList] }); @@ -131,7 +131,7 @@ class Grid extends react.Component { this.setState({ cards: [], rest: false, - endOfList: false + endOfList: false, }); endOfList = false; @@ -183,7 +183,7 @@ class Grid extends react.Component { } if (requestAfter === -1) { - requestQueue = requestQueue.filter(a => a !== queue); + requestQueue = requestQueue.filter((a) => a !== queue); return; } @@ -240,17 +240,17 @@ class Grid extends react.Component { return react.createElement( "section", { - className: "contentSpacing" + className: "contentSpacing", }, react.createElement( "div", { - className: "reddit-header" + className: "reddit-header", }, react.createElement("h1", null, this.props.title), react.createElement(SortBox, { onChange: this.updateSort.bind(this), - onServicesChange: this.updateTabs.bind(this) + onServicesChange: this.updateTabs.bind(this), }) ), react.createElement( @@ -262,8 +262,8 @@ class Grid extends react.Component { "--minimumColumnWidth": "180px", "--column-width": "minmax(var(--minimumColumnWidth),1fr)", "--column-count": "auto-fill", - "--grid-gap": "24px" - } + "--grid-gap": "24px", + }, }, [...cardList] ), @@ -272,8 +272,8 @@ class Grid extends react.Component { { style: { margin: "auto", - textAlign: "center" - } + textAlign: "center", + }, }, !this.state.endOfList && (this.state.rest ? react.createElement(LoadMoreIcon, { onClick: this.loadMore.bind(this) }) : react.createElement(LoadingIcon)) @@ -282,7 +282,7 @@ class Grid extends react.Component { react.createElement(TopBarContent, { switchCallback: this.switchTo.bind(this), links: CONFIG.services, - activeLink: CONFIG.lastService + activeLink: CONFIG.lastService, }) ); } @@ -298,7 +298,7 @@ async function getSubreddit(after = "") { url += `&t=${sortConfig.time}`; } - return await fetch(url, { method: "GET" }).then(res => res.json()); + return await fetch(url, { method: "GET" }).then((res) => res.json()); } async function fetchPlaylist(post) { @@ -307,8 +307,8 @@ async function fetchPlaylist(post) { policy: { name: true, picture: true, - followers: true - } + followers: true, + }, }); const { metadata } = res; @@ -319,7 +319,7 @@ async function fetchPlaylist(post) { subtitle: post.title, imageURL: metadata.picture, upvotes: post.upvotes, - followersCount: metadata.followers + followersCount: metadata.followers, }; } catch { return null; @@ -330,16 +330,21 @@ async function fetchAlbum(post) { const { getAlbum } = Spicetify.GraphQL.Definitions; try { - const { data } = await Spicetify.GraphQL.Request(getAlbum, { uri: post.uri, locale: Spicetify.Locale.getLocale(), offset: 0, limit: 10 }); + const { data } = await Spicetify.GraphQL.Request(getAlbum, { + uri: post.uri, + locale: Spicetify.Locale.getLocale(), + offset: 0, + limit: 10, + }); const metadata = data.albumUnion; return { type: typesLocale.album, uri: post.uri, title: metadata.name, - subtitle: metadata.artists.items.map(artist => artist.profile.name).join(", "), + subtitle: metadata.artists.items.map((artist) => artist.profile.name).join(", "), imageURL: metadata.coverArt.sources.reduce((prev, curr) => (prev.width > curr.width ? prev : curr)).url, - upvotes: post.upvotes + upvotes: post.upvotes, }; } catch { return null; @@ -356,7 +361,7 @@ async function fetchTrack(post) { title: metadata.name, subtitle: metadata.artists, imageURL: metadata.album.images[0].url, - upvotes: post.upvotes + upvotes: post.upvotes, }; } catch { return null; @@ -372,7 +377,7 @@ function postMapper(posts) { uri: uri.toURI(), type: uri.type, title: post.data.title, - upvotes: post.data.ups + upvotes: post.data.ups, }); } } diff --git a/Extensions/autoSkipExplicit.js b/Extensions/autoSkipExplicit.js index 5956976bf5..a11f46212b 100644 --- a/Extensions/autoSkipExplicit.js +++ b/Extensions/autoSkipExplicit.js @@ -12,7 +12,7 @@ let isEnabled = Spicetify.LocalStorage.get("ChristianMode") === "1"; - new Spicetify.Menu.Item("Christian mode", isEnabled, self => { + new Spicetify.Menu.Item("Christian mode", isEnabled, (self) => { isEnabled = !isEnabled; Spicetify.LocalStorage.set("ChristianMode", isEnabled ? "1" : "0"); self.setState(isEnabled); diff --git a/Extensions/bookmark.js b/Extensions/bookmark.js index e4c67e02d6..8b2195e376 100644 --- a/Extensions/bookmark.js +++ b/Extensions/bookmark.js @@ -40,7 +40,7 @@ this.items.append(createMenuItem("Track with timestamp", storeTrackWithTime)); const select = createSortSelect(this.filter); - select.onchange = event => { + select.onchange = (event) => { this.filter = event.srcElement.selectedIndex; this.apply(); }; @@ -87,7 +87,7 @@ } removeFromStorage(id) { - const storage = this.getStorage().filter(item => item.id !== id); + const storage = this.getStorage().filter((item) => item.id !== id); LocalStorage.set(STORAGE_KEY, JSON.stringify(storage)); this.apply(); @@ -161,7 +161,7 @@ if (isPlayable) { /** @type {HTMLButtonElement} */ const playButton = this.querySelector("button.main-playButton-PlayButton"); - playButton.onclick = event => { + playButton.onclick = (event) => { onPlayClick(info); event.stopPropagation(); }; @@ -169,7 +169,7 @@ /** @type {HTMLDivElement} */ const controls = this.querySelector(".bookmark-controls"); - controls.onclick = event => { + controls.onclick = (event) => { LIST.removeFromStorage(info.id); event.stopPropagation(); }; @@ -185,7 +185,7 @@ new Spicetify.Topbar.Button( BUTTON_NAME_TEXT, ``, - self => { + (self) => { const bound = self.element.getBoundingClientRect(); LIST.changePosition(bound.left, bound.top); document.body.append(LIST.container); @@ -206,7 +206,7 @@ { onClick: () => { callback?.(); - } + }, }, title ), @@ -226,7 +226,7 @@ const trackOpt = document.createElement("option"); trackOpt.text = "Track"; - select.onclick = ev => ev.stopPropagation(); + select.onclick = (ev) => ev.stopPropagation(); select.append(allOpt, pageOpt, trackOpt); select.options[defaultOpt].selected = true; @@ -282,14 +282,14 @@ title, description, imageUrl, - context + context, }); } function getTrackMeta() { const meta = { title: Player.data.item.metadata.title, - imageUrl: Player.data.item.metadata.image_url + imageUrl: Player.data.item.metadata.image_url, }; meta.uri = Player.data.item.uri; if (URI.isEpisode(meta.uri)) { @@ -319,7 +319,7 @@ // Utilities function idToProperName(id) { - const newId = id.replace(/\-/g, " ").replace(/^.|\s./g, char => char.toUpperCase()); + const newId = id.replace(/\-/g, " ").replace(/^.|\s./g, (char) => char.toUpperCase()); return newId; } @@ -437,7 +437,7 @@ const menu = document.createElement("ul"); menu.id = "bookmark-menu"; menu.className = "main-contextMenu-menu"; - menu.onclick = e => e.stopPropagation(); + menu.onclick = (e) => e.stopPropagation(); container.append(style, menu); @@ -474,37 +474,42 @@ Spicetify.Player.playUri(uri, {}, options); } - const fetchAlbum = async uri => { + const fetchAlbum = async (uri) => { const { getAlbum } = Spicetify.GraphQL.Definitions; - const { data } = await Spicetify.GraphQL.Request(getAlbum, { uri, locale: Spicetify.Locale.getLocale(), offset: 0, limit: 10 }); + const { data } = await Spicetify.GraphQL.Request(getAlbum, { + uri, + locale: Spicetify.Locale.getLocale(), + offset: 0, + limit: 10, + }); const res = data.albumUnion; return { uri, title: res.name, description: "Album", - imageUrl: res.coverArt.sources.reduce((prev, curr) => (prev.width > curr.width ? prev : curr)).url + imageUrl: res.coverArt.sources.reduce((prev, curr) => (prev.width > curr.width ? prev : curr)).url, }; }; - const fetchShow = async uri => { + const fetchShow = async (uri) => { const base62 = uri.split(":")[2]; const res = await CosmosAsync.get(`sp://core-show/v1/shows/${base62}?responseFormat=protobufJson`, { - policy: { list: { index: true } } + policy: { list: { index: true } }, }); return { uri, title: res.header.showMetadata.name, description: "Podcast", - imageUrl: res.header.showMetadata.covers.standardLink + imageUrl: res.header.showMetadata.covers.standardLink, }; }; - const fetchArtist = async uri => { + const fetchArtist = async (uri) => { const { queryArtistOverview } = Spicetify.GraphQL.Definitions; const { data } = await Spicetify.GraphQL.Request(queryArtistOverview, { uri, locale: Spicetify.Locale.getLocale(), - includePrerelease: false + includePrerelease: false, }); const res = data.artistUnion; return { @@ -513,7 +518,7 @@ description: "Artist", imageUrl: res.visuals.avatarImage?.sources.reduce((prev, curr) => (prev.width > curr.width ? prev : curr)).url || - res.visuals.headerImage?.sources[0].url + res.visuals.headerImage?.sources[0].url, }; }; @@ -529,11 +534,11 @@ title: res.name, description: res.artists[0].name, imageUrl: res.album.images[0].url, - context: newContext ?? context + context: newContext ?? context, }; }; - const fetchEpisode = async uri => { + const fetchEpisode = async (uri) => { const base62 = uri.split(":")[2]; const res = await CosmosAsync.get(`https://api.spotify.com/v1/episodes/${base62}`); console.log(res); @@ -541,19 +546,19 @@ uri, title: res.name, description: `${res.show.name} episode`, - imageUrl: res.show.images[0].url + imageUrl: res.show.images[0].url, }; }; - const fetchPlaylist = async uri => { + const fetchPlaylist = async (uri) => { const res = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri}/metadata`, { - policy: { picture: true, name: true } + policy: { picture: true, name: true }, }); return { uri, title: res.metadata.name, description: "Playlist", - imageUrl: res.metadata.picture + imageUrl: res.metadata.picture, }; }; diff --git a/Extensions/fullAppDisplay.js b/Extensions/fullAppDisplay.js index c50b1b6481..b0642cc531 100644 --- a/Extensions/fullAppDisplay.js +++ b/Extensions/fullAppDisplay.js @@ -233,7 +233,7 @@ body.video-full-screen.video-full-screen--hide-ui { min-width: 56px; margin-right: 10px; text-align: right; -}` +}`, ]; const lyricsPlusBase = ` @@ -259,7 +259,7 @@ body.video-full-screen.video-full-screen--hide-ui { max-width: 210px; margin-left: 50px; }`, - "" + "", ]; updateStyle(); @@ -270,8 +270,8 @@ body.video-full-screen.video-full-screen--hide-ui { viewBox: "0 0 16 16", fill: "currentColor", dangerouslySetInnerHTML: { - __html: icon - } + __html: icon, + }, }); }; @@ -279,7 +279,7 @@ body.video-full-screen.video-full-screen--hide-ui { return react.createElement( "div", { - id + id, }, CONFIG.icons && react.createElement(DisplayIcon, { icon, size: 35 }), react.createElement("span", null, text) @@ -290,7 +290,7 @@ body.video-full-screen.video-full-screen--hide-ui { return react.createElement( "button", { - onClick + onClick, }, react.createElement(DisplayIcon, { icon, size: 20 }) ); @@ -314,8 +314,8 @@ body.video-full-screen.video-full-screen--hide-ui { react.createElement("div", { id: "fad-progress-inner", style: { - width: `${(value / duration) * 100}%` - } + width: `${(value / duration) * 100}%`, + }, }) ), react.createElement("span", { id: "fad-duration" }, Spicetify.Player.formatTime(duration)) @@ -334,15 +334,15 @@ body.video-full-screen.video-full-screen--hide-ui { { id: "fad-controls" }, react.createElement(ButtonIcon, { icon: Spicetify.SVGIcons["skip-back"], - onClick: Spicetify.Player.back + onClick: Spicetify.Player.back, }), react.createElement(ButtonIcon, { icon: Spicetify.SVGIcons[value ? "pause" : "play"], - onClick: Spicetify.Player.togglePlay + onClick: Spicetify.Player.togglePlay, }), react.createElement(ButtonIcon, { icon: Spicetify.SVGIcons["skip-forward"], - onClick: Spicetify.Player.next + onClick: Spicetify.Player.next, }) ); }; @@ -357,7 +357,7 @@ body.video-full-screen.video-full-screen--hide-ui { album: "", releaseDate: "", cover: "", - heart: Spicetify.Player.getHeart() + heart: Spicetify.Player.getHeart(), }; this.currTrackImg = new Image(); this.nextTrackImg = new Image(); @@ -373,7 +373,7 @@ body.video-full-screen.video-full-screen--hide-ui { return albumDate.toLocaleString("default", { year: "numeric", month: "short", - day: "numeric" + day: "numeric", }); } @@ -395,9 +395,9 @@ body.video-full-screen.video-full-screen--hide-ui { let artistName; if (CONFIG.showAllArtists) { artistName = Object.keys(meta) - .filter(key => key.startsWith("artist_name")) + .filter((key) => key.startsWith("artist_name")) .sort() - .map(key => meta[key]) + .map((key) => meta[key]) .join(", "); } else { artistName = meta.artist_name; @@ -420,7 +420,7 @@ body.video-full-screen.video-full-screen--hide-ui { title: rawTitle || "", artist: artistName || "", album: albumText || "", - releaseDate: releaseDate || "" + releaseDate: releaseDate || "", }); return; } @@ -438,7 +438,7 @@ body.video-full-screen.video-full-screen--hide-ui { artist: artistName || "", album: albumText || "", releaseDate: releaseDate || "", - cover: bgImage + cover: bgImage, }); }; this.currTrackImg.onerror = () => { @@ -505,8 +505,8 @@ body.video-full-screen.video-full-screen--hide-ui { this.fetchInfo(); }; - this.onQueueChange = async queue => { - queue = queue.data; + this.onQueueChange = async (queueData) => { + const queue = queueData.data; let nextTrack; if (queue.queued.length) { nextTrack = queue.queued[0]; @@ -517,7 +517,7 @@ body.video-full-screen.video-full-screen--hide-ui { }; const scaleLimit = { min: 0.1, max: 4, step: 0.05 }; - this.onScaleChange = event => { + this.onScaleChange = (event) => { if (!event.ctrlKey) return; const dir = event.deltaY < 0 ? 1 : -1; let temp = (CONFIG.scale || 1) + dir * scaleLimit.step; @@ -549,13 +549,13 @@ body.video-full-screen.video-full-screen--hide-ui { id: "full-app-display", className: "Video VideoPlayer--fullscreen VideoPlayer--landscape", onDoubleClick: deactivate, - onContextMenu: openConfig + onContextMenu: openConfig, }, react.createElement("canvas", { id: "fad-background", - ref: el => { + ref: (el) => { this.back = el; - } + }, }), react.createElement("div", { id: "fad-header" }), react.createElement( @@ -566,12 +566,12 @@ body.video-full-screen.video-full-screen--hide-ui { { id: "fad-foreground", style: { - "--fad-scale": CONFIG.scale || 1 + "--fad-scale": CONFIG.scale || 1, }, - ref: el => { + ref: (el) => { if (!el) return; el.onmousewheel = this.onScaleChange; - } + }, }, react.createElement( "div", @@ -582,13 +582,13 @@ body.video-full-screen.video-full-screen--hide-ui { id: "fad-art-image", className: CONFIG.enableFade && "fad-background-fade", style: { - backgroundImage: this.state.cover - } + backgroundImage: this.state.cover, + }, }, react.createElement( "div", { - id: "fad-art-overlay" + id: "fad-art-overlay", }, react.createElement( "button", @@ -597,16 +597,16 @@ body.video-full-screen.video-full-screen--hide-ui { onClick: () => { Spicetify.Player.toggleHeart(); this.setState({ heart: !this.state.heart }); - } + }, }, react.createElement(DisplayIcon, { icon: Spicetify.SVGIcons[this.state.heart ? "heart-active" : "heart"], - size: 50 + size: 50, }) ) ), react.createElement("div", { - id: "fad-art-inner" + id: "fad-art-inner", }) ) ), @@ -617,25 +617,25 @@ body.video-full-screen.video-full-screen--hide-ui { react.createElement(SubInfo, { id: "fad-artist", text: this.state.artist, - icon: Spicetify.SVGIcons.artist + icon: Spicetify.SVGIcons.artist, }), CONFIG.showAlbum && react.createElement(SubInfo, { id: "fad-album", text: this.state.album, - icon: Spicetify.SVGIcons.album + icon: Spicetify.SVGIcons.album, }), CONFIG.showReleaseDate && react.createElement(SubInfo, { id: "fad-release-date", text: this.state.releaseDate, - icon: Spicetify.SVGIcons.clock + icon: Spicetify.SVGIcons.clock, }), react.createElement( "div", { id: "fad-status", - className: (CONFIG.enableControl || CONFIG.enableProgress) && "active" + className: (CONFIG.enableControl || CONFIG.enableProgress) && "active", }, CONFIG.enableControl && react.createElement(PlayerControls), CONFIG.enableProgress && react.createElement(ProgressBar) @@ -647,8 +647,8 @@ body.video-full-screen.video-full-screen--hide-ui { id: "fad-lyrics-plus-container", style: { "--lyrics-color-active": "#ffffff", - "--lyrics-color-inactive": "#ffffff50" - } + "--lyrics-color-inactive": "#ffffff50", + }, }) ) ); @@ -798,11 +798,11 @@ body.video-full-screen.video-full-screen--hide-ui { setValue(state); saveConfig(); func(); - } + }, }, react.createElement(DisplayIcon, { icon: Spicetify.SVGIcons.check, - size: 16 + size: 16, }) ) ) @@ -847,8 +847,8 @@ button.switch.disabled, button.switch[disabled] { color: rgba(var(--spice-rgb-text), .3); } -` - } +`, + }, }); const configContainer = react.createElement( "div", @@ -861,62 +861,62 @@ button.switch[disabled] { updateVisual(); requestLyricsPlus(); }, - disabled: !checkLyricsPlus() + disabled: !checkLyricsPlus(), }), react.createElement(ConfigItem, { name: "Enable progress bar", field: "enableProgress", - func: updateVisual + func: updateVisual, }), react.createElement(ConfigItem, { name: "Enable controls", field: "enableControl", - func: updateVisual + func: updateVisual, }), react.createElement(ConfigItem, { name: "Trim title", field: "trimTitle", - func: updateVisual + func: updateVisual, }), react.createElement(ConfigItem, { name: "Show album", field: "showAlbum", - func: updateVisual + func: updateVisual, }), react.createElement(ConfigItem, { name: "Show all artists", field: "showAllArtists", - func: updateVisual + func: updateVisual, }), react.createElement(ConfigItem, { name: "Show release date", field: "showReleaseDate", - func: updateVisual + func: updateVisual, }), react.createElement(ConfigItem, { name: "Show icons", field: "icons", - func: updateVisual + func: updateVisual, }), react.createElement(ConfigItem, { name: "Vertical mode", field: "vertical", - func: updateStyle + func: updateStyle, }), react.createElement(ConfigItem, { name: "Enable fullscreen", field: "enableFullscreen", - func: toggleFullscreen + func: toggleFullscreen, }), react.createElement(ConfigItem, { name: "Enable song change animation", field: "enableFade", - func: updateVisual + func: updateVisual, }) ); Spicetify.PopupModal.display({ title: "Full App Display", - content: configContainer + content: configContainer, }); } diff --git a/Extensions/keyboardShortcut.js b/Extensions/keyboardShortcut.js index d7167e1520..80f367cac2 100644 --- a/Extensions/keyboardShortcut.js +++ b/Extensions/keyboardShortcut.js @@ -25,7 +25,7 @@ "ctrl+q": { callback: () => Spicetify.CosmosAsync.post("sp://esperanto/spotify.desktop.lifecycle_esperanto.proto.DesktopLifecycle/Shutdown") && - Spicetify.CosmosAsync.post("sp://desktop/v1/shutdown") + Spicetify.CosmosAsync.post("sp://desktop/v1/shutdown"), }, // Rotate through sidebar items using Ctrl+Tab and Ctrl+Shift+Tab @@ -64,17 +64,17 @@ // Activate Vim mode and set cancel key to 'ESCAPE' f: { - callback: event => { + callback: (event) => { vim.activate(event); vim.setCancelKey("ESCAPE"); - } - } + }, + }, }; // Bind all the keys for (const [key, { staticCondition, callback }] of Object.entries(binds)) { if (typeof staticCondition === "undefined" || staticCondition) { - Spicetify.Mousetrap.bind(key, event => { + Spicetify.Mousetrap.bind(key, (event) => { event.preventDefault(); if (!vim.isActive) { callback(event); @@ -86,7 +86,7 @@ // re-render vim on window resize & prevent mouse event while active window.addEventListener( "resize", - event => { + (event) => { if (vim.isActive) { vim.activate(); } @@ -96,7 +96,7 @@ window.addEventListener( "mousedown", - event => { + (event) => { if (vim.isActive) { event.stopPropagation(); } @@ -348,7 +348,7 @@ function VimBind() { const event = new MouseEvent("mouseover", { view: window, bubbles: true, - cancelable: true + cancelable: true, }); element.dispatchEvent(event); diff --git a/Extensions/loopyLoop.js b/Extensions/loopyLoop.js index 5aec1f393d..7b2b18d0fa 100644 --- a/Extensions/loopyLoop.js +++ b/Extensions/loopyLoop.js @@ -55,7 +55,7 @@ } let debouncing = 0; - Spicetify.Player.addEventListener("onprogress", event => { + Spicetify.Player.addEventListener("onprogress", (event) => { if (start != null && end != null) { if (debouncing) { if (event.timeStamp - debouncing > 1000) { @@ -83,7 +83,7 @@ onClick: () => { contextMenu.hidden = true; callback?.(); - } + }, }, title ), @@ -121,7 +121,7 @@ contextMenu.hidden = true; }); - bar.oncontextmenu = event => { + bar.oncontextmenu = (event) => { const { x, width } = bar.firstElementChild.getBoundingClientRect(); mouseOnBarPercent = (event.clientX - x) / width; contextMenu.style.transform = `translate(${event.clientX}px,${event.clientY - contextMenuHeight}px)`; diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index 7ab806a723..0660d30634 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -12,7 +12,7 @@ if (!navigator.serviceWorker) { // Offload setInterval to a Worker to consistently call tick function. let num = null; // biome-ignore lint/suspicious/noGlobalAssign: - onmessage = event => { + onmessage = (event) => { if (event.data === "popup-lyric-request-update") { console.warn("popup-lyric-request-update"); num = setInterval(() => postMessage("popup-lyric-update-ui"), 16.66); @@ -34,7 +34,7 @@ function PopupLyrics() { } const worker = new Worker("./extensions/popupLyrics.js"); - worker.onmessage = event => { + worker.onmessage = (event) => { if (event.data === "popup-lyric-update-ui") { tick(userConfigs); } @@ -74,8 +74,8 @@ function PopupLyrics() { }, capitalize(s) { - return s.replace(/^(\w)/, $1 => $1.toUpperCase()); - } + return s.replace(/^(\w)/, ($1) => $1.toUpperCase()); + }, }; const LyricProviders = { @@ -90,9 +90,9 @@ function PopupLyrics() { } const lines = lyricsData.lines; - const lyrics = lines.map(a => ({ + const lyrics = lines.map((a) => ({ startTime: a.startTimeMs / 1000, - text: a.words + text: a.words, })); return { lyrics }; @@ -112,19 +112,19 @@ function PopupLyrics() { track_spotify_id: info.uri, q_duration: durr, f_subtitle_length: Math.floor(durr), - usertoken: userConfigs.services.musixmatch.token + usertoken: userConfigs.services.musixmatch.token, }; const finalURL = baseURL + Object.keys(params) - .map(key => `${key}=${encodeURIComponent(params[key])}`) + .map((key) => `${key}=${encodeURIComponent(params[key])}`) .join("&"); try { let body = await CosmosAsync.get(finalURL, null, { authority: "apic-desktop.musixmatch.com", - cookie: "x-mxm-token-guid=" + cookie: "x-mxm-token-guid=", }); body = body.message.body.macro_calls; @@ -132,7 +132,7 @@ function PopupLyrics() { if (body["matcher.track.get"].message.header.status_code !== 200) { const head = body["matcher.track.get"].message.header; return { - error: `Requested error: ${head.status_code}: ${head.hint} - ${head.mode}` + error: `Requested error: ${head.status_code}: ${head.hint} - ${head.mode}`, }; } @@ -146,9 +146,9 @@ function PopupLyrics() { if (hasSynced) { const subtitle = body["track.subtitles.get"].message.body.subtitle_list[0].subtitle; - const lyrics = JSON.parse(subtitle.subtitle_body).map(line => ({ + const lyrics = JSON.parse(subtitle.subtitle_body).map((line) => ({ text: line.text || "♪", - startTime: line.time.total + startTime: line.time.total, })); return { lyrics }; } @@ -163,7 +163,7 @@ function PopupLyrics() { const searchURL = "https://music.xianqiao.wang/neteaseapiv2/search?limit=10&type=1&keywords="; const lyricURL = "https://music.xianqiao.wang/neteaseapiv2/lyric?id="; const requestHeader = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0" + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0", }; const cleanTitle = LyricUtils.removeExtraInfo(LyricUtils.normalize(info.title)); @@ -176,7 +176,7 @@ function PopupLyrics() { } const album = LyricUtils.capitalize(info.album); - const itemId = items.findIndex(val => LyricUtils.capitalize(val.album.name) === album || Math.abs(info.duration - val.duration) < 1000); + const itemId = items.findIndex((val) => LyricUtils.capitalize(val.album.name) === album || Math.abs(info.duration - val.duration) < 1000); if (itemId === -1) return { error: "Cannot find track" }; const meta = await CosmosAsync.get(lyricURL + items[itemId].id, null, requestHeader); @@ -191,14 +191,14 @@ function PopupLyrics() { "\\s?作?\\s*词|\\s?作?\\s*曲|\\s?编\\s*曲?|\\s?监\\s*制?", ".*编写|.*和音|.*和声|.*合声|.*提琴|.*录|.*工程|.*工作室|.*设计|.*剪辑|.*制作|.*发行|.*出品|.*后期|.*混音|.*缩混", "原唱|翻唱|题字|文案|海报|古筝|二胡|钢琴|吉他|贝斯|笛子|鼓|弦乐", - "lrc|publish|vocal|guitar|program|produce|write|mix" + "lrc|publish|vocal|guitar|program|produce|write|mix", ]; const otherInfoRegexp = new RegExp(`^(${otherInfoKeys.join("|")}).*(:|:)`, "i"); - const lines = lyricStr.split(/\r?\n/).map(line => line.trim()); + const lines = lyricStr.split(/\r?\n/).map((line) => line.trim()); let noLyrics = false; const lyrics = lines - .flatMap(line => { + .flatMap((line) => { // ["[ar:Beyond]"] // ["[03:10]"] // ["[03:10]", "永远高唱我歌"] @@ -208,14 +208,14 @@ function PopupLyrics() { if (!matchResult.length || matchResult.length === 1) { return; } - const textIndex = matchResult.findIndex(slice => !slice.endsWith("]")); + const textIndex = matchResult.findIndex((slice) => !slice.endsWith("]")); let text = ""; if (textIndex > -1) { text = matchResult.splice(textIndex, 1)[0]; text = LyricUtils.capitalize(LyricUtils.normalize(text, false)); } if (text === "纯音乐, 请欣赏") noLyrics = true; - return matchResult.map(slice => { + return matchResult.map((slice) => { const result = {}; const matchResult = slice.match(/[^\[\]]+/g); const [key, value] = matchResult[0].split(":") || []; @@ -247,7 +247,7 @@ function PopupLyrics() { } return { lyrics }; - } + }, }; const userConfigs = { @@ -263,21 +263,21 @@ function PopupLyrics() { netease: { on: boolLocalStorage("popup-lyrics:services:netease:on"), call: LyricProviders.fetchNetease, - desc: "Crowdsourced lyrics provider ran by Chinese developers and users." + desc: "Crowdsourced lyrics provider ran by Chinese developers and users.", }, musixmatch: { on: boolLocalStorage("popup-lyrics:services:musixmatch:on"), call: LyricProviders.fetchMusixmatch, desc: `Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. Follow instructions on Spicetify Docs.`, - token: LocalStorage.get("popup-lyrics:services:musixmatch:token") || "2005218b74f939209bda92cb633c7380612e14cb7fe92dcd6a780f" + token: LocalStorage.get("popup-lyrics:services:musixmatch:token") || "2005218b74f939209bda92cb633c7380612e14cb7fe92dcd6a780f", }, spotify: { on: boolLocalStorage("popup-lyrics:services:spotify:on"), call: LyricProviders.fetchSpotify, - desc: "Lyrics sourced from official Spotify API." - } + desc: "Lyrics sourced from official Spotify API.", + }, }, - servicesOrder: [] + servicesOrder: [], }; userConfigs.fontSize = userConfigs.fontSize ? Number(userConfigs.fontSize) : 46; @@ -287,7 +287,7 @@ function PopupLyrics() { if (!Array.isArray(userConfigs.servicesOrder)) throw ""; - userConfigs.servicesOrder = userConfigs.servicesOrder.filter(s => userConfigs.services[s]); // Remove obsoleted services + userConfigs.servicesOrder = userConfigs.servicesOrder.filter((s) => userConfigs.services[s]); // Remove obsoleted services const allServices = Object.keys(userConfigs.services); if (userConfigs.servicesOrder.length !== allServices.length) { @@ -379,7 +379,7 @@ function PopupLyrics() { album: meta.album_title, artist: meta.artist_name, title: meta.title, - uri: Player.data.item.uri + uri: Player.data.item.uri, }; for (const name of userConfigs.servicesOrder) { @@ -502,7 +502,7 @@ function PopupLyrics() { left: startX + translateX, right: ctx.canvas.width - options.left - actualWidth + translateX, top: startY - ascent + translateY, - bottom: startY + body + descent + translateY + bottom: startY + body + descent + translateY, }; } @@ -535,7 +535,7 @@ function PopupLyrics() { hCenter: true, left: 0, right: 0, - lineHeight: fontSize + lineHeight: fontSize, }); ctx.restore(); } @@ -564,7 +564,7 @@ function PopupLyrics() { return { offscreenCtx, gradient1, - gradient2 + gradient2, }; } @@ -612,7 +612,7 @@ function PopupLyrics() { ...userConfigs, currentIndex, lyrics, - progress + progress, }; if (isEqualState(nextState, renderState)) return; renderState = nextState; @@ -636,7 +636,7 @@ function PopupLyrics() { left: marginWidth, right: marginWidth, lineHeight: focusLineFontSize, - measure: true + measure: true, }).height; const pos = drawParagraph(offscreenCtx, lyrics[currentIndex].text, { @@ -645,7 +645,7 @@ function PopupLyrics() { left: marginWidth, right: progressRight, lineHeight: fLineHeight, - translateY: selfHeight => ((prevLineFocusHeight + selfHeight) / 2 + focusLineMargin) * (1 - progress) + translateY: (selfHeight) => ((prevLineFocusHeight + selfHeight) / 2 + focusLineMargin) * (1 - progress), }); // offscreenCtx.strokeRect(pos.left, pos.top, pos.width, pos.height); @@ -666,7 +666,7 @@ function PopupLyrics() { bottom: i === 0 ? lastBeforePos.top - focusLineMargin : lastBeforePos.top - otherLineMargin, left: marginWidth, right: i === 0 ? marginWidth + progress * (otherRight - marginWidth) : otherRight, - lineHeight: i === 0 ? otherLineHeight + (1 - progress) * (focusLineHeight - otherLineHeight) : otherLineHeight + lineHeight: i === 0 ? otherLineHeight + (1 - progress) * (focusLineHeight - otherLineHeight) : otherLineHeight, }); if (lastBeforePos.top < 0) break; } @@ -680,7 +680,7 @@ function PopupLyrics() { top: i === currentIndex + 1 ? lastAfterPos.bottom + focusLineMargin : lastAfterPos.bottom + otherLineMargin, left: marginWidth, right: otherRight, - lineHeight: otherLineHeight + lineHeight: otherLineHeight, }); if (lastAfterPos.bottom > ctx.canvas.height) break; } @@ -706,7 +706,7 @@ function PopupLyrics() { const audio = { currentTime: (Player.getProgress() - Number(options.delay)) / 1000, - duration: Player.getDuration() / 1000 + duration: Player.getDuration() / 1000, }; const { error, lyrics } = sharedData; @@ -818,19 +818,19 @@ button.switch.small { `; const optionHeader = document.createElement("h2"); optionHeader.innerText = "Options"; - const smooth = createSlider("Smooth scrolling", userConfigs.smooth, state => { + const smooth = createSlider("Smooth scrolling", userConfigs.smooth, (state) => { userConfigs.smooth = state; LocalStorage.set("popup-lyrics:smooth", String(state)); }); - const center = createSlider("Center align", userConfigs.centerAlign, state => { + const center = createSlider("Center align", userConfigs.centerAlign, (state) => { userConfigs.centerAlign = state; LocalStorage.set("popup-lyrics:center-align", String(state)); }); - const cover = createSlider("Show cover", userConfigs.showCover, state => { + const cover = createSlider("Show cover", userConfigs.showCover, (state) => { userConfigs.showCover = state; LocalStorage.set("popup-lyrics:show-cover", String(state)); }); - const ratio = createOptions("Aspect ratio", { 11: "1:1", 43: "4:3", 169: "16:9" }, userConfigs.ratio, state => { + const ratio = createOptions("Aspect ratio", { 11: "1:1", 43: "4:3", 169: "16:9" }, userConfigs.ratio, (state) => { userConfigs.ratio = state; LocalStorage.set("popup-lyrics:ratio", state); let value = lyricVideo.width; @@ -858,10 +858,10 @@ button.switch.small { 46: "46px", 50: "50px", 54: "54px", - 58: "58px" + 58: "58px", }, String(userConfigs.fontSize), - state => { + (state) => { userConfigs.fontSize = Number(state); LocalStorage.set("popup-lyrics:font-size", state); } @@ -872,15 +872,15 @@ button.switch.small { 2: "2px", 5: "5px", 10: "10px", - 15: "15px" + 15: "15px", }, String(userConfigs.blurSize), - state => { + (state) => { userConfigs.blurSize = Number(state); LocalStorage.set("popup-lyrics:blur-size", state); } ); - const delay = createOptionsInput("Delay", String(userConfigs.delay), state => { + const delay = createOptionsInput("Delay", String(userConfigs.delay), (state) => { userConfigs.delay = Number(state); LocalStorage.set("popup-lyrics:delay", state); }); @@ -919,7 +919,7 @@ button.switch.small { function posCallback(el, dir) { const id = el.dataset.id; - const curPos = userConfigs.servicesOrder.findIndex(val => val === id); + const curPos = userConfigs.servicesOrder.findIndex((val) => val === id); const newPos = curPos + dir; const temp = userConfigs.servicesOrder[newPos]; @@ -949,7 +949,7 @@ button.switch.small { } Spicetify.PopupModal.display({ title: "Popup Lyrics", - content: configContainer + content: configContainer, }); } @@ -985,7 +985,7 @@ button.switch.small { - ${options.map(option => ``).join("")} + ${options.map((option) => ``).join("")} `; const dropdown = container.querySelector("select"); @@ -269,7 +273,7 @@ ${Spicetify.SVGIcons.search} setOverrides(Spicetify.createInternalMap?.(featureMap)); })(); - await new Promise(res => Spicetify.Events.webpackLoaded.on(res)); + await new Promise((res) => Spicetify.Events.webpackLoaded.on(res)); new Spicetify.Menu.Item( "Experimental features", @@ -278,7 +282,7 @@ ${Spicetify.SVGIcons.search} Spicetify.PopupModal.display({ title: "Experimental features", content, - isLarge: true + isLarge: true, }); if (!isFallback) return; @@ -287,7 +291,7 @@ ${Spicetify.SVGIcons.search} if (closeButton && modalOverlay) { closeButton.onclick = () => location.reload(); - modalOverlay.onclick = e => { + modalOverlay.onclick = (e) => { // If clicked on overlay, also reload if (e.target === modalOverlay) { location.reload(); diff --git a/jsHelper/homeConfig.js b/jsHelper/homeConfig.js index 7b5490df83..f6e1cd73b6 100644 --- a/jsHelper/homeConfig.js +++ b/jsHelper/homeConfig.js @@ -11,7 +11,7 @@ SpicetifyHomeConfig = {}; const statusDic = {}; let mounted = false; - SpicetifyHomeConfig.arrange = sections => { + SpicetifyHomeConfig.arrange = (sections) => { mounted = true; if (list) { return list; @@ -21,7 +21,7 @@ SpicetifyHomeConfig = {}; const stickSections = []; const lowSections = []; for (const uri of stickList) { - const index = sections.findIndex(a => a?.uri === uri); + const index = sections.findIndex((a) => a?.uri === uri); if (index !== -1) { const item = sections[index]; statusDic[item.uri] = STICKY; @@ -30,7 +30,7 @@ SpicetifyHomeConfig = {}; } } for (const uri of lowList) { - const index = sections.findIndex(a => a?.uri === uri); + const index = sections.findIndex((a) => a?.uri === uri); if (index !== -1) { const item = sections[index]; statusDic[item.uri] = LOWERED; @@ -38,9 +38,8 @@ SpicetifyHomeConfig = {}; sections[index] = undefined; } } - sections = sections.filter(Boolean); - list = [...stickSections, ...sections, ...lowSections]; + list = [...stickSections, ...sections.filter(Boolean), ...lowSections]; return list; }; @@ -106,11 +105,11 @@ SpicetifyHomeConfig = {}; localStorage.setItem( "spicetify-home-config:stick", - stick.map(a => a.dataset.uri) + stick.map((a) => a.dataset.uri) ); localStorage.setItem( "spicetify-home-config:low", - low.map(a => a.dataset.uri) + low.map((a) => a.dataset.uri) ); elem = [...stick, ...normal, ...low]; @@ -119,7 +118,7 @@ SpicetifyHomeConfig = {}; function onSwap(item, dir) { container.remove(); - const curPos = elem.findIndex(e => e === item); + const curPos = elem.findIndex((e) => e === item); const newPos = curPos + dir; if (newPos < 0 || newPos > elem.length - 1) return; @@ -138,7 +137,7 @@ SpicetifyHomeConfig = {}; for (const el of elem) { el.onmouseover = () => { const status = statusDic[el.dataset.uri]; - const index = elem.findIndex(a => a === el); + const index = elem.findIndex((a) => a === el); if (!status || index === 0 || status !== statusDic[elem[index - 1]?.dataset.uri]) { up.disabled = true; @@ -171,12 +170,12 @@ SpicetifyHomeConfig = {}; } } - await new Promise(res => Spicetify.Events.webpackLoaded.on(res)); + await new Promise((res) => Spicetify.Events.webpackLoaded.on(res)); SpicetifyHomeConfig.menu = new Spicetify.Menu.Item( "Home config", false, - self => { + (self) => { self.setState(!self.isEnabled); if (self.isEnabled) { injectInteraction(); @@ -195,7 +194,7 @@ SpicetifyHomeConfig = {}; SpicetifyHomeConfig.menu.deregister(); }; - await new Promise(res => Spicetify.Events.platformLoaded.on(res)); + await new Promise((res) => Spicetify.Events.platformLoaded.on(res)); // Init if (Spicetify.Platform.History.location.pathname === "/") { SpicetifyHomeConfig.addToMenu(); diff --git a/jsHelper/sidebarConfig.js b/jsHelper/sidebarConfig.js index 73c5c113e5..3c6e89255f 100644 --- a/jsHelper/sidebarConfig.js +++ b/jsHelper/sidebarConfig.js @@ -21,7 +21,7 @@ const newButtons = [...buttons]; const orderedButtons = []; for (const ele of storage) { - const index = newButtons.findIndex(a => ele[0] === a?.dataset.id); + const index = newButtons.findIndex((a) => ele[0] === a?.dataset.id); if (index !== -1) { orderedButtons.push([newButtons[index], ele[1]]); newButtons[index] = undefined; @@ -54,7 +54,7 @@ } function writeStorage() { - const array = ordered.map(a => [a[0].dataset.id, a[1]]); + const array = ordered.map((a) => [a[0].dataset.id, a[1]]); if (isYLX) return localStorage.setItem("spicetify-sidebar-config:ylx", JSON.stringify(array)); return localStorage.setItem("spicetify-sidebar-config", JSON.stringify(array)); @@ -99,7 +99,7 @@ color: var(--spice-button-disabled); function injectInteraction() { function onSwap(item, dir) { container.remove(); - const curPos = ordered.findIndex(e => e[0] === item); + const curPos = ordered.findIndex((e) => e[0] === item); const newPos = curPos + dir; if (newPos < 0 || newPos > ordered.length - 1) return; @@ -109,7 +109,7 @@ color: var(--spice-button-disabled); function onChangeStatus(item, status) { container.remove(); - const curPos = ordered.findIndex(e => e[0] === item); + const curPos = ordered.findIndex((e) => e[0] === item); ordered[curPos][1] = ordered[curPos][1] === status ? SHOW : status; appendItems(); } @@ -125,7 +125,7 @@ color: var(--spice-button-disabled); for (const el of ordered) { el[0].onmouseover = () => { const [item, status] = el; - const index = ordered.findIndex(a => a === el); + const index = ordered.findIndex((a) => a === el); if (index === 0 || ordered[index][1] !== ordered[index - 1][1]) { up.disabled = true; } else { @@ -169,11 +169,11 @@ color: var(--spice-button-disabled); } (async () => { - await new Promise(res => Spicetify.Events.webpackLoaded.on(res)); + await new Promise((res) => Spicetify.Events.webpackLoaded.on(res)); new Spicetify.Menu.Item( "Sidebar config", false, - self => { + (self) => { self.setState(!self.isEnabled); if (self.isEnabled) { injectInteraction(); @@ -240,7 +240,7 @@ color: var(--spice-button-disabled); ele.dataset.id = link.pathname; } ele.classList.add("personal-library"); - new MutationObserver(mutations => { + new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === "attributes" && mutation.attributeName === "class") { if (!mutation.target.classList.contains("personal-library")) { @@ -258,7 +258,7 @@ color: var(--spice-button-disabled); storage = JSON.parse(localStorage.getItem("spicetify-sidebar-config")); if (!Array.isArray(storage)) throw ""; } catch { - storage = buttons.map(el => [el.dataset.id, STICKY]); + storage = buttons.map((el) => [el.dataset.id, STICKY]); } arrangeItems(storage); @@ -302,10 +302,10 @@ color: var(--spice-button-disabled); storage = JSON.parse(localStorage.getItem("spicetify-sidebar-config:ylx")); if (!Array.isArray(storage)) throw ""; } catch { - storage = buttons.map(el => [el.dataset.id, STICKY]); + storage = buttons.map((el) => [el.dataset.id, STICKY]); } - const observer = new MutationObserver(mutations => { + const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === "childList" && mutation.addedNodes.length) { mutation.addedNodes[0].id = "spicetify-playlist-list"; @@ -321,7 +321,7 @@ color: var(--spice-button-disabled); initConfig(); // Rearrange sidebar when dynamically switching in Experimental Features - new MutationObserver(mutations => { + new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.attributeName === "class") { if (mutation.target.classList.contains("hasYLXSidebar") || !!mutation.target.querySelector(".main-yourLibraryX-entryPoints")) { diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 1fec2f1e45..ac5d0ea1c3 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -6,7 +6,7 @@ window.Spicetify = { } Spicetify.Player.eventListeners[type].push(callback); }, - dispatchEvent: event => { + dispatchEvent: (event) => { if (!(event.type in Spicetify.Player.eventListeners)) { return true; } @@ -19,18 +19,16 @@ window.Spicetify = { return !event.defaultPrevented; }, eventListeners: {}, - seek: p => { - if (p <= 1) { - p = Math.round(p * Spicetify.Player.origin._state.duration); - } - Spicetify.Player.origin.seekTo(p); + seek: (p) => { + const duration = p <= 1 ? Math.round(p * Spicetify.Player.origin._state.duration) : p; + Spicetify.Player.origin.seekTo(duration); }, getProgress: () => (Spicetify.Player.origin._state.isPaused ? 0 : Date.now() - Spicetify.Player.origin._state.timestamp) + Spicetify.Player.origin._state.positionAsOfTimestamp, getProgressPercent: () => Spicetify.Player.getProgress() / Spicetify.Player.origin._state.duration, getDuration: () => Spicetify.Player.origin._state.duration, - setVolume: v => { + setVolume: (v) => { Spicetify.Platform.PlaybackAPI.setVolume(v); }, increaseVolume: () => { @@ -54,26 +52,26 @@ window.Spicetify = { Spicetify.Player.origin.setShuffle(!Spicetify.Player.origin._state.shuffle); }, getShuffle: () => Spicetify.Player.origin._state.shuffle, - setShuffle: b => { + setShuffle: (b) => { Spicetify.Player.origin.setShuffle(b); }, toggleRepeat: () => { Spicetify.Player.origin.setRepeat((Spicetify.Player.origin._state.repeat + 1) % 3); }, getRepeat: () => Spicetify.Player.origin._state.repeat, - setRepeat: r => { + setRepeat: (r) => { Spicetify.Player.origin.setRepeat(r); }, getMute: () => Spicetify.Player.getVolume() === 0, toggleMute: () => { Spicetify.Player.setMute(!Spicetify.Player.getMute()); }, - setMute: b => { + setMute: (b) => { if (b !== Spicetify.Player.getMute()) { document.querySelector(".volume-bar__icon-button")?.click(); } }, - formatTime: ms => { + formatTime: (ms) => { let seconds = Math.floor(ms / 1e3); const minutes = Math.floor(seconds / 60); seconds -= minutes * 60; @@ -105,7 +103,7 @@ window.Spicetify = { skipForward: (amount = 15e3) => { Spicetify.Player.origin.seekForward(amount); }, - setHeart: b => { + setHeart: (b) => { const uris = [Spicetify.Player.origin._state.item.uri]; if (b) { Spicetify.Platform.LibraryAPI.add({ uris }); @@ -115,7 +113,7 @@ window.Spicetify = { }, toggleHeart: () => { Spicetify.Player.setHeart(!Spicetify.Player.getHeart()); - } + }, }, test: () => { function checkObject(object) { @@ -166,8 +164,6 @@ window.Spicetify = { "test", "Platform", "_platform", - "getFontStyle", - "_fontStyle", "Config", "expFeatureOverride", "createInternalMap", @@ -188,8 +184,8 @@ window.Spicetify = { "Snackbar", "ContextMenuV2", "ReactJSX", - "_renderNavLinks" - ]) + "_renderNavLinks", + ]), }, { objectToCheck: Spicetify.Player, @@ -230,8 +226,8 @@ window.Spicetify = { "toggleShuffle", "origin", "playUri", - "setHeart" - ]) + "setHeart", + ]), }, { objectToCheck: Spicetify.ReactComponent, @@ -267,8 +263,8 @@ window.Spicetify = { "Dropdown", "MenuSubMenuItem", "Navigation", - "ScrollableContainer" - ]) + "ScrollableContainer", + ]), }, { objectToCheck: Spicetify.ReactComponent.Cards, @@ -285,14 +281,14 @@ window.Spicetify = { "Profile", "Show", "Track", - "FeatureCard" - ]) + "FeatureCard", + ]), }, { objectToCheck: Spicetify.ReactHook, name: "Spicetify.ReactHook", - methods: new Set(["DragHandler", "useExtractedColor"]) - } + methods: new Set(["DragHandler", "useExtractedColor"]), + }, ]); for (const object of objectsToCheck) { @@ -300,13 +296,13 @@ window.Spicetify = { } }, GraphQL: { - Definitions: {} + Definitions: {}, }, ReactComponent: {}, ReactHook: {}, ReactFlipToolkit: {}, Snackbar: {}, - Platform: {} + Platform: {}, }; (function waitForPlatform() { @@ -331,7 +327,7 @@ window.Spicetify = { setTimeout(addMissingPlatformAPIs, 50); return; } - const version = Spicetify.Platform.version.split(".").map(i => Number.parseInt(i)); + const version = Spicetify.Platform.version.split(".").map((i) => Number.parseInt(i)); if (version[0] === 1 && version[1] === 2 && version[2] < 38) return; for (const [key, _] of Spicetify.Platform.Registry._map.entries()) { @@ -365,7 +361,7 @@ window.Spicetify = { post: "post", del: "delete", put: "put", - patch: "patch" + patch: "patch", }; const allowedMethodsSet = new Set(Object.keys(allowedMethodsMap)); const internalEndpoints = new Set(["sp:", "wg:"]); @@ -375,7 +371,7 @@ window.Spicetify = { const internalFetch = Reflect.get(target, prop, receiver); if (typeof internalFetch !== "function" || !allowedMethodsSet.has(prop)) return internalFetch; - const version = Spicetify.Platform.version.split(".").map(i => Number.parseInt(i)); + const version = Spicetify.Platform.version.split(".").map((i) => Number.parseInt(i)); if (version[0] === 1 && version[1] === 2 && version[2] < 31) return internalFetch; return async function (url, body) { @@ -390,13 +386,13 @@ window.Spicetify = { const method = allowedMethodsMap[prop.toLowerCase()]; const headers = { - "Content-Type": "application/json" + "Content-Type": "application/json", }; const options = { method, headers, - timeout: 1000 * 15 + timeout: 1000 * 15, }; let finalURL = urlObj.toString(); @@ -415,13 +411,13 @@ window.Spicetify = { injectedHeaders = { Authorization, "Spotify-App-Version": Spicetify.Platform.version, - "App-Platform": Spicetify.Platform.PlatformData.app_platform + "App-Platform": Spicetify.Platform.PlatformData.app_platform, }; } Object.assign(options.headers, injectedHeaders); try { - return fetch(finalURL, options).then(res => { + return fetch(finalURL, options).then((res) => { if (!res.ok) return { code: res.status, error: res.statusText, message: "Failed to fetch", stack: undefined }; try { return res.clone().json(); @@ -437,14 +433,14 @@ window.Spicetify = { console.error(e); } }; - } + }, }; Spicetify.Player.origin._cosmos = new Proxy(_cosmos, handler); Object.defineProperty(Spicetify, "CosmosAsync", { get: () => { return Spicetify.Player.origin?._cosmos; - } + }, }); })(); @@ -454,16 +450,16 @@ window.Spicetify = { return; } // Force all webpack modules to load - const require = webpackChunkclient_web.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); return; } - const cache = Object.keys(require.m).map(id => require(id)); + const cache = Object.keys(require.m).map((id) => require(id)); const modules = cache - .filter(module => typeof module === "object") - .flatMap(module => { + .filter((module) => typeof module === "object") + .flatMap((module) => { try { return Object.values(module); } catch {} @@ -477,15 +473,15 @@ window.Spicetify = { return a; }, {}); }; - const functionModules = modules.filter(module => typeof module === "function"); - const exportedReactObjects = groupBy(modules.filter(Boolean), x => x.$$typeof); + const functionModules = modules.filter((module) => typeof module === "function"); + const exportedReactObjects = groupBy(modules.filter(Boolean), (x) => x.$$typeof); const exportedMemos = exportedReactObjects[Symbol.for("react.memo")]; const exportedForwardRefs = exportedReactObjects[Symbol.for("react.forward_ref")]; - const exportedMemoFRefs = exportedMemos.filter(m => m.type.$$typeof === Symbol.for("react.forward_ref")); + const exportedMemoFRefs = exportedMemos.filter((m) => m.type.$$typeof === Symbol.for("react.forward_ref")); const exposeReactComponentsUI = ({ modules, functionModules, exportedForwardRefs }) => { - const componentNames = Object.keys(modules.filter(Boolean).find(e => e.BrowserDefaultFocusStyleProvider)); - const componentRegexes = componentNames.map(n => new RegExp(`"data-encore-id":(?:[a-zA-Z_\$][\w\$]*\\.){2}${n}\\b`)); - const componentPairs = [functionModules.map(f => [f, f]), exportedForwardRefs.map(f => [f.render, f])] + const componentNames = Object.keys(modules.filter(Boolean).find((e) => e.BrowserDefaultFocusStyleProvider)); + const componentRegexes = componentNames.map((n) => new RegExp(`"data-encore-id":(?:[a-zA-Z_\$][\w\$]*\\.){2}${n}\\b`)); + const componentPairs = [functionModules.map((f) => [f, f]), exportedForwardRefs.map((f) => [f.render, f])] .flat() .map(([s, f]) => [componentNames.find((_, i) => s.toString().match(componentRegexes[i])), f]); return Object.fromEntries(componentPairs); @@ -494,9 +490,9 @@ window.Spicetify = { const knownMenuTypes = ["album", "show", "artist", "track"]; const menus = modules - .map(m => m?.type?.toString().match(/value:"[\w-]+"/g) && [m, ...m.type.toString().match(/value:"[\w-]+"/g)]) + .map((m) => m?.type?.toString().match(/value:"[\w-]+"/g) && [m, ...m.type.toString().match(/value:"[\w-]+"/g)]) .filter(Boolean) - .filter(m => m[1] !== 'value:"row"') + .filter((m) => m[1] !== 'value:"row"') .map(([module, type]) => { type = type.match(/value:"([\w-]+)"/)[1]; @@ -505,7 +501,7 @@ window.Spicetify = { type = `${type .split("-") - .map(str => str[0].toUpperCase() + str.slice(1)) + .map((str) => str[0].toUpperCase() + str.slice(1)) .join("")}Menu`; return [type, module]; }) @@ -514,8 +510,8 @@ window.Spicetify = { const cardTypesToFind = ["album", "artist", "audiobook", "episode", "playlist", "profile", "show", "track"]; const cards = [ ...functionModules - .flatMap(m => { - return cardTypesToFind.map(type => { + .flatMap((m) => { + return cardTypesToFind.map((type) => { if (m.toString().includes(`featureIdentifier:"${type}"`)) { cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1); return [type[0].toUpperCase() + type.slice(1), m]; @@ -524,8 +520,8 @@ window.Spicetify = { }) .filter(Boolean), ...modules - .flatMap(m => { - return cardTypesToFind.map(type => { + .flatMap((m) => { + return cardTypesToFind.map((type) => { try { if (m?.type?.toString().includes(`featureIdentifier:"${type}"`)) { cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1); @@ -534,118 +530,118 @@ window.Spicetify = { } catch {} }); }) - .filter(Boolean) + .filter(Boolean), ]; Object.assign(Spicetify, { - React: cache.find(m => m?.useMemo), - ReactJSX: cache.find(m => m?.jsx), - ReactDOM: cache.find(m => m?.createPortal), - ReactDOMServer: cache.find(m => m?.renderToString), + React: cache.find((m) => m?.useMemo), + ReactJSX: cache.find((m) => m?.jsx), + ReactDOM: cache.find((m) => m?.createPortal), + ReactDOMServer: cache.find((m) => m?.renderToString), // https://github.com/JedWatson/classnames/ classnames: chunks .filter(([_, v]) => v.toString().includes("[native code]")) .map(([i]) => require(i)) - .find(e => typeof e === "function"), - Color: functionModules.find(m => m.toString().includes("static fromHex") || m.toString().includes("this.rgb")), + .find((e) => typeof e === "function"), + Color: functionModules.find((m) => m.toString().includes("static fromHex") || m.toString().includes("this.rgb")), Player: { ...Spicetify.Player, get origin() { return Spicetify.Platform?.PlayerAPI; - } + }, }, GraphQL: { ...Spicetify.GraphQL, get Request() { return Spicetify.Platform?.GraphQLLoader || Spicetify.GraphQL.Handler?.(Spicetify.GraphQL.Context); }, - Context: functionModules.find(m => m.toString().includes("subscription") && m.toString().includes("mutation")), - Handler: functionModules.find(m => m.toString().includes("GraphQL subscriptions are not supported")) + Context: functionModules.find((m) => m.toString().includes("subscription") && m.toString().includes("mutation")), + Handler: functionModules.find((m) => m.toString().includes("GraphQL subscriptions are not supported")), }, ReactComponent: { ...Spicetify.ReactComponent, - TextComponent: modules.find(m => m?.h1 && m?.render), + TextComponent: modules.find((m) => m?.h1 && m?.render), ConfirmDialog: functionModules.find( - m => m.toString().includes("isOpen") && m.toString().includes("shouldCloseOnEsc") && m.toString().includes("onClose") + (m) => m.toString().includes("isOpen") && m.toString().includes("shouldCloseOnEsc") && m.toString().includes("onClose") ), - Menu: functionModules.find(m => m.toString().includes("getInitialFocusElement") && m.toString().includes("children")), - MenuItem: functionModules.find(m => m.toString().includes("handleMouseEnter") && m.toString().includes("onClick")), - MenuSubMenuItem: functionModules.find(f => f.toString().includes("subMenuIcon")), - Slider: wrapProvider(functionModules.find(m => m.toString().includes("onStepBackward") && !m.toString().includes("volume"))), - RemoteConfigProvider: functionModules.find(m => m.toString().includes("resolveSuspense") && m.toString().includes("configuration")), + Menu: functionModules.find((m) => m.toString().includes("getInitialFocusElement") && m.toString().includes("children")), + MenuItem: functionModules.find((m) => m.toString().includes("handleMouseEnter") && m.toString().includes("onClick")), + MenuSubMenuItem: functionModules.find((f) => f.toString().includes("subMenuIcon")), + Slider: wrapProvider(functionModules.find((m) => m.toString().includes("onStepBackward") && !m.toString().includes("volume"))), + RemoteConfigProvider: functionModules.find((m) => m.toString().includes("resolveSuspense") && m.toString().includes("configuration")), RightClickMenu: functionModules.find( - m => + (m) => 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")), + TooltipWrapper: functionModules.find((m) => m.toString().includes("renderInline") && m.toString().includes("showDelay")), ButtonPrimary: reactComponentsUI.ButtonPrimary, ButtonSecondary: reactComponentsUI.ButtonSecondary, ButtonTertiary: reactComponentsUI.ButtonTertiary, Snackbar: { - wrapper: functionModules.find(m => m.toString().includes("encore-light-theme") && m.toString().includes("elevated")), - simpleLayout: functionModules.find(m => ["leading", "center", "trailing"].every(keyword => m.toString().includes(keyword))), - ctaText: functionModules.find(m => m.toString().includes("ctaText")), - styledImage: functionModules.find(m => m.toString().includes("placeholderSrc")) + wrapper: functionModules.find((m) => m.toString().includes("encore-light-theme") && m.toString().includes("elevated")), + simpleLayout: functionModules.find((m) => ["leading", "center", "trailing"].every((keyword) => m.toString().includes(keyword))), + ctaText: functionModules.find((m) => m.toString().includes("ctaText")), + styledImage: functionModules.find((m) => m.toString().includes("placeholderSrc")), }, Chip: reactComponentsUI.Chip, - Toggle: functionModules.find(m => m.toString().includes("onSelected") && m.toString().includes('type:"checkbox"')), + Toggle: functionModules.find((m) => m.toString().includes("onSelected") && m.toString().includes('type:"checkbox"')), Cards: { Default: reactComponentsUI.Card, FeatureCard: functionModules.find( - m => m.toString().includes("?highlight") && m.toString().includes("headerText") && m.toString().includes("imageContainer") + (m) => m.toString().includes("?highlight") && m.toString().includes("headerText") && m.toString().includes("imageContainer") ), - Hero: functionModules.find(m => m?.toString().includes('"herocard-click-handler"')), + Hero: functionModules.find((m) => m?.toString().includes('"herocard-click-handler"')), CardImage: functionModules.find( - m => + (m) => m.toString().includes("isHero") && (m.toString().includes("withWaves") || m.toString().includes("isCircular")) && m.toString().includes("imageWrapper") ), - ...Object.fromEntries(cards) + ...Object.fromEntries(cards), }, - Router: functionModules.find(m => m.toString().includes("navigationType") && m.toString().includes("static")), - Routes: functionModules.find(m => m.toString().match(/\([\w$]+\)\{let\{children:[\w$]+,location:[\w$]+\}=[\w$]+/)), - Route: functionModules.find(m => m.toString().match(/^function [\w$]+\([\w$]+\)\{\(0,[\w$]+\.[\w$]+\)\(\!1\)\}$/)), - StoreProvider: functionModules.find(m => m.toString().includes("notifyNestedSubs") && m.toString().includes("serverState")), - Navigation: exportedMemoFRefs.find(m => m.type.render.toString().includes("navigationalRoot")), - ScrollableContainer: functionModules.find(m => m.toString().includes("scrollLeft") && m.toString().includes("showButtons")), + Router: functionModules.find((m) => m.toString().includes("navigationType") && m.toString().includes("static")), + Routes: functionModules.find((m) => m.toString().match(/\([\w$]+\)\{let\{children:[\w$]+,location:[\w$]+\}=[\w$]+/)), + Route: functionModules.find((m) => m.toString().match(/^function [\w$]+\([\w$]+\)\{\(0,[\w$]+\.[\w$]+\)\(\!1\)\}$/)), + StoreProvider: functionModules.find((m) => m.toString().includes("notifyNestedSubs") && m.toString().includes("serverState")), + Navigation: exportedMemoFRefs.find((m) => m.type.render.toString().includes("navigationalRoot")), + ScrollableContainer: functionModules.find((m) => m.toString().includes("scrollLeft") && m.toString().includes("showButtons")), IconComponent: reactComponentsUI.Icon, - ...Object.fromEntries(menus) + ...Object.fromEntries(menus), }, ReactHook: { - DragHandler: functionModules.find(m => m.toString().includes("dataTransfer") && m.toString().includes("data-dragging")), + DragHandler: functionModules.find((m) => m.toString().includes("dataTransfer") && m.toString().includes("data-dragging")), useExtractedColor: functionModules.find( - m => m.toString().includes("extracted-color") || (m.toString().includes("colorRaw") && m.toString().includes("useEffect")) - ) + (m) => m.toString().includes("extracted-color") || (m.toString().includes("colorRaw") && m.toString().includes("useEffect")) + ), }, // React Query // https://github.com/TanStack/query // v3 until Spotify v1.2.29 // v5 since Spotify v1.2.30 - ReactQuery: cache.find(module => module.useQuery) || { - PersistQueryClientProvider: functionModules.find(m => m.toString().includes("persistOptions")), - QueryClient: functionModules.find(m => m.toString().includes("defaultMutationOptions")), - QueryClientProvider: functionModules.find(m => m.toString().includes("use QueryClientProvider")), - notifyManager: modules.find(m => m?.setBatchNotifyFunction), - useMutation: functionModules.find(m => m.toString().includes("mutateAsync")), - useQuery: functionModules.find(m => + ReactQuery: cache.find((module) => module.useQuery) || { + PersistQueryClientProvider: functionModules.find((m) => m.toString().includes("persistOptions")), + QueryClient: functionModules.find((m) => m.toString().includes("defaultMutationOptions")), + QueryClientProvider: functionModules.find((m) => m.toString().includes("use QueryClientProvider")), + notifyManager: modules.find((m) => m?.setBatchNotifyFunction), + useMutation: functionModules.find((m) => m.toString().includes("mutateAsync")), + useQuery: functionModules.find((m) => m.toString().match(/^function [\w_$]+\(([\w_$]+),([\w_$]+)\)\{return\(0,[\w_$]+\.[\w_$]+\)\(\1,[\w_$]+\.[\w_$]+,\2\)\}$/) ), useQueryClient: functionModules.find( - m => m.toString().includes("client") && m.toString().includes("Provider") && m.toString().includes("mount") + (m) => m.toString().includes("client") && m.toString().includes("Provider") && m.toString().includes("mount") ), useSuspenseQuery: functionModules.find( - m => m.toString().includes("throwOnError") && m.toString().includes("suspense") && m.toString().includes("enabled") - ) + (m) => m.toString().includes("throwOnError") && m.toString().includes("suspense") && m.toString().includes("enabled") + ), }, ReactFlipToolkit: { ...Spicetify.ReactFlipToolkit, - Flipper: functionModules.find(m => m?.prototype?.getSnapshotBeforeUpdate), - Flipped: functionModules.find(m => m.displayName === "Flipped") + Flipper: functionModules.find((m) => m?.prototype?.getSnapshotBeforeUpdate), + Flipped: functionModules.find((m) => m.displayName === "Flipped"), }, - _reservedPanelIds: modules.find(m => m?.BuddyFeed), - Mousetrap: cache.find(m => m?.addKeycodes), - Locale: modules.find(m => m?._dictionary) + _reservedPanelIds: modules.find((m) => m?.BuddyFeed), + Mousetrap: cache.find((m) => m?.addKeycodes), + Locale: modules.find((m) => m?._dictionary), }); if (!Spicetify.ContextMenuV2._context) Spicetify.ContextMenuV2._context = Spicetify.React.createContext({}); @@ -659,15 +655,15 @@ window.Spicetify = { // https://github.com/iamhosseindhv/notistack Spicetify.Snackbar = { ...Spicetify.Snackbar, - SnackbarProvider: functionModules.find(m => m.toString().includes("enqueueSnackbar called with invalid argument")), - useSnackbar: functionModules.find(m => m.toString().match(/^function\(\)\{return\(0,[\w$]+\.useContext\)\([\w$]+\)\}$/)) + SnackbarProvider: functionModules.find((m) => m.toString().includes("enqueueSnackbar called with invalid argument")), + useSnackbar: functionModules.find((m) => m.toString().match(/^function\(\)\{return\(0,[\w$]+\.useContext\)\([\w$]+\)\}$/)), }; })(); - const localeModule = modules.find(m => m?.getTranslations); + const localeModule = modules.find((m) => m?.getTranslations); if (localeModule) { const createUrlLocale = functionModules.find( - m => m.toString().includes("has") && m.toString().includes("baseName") && m.toString().includes("language") + (m) => m.toString().includes("has") && m.toString().includes("baseName") && m.toString().includes("language") ); Spicetify.Locale = { get _relativeTimeFormat() { @@ -690,75 +686,77 @@ window.Spicetify = { formatNumber: (number, options) => localeModule.formatNumber(number, options), formatNumberCompact: (number, options) => localeModule.formatNumberCompact(number, options), get: (key, children) => localeModule.get(key, children), - getDateTimeFormat: options => localeModule.getDateTimeFormat(options), + getDateTimeFormat: (options) => localeModule.getDateTimeFormat(options), getDictionary: () => localeModule.getTranslations(), getLocale: () => localeModule._localeForTranslation.baseName, getSmartlingLocale: () => localeModule.getLocaleForSmartling(), getUrlLocale: () => localeModule.getLocaleForURLPath(), getRelativeTimeFormat: () => localeModule.getRelativeTimeFormat(), getSeparator: () => localeModule.getSeparator(), - setLocale: locale => { + setLocale: (locale) => { return localeModule.initialize({ localeForTranslation: locale, localeForFormatting: localeModule._localeForFormatting.baseName, - translations: localeModule._translations + translations: localeModule._translations, }); }, - setUrlLocale: locale => { + setUrlLocale: (locale) => { if (createUrlLocale) localeModule._localeForURLPath = createUrlLocale(locale); }, - setDictionary: dictionary => { + setDictionary: (dictionary) => { return localeModule.initialize({ localeForTranslation: localeModule._localeForTranslation.baseName, localeForFormatting: localeModule._localeForFormatting.baseName, - translations: dictionary + translations: dictionary, }); }, - toLocaleLowerCase: text => localeModule.toLocaleLowerCase(text), - toLocaleUpperCase: text => localeModule.toLocaleUpperCase(text) + toLocaleLowerCase: (text) => localeModule.toLocaleLowerCase(text), + toLocaleUpperCase: (text) => localeModule.toLocaleUpperCase(text), }; } - if (Spicetify.Locale) Spicetify.Locale._supportedLocales = cache.find(m => typeof m?.ja === "string"); + if (Spicetify.Locale) Spicetify.Locale._supportedLocales = cache.find((m) => typeof m?.ja === "string"); Object.defineProperty(Spicetify, "Queue", { get() { return Spicetify.Player.origin?._queue?._state ?? Spicetify.Player.origin?._queue?._queue; - } + }, }); const confirmDialogChunk = chunks.find( ([, value]) => value.toString().includes("confirmDialog") && value.toString().includes("shouldCloseOnEsc") && value.toString().includes("isOpen") ); if (!Spicetify.ReactComponent?.ConfirmDialog && confirmDialogChunk) { - Spicetify.ReactComponent.ConfirmDialog = Object.values(require(confirmDialogChunk[0])).find(m => typeof m === "object"); + Spicetify.ReactComponent.ConfirmDialog = Object.values(require(confirmDialogChunk[0])).find((m) => typeof m === "object"); } const contextMenuChunk = chunks.find(([, value]) => value.toString().includes("toggleContextMenu")); if (contextMenuChunk) { - Spicetify.ReactComponent.ContextMenu = Object.values(require(contextMenuChunk[0])).find(m => typeof m === "function"); + Spicetify.ReactComponent.ContextMenu = Object.values(require(contextMenuChunk[0])).find((m) => typeof m === "function"); } const playlistMenuChunk = chunks.find( ([, value]) => value.toString().includes('value:"playlist"') && value.toString().includes("canView") && value.toString().includes("permissions") ); if (playlistMenuChunk) { - Spicetify.ReactComponent.PlaylistMenu = Object.values(require(playlistMenuChunk[0])).find(m => typeof m === "function" || typeof m === "object"); + Spicetify.ReactComponent.PlaylistMenu = Object.values(require(playlistMenuChunk[0])).find( + (m) => typeof m === "function" || typeof m === "object" + ); } const dropdownChunk = chunks.find(([, value]) => value.toString().includes("dropDown") && value.toString().includes("isSafari")); if (dropdownChunk) { - Spicetify.ReactComponent.Dropdown = Object.values(require(dropdownChunk[0])).find(m => typeof m === "function"); + Spicetify.ReactComponent.Dropdown = Object.values(require(dropdownChunk[0])).find((m) => typeof m === "function"); } const infiniteQueryChunk = chunks.find( ([_, value]) => value.toString().includes("fetchPreviousPage") && value.toString().includes("getOptimisticResult") ); if (infiniteQueryChunk) { - Spicetify.ReactQuery.useInfiniteQuery = Object.values(require(infiniteQueryChunk[0])).find(m => typeof m === "function"); + Spicetify.ReactQuery.useInfiniteQuery = Object.values(require(infiniteQueryChunk[0])).find((m) => typeof m === "function"); } - if (Spicetify.Color) Spicetify.Color.CSSFormat = modules.find(m => m?.RGBA); + if (Spicetify.Color) Spicetify.Color.CSSFormat = modules.find((m) => m?.RGBA); // Combine snackbar and notification (function bindShowNotification() { @@ -771,7 +769,7 @@ window.Spicetify = { Spicetify.showNotification = (message, isError, msTimeout) => { Spicetify.Snackbar.enqueueSnackbar(message, { variant: isError ? "error" : "default", - autoHideDuration: msTimeout + autoHideDuration: msTimeout, }); }; @@ -790,8 +788,8 @@ window.Spicetify = { setTimeout(bindColorExtractor, 10); return; } - let imageAnalysis = functionModules.find(m => m.toString().match(/\![\w$]+\.isFallback|\{extractColor/g)); - const fallbackPreset = modules.find(m => m?.colorDark); + let imageAnalysis = functionModules.find((m) => m.toString().match(/\![\w$]+\.isFallback|\{extractColor/g)); + const fallbackPreset = modules.find((m) => m?.colorDark); // Search chunk in Spotify 1.2.13 or much older because it is impossible to find any distinguishing features if (!imageAnalysis) { @@ -800,13 +798,13 @@ window.Spicetify = { (value.toString().match(/[\w$]+\.isFallback/g) || value.toString().includes("colorRaw:")) && value.toString().match(/.extractColor/g) ); if (!chunk) { - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); chunk = chunks.find(([, value]) => value.toString().match(/[\w$]+\.isFallback/g) && value.toString().match(/.extractColor/g)); } - imageAnalysis = Object.values(require(chunk[0])).find(m => typeof m === "function"); + imageAnalysis = Object.values(require(chunk[0])).find((m) => typeof m === "function"); } - Spicetify.extractColorPreset = async image => { + Spicetify.extractColorPreset = async (image) => { const analysis = await imageAnalysis(Spicetify.GraphQL.Request, image); for (const result of analysis) { if ("isFallback" in result === false) result.isFallback = fallbackPreset === result; @@ -818,7 +816,7 @@ window.Spicetify = { function wrapProvider(component) { if (!component) return null; - return props => + return (props) => Spicetify.React.createElement( Spicetify.ReactComponent.RemoteConfigProvider, { configuration: Spicetify.Platform.RemoteConfiguration }, @@ -836,12 +834,12 @@ window.Spicetify = { if (Spicetify.URI.Type) return; const URIChunk = cache - .filter(module => typeof module === "object") - .find(m => { + .filter((module) => typeof module === "object") + .find((m) => { // Avoid creating 2 arrays of the same values try { const values = Object.values(m); - return values.some(m => typeof m === "function") && values.some(m => m?.AD); + return values.some((m) => typeof m === "function") && values.some((m) => m?.AD); } catch { return false; } @@ -849,16 +847,16 @@ window.Spicetify = { const URIModules = Object.values(URIChunk); // URI.Type - Spicetify.URI.Type = URIModules.find(m => m?.AD); + Spicetify.URI.Type = URIModules.find((m) => m?.AD); // Parse functions - Spicetify.URI.from = URIModules.find(m => typeof m === "function" && m.toString().includes("allowedTypes")); - Spicetify.URI.fromString = URIModules.find(m => typeof m === "function" && m.toString().includes("Argument `uri`")); + Spicetify.URI.from = URIModules.find((m) => typeof m === "function" && m.toString().includes("allowedTypes")); + Spicetify.URI.fromString = URIModules.find((m) => typeof m === "function" && m.toString().includes("Argument `uri`")); // createURI functions - const createURIFunctions = URIModules.filter(m => typeof m === "function" && m.toString().match(/\([\w$]+\./)); + const createURIFunctions = URIModules.filter((m) => typeof m === "function" && m.toString().match(/\([\w$]+\./)); for (const type of Object.keys(Spicetify.URI.Type)) { - const func = createURIFunctions.find(m => m.toString().match(new RegExp(`\\([\\w$]+\\.${type}\(?!_\)`))); + const func = createURIFunctions.find((m) => m.toString().match(new RegExp(`\\([\\w$]+\\.${type}\(?!_\)`))); if (!func) continue; const camelCaseType = type @@ -873,19 +871,19 @@ window.Spicetify = { } // isURI functions - const isURIFUnctions = URIModules.filter(m => typeof m === "function" && m.toString().match(/=[\w$]+\./)); + const isURIFUnctions = URIModules.filter((m) => typeof m === "function" && m.toString().match(/=[\w$]+\./)); for (const type of Object.keys(Spicetify.URI.Type)) { - const func = isURIFUnctions.find(m => m.toString().match(new RegExp(`===[\\w$]+\\.${type}\(?!_\)\\}`))); + const func = isURIFUnctions.find((m) => m.toString().match(new RegExp(`===[\\w$]+\\.${type}\(?!_\)\\}`))); const camelCaseType = type .toLowerCase() .split("_") - .map(word => word[0].toUpperCase() + word.slice(1)) + .map((word) => word[0].toUpperCase() + word.slice(1)) .join(""); // Fill in missing functions, only serves as placebo as they cannot be as accurate as the original functions Spicetify.URI[`is${camelCaseType}`] = func ?? - (uri => { + ((uri) => { let uriObj; try { uriObj = Spicetify.URI.from?.(uri) ?? Spicetify.URI.fromString?.(uri); @@ -897,14 +895,14 @@ window.Spicetify = { }); } - Spicetify.URI.isPlaylistV1OrV2 = uri => Spicetify.URI.isPlaylist(uri) || Spicetify.URI.isPlaylistV2(uri); + Spicetify.URI.isPlaylistV1OrV2 = (uri) => Spicetify.URI.isPlaylist(uri) || Spicetify.URI.isPlaylistV2(uri); // Conversion functions - Spicetify.URI.idToHex = URIModules.find(m => typeof m === "function" && m.toString().includes("22===")); - Spicetify.URI.hexToId = URIModules.find(m => typeof m === "function" && m.toString().includes("32===")); + Spicetify.URI.idToHex = URIModules.find((m) => typeof m === "function" && m.toString().includes("22===")); + Spicetify.URI.hexToId = URIModules.find((m) => typeof m === "function" && m.toString().includes("32===")); // isSameIdentity - Spicetify.URI.isSameIdentity = URIModules.find(m => typeof m === "function" && m.toString().match(/[\w$]+\.id===[\w$]+\.id/)); + Spicetify.URI.isSameIdentity = URIModules.find((m) => typeof m === "function" && m.toString().match(/[\w$]+\.id===[\w$]+\.id/)); })(); Spicetify.Events.webpackLoaded.fire(); @@ -936,7 +934,7 @@ Spicetify.Events = (() => { const playerState = { cache: null, - current: null + current: null, }; const interval = setInterval(() => { @@ -990,17 +988,17 @@ Spicetify.Events = (() => { } }, 100); - Spicetify.addToQueue = uri => { + Spicetify.addToQueue = (uri) => { return Spicetify.Player.origin._queue.addToQueue(uri); }; - Spicetify.removeFromQueue = uri => { + Spicetify.removeFromQueue = (uri) => { return Spicetify.Player.origin._queue.removeFromQueue(uri); }; })(); -Spicetify.getAudioData = async uri => { - uri = uri || Spicetify.Player.data.item.uri; - const uriObj = Spicetify.URI.from?.(uri) ?? Spicetify.URI.fromString?.(uri); +Spicetify.getAudioData = async (uri) => { + const providedURI = uri || Spicetify.Player.data.item.uri; + const uriObj = Spicetify.URI.from?.(providedURI) ?? Spicetify.URI.fromString?.(providedURI); if (!uriObj || (uriObj.Type || uriObj.type) !== Spicetify.URI.Type.TRACK) { throw "URI is invalid."; } @@ -1010,7 +1008,7 @@ Spicetify.getAudioData = async uri => { ); }; -Spicetify.colorExtractor = async uri => { +Spicetify.colorExtractor = async (uri) => { const body = await Spicetify.CosmosAsync.get(`https://spclient.wg.spotify.com/colorextractor/v1/extract-presets?uri=${uri}&format=json`); if (body.entries?.length) { @@ -1025,9 +1023,9 @@ Spicetify.colorExtractor = async uri => { Spicetify.LocalStorage = { clear: () => localStorage.clear(), - get: key => localStorage.getItem(key), - remove: key => localStorage.removeItem(key), - set: (key, value) => localStorage.setItem(key, value) + get: (key) => localStorage.getItem(key), + remove: (key) => localStorage.removeItem(key), + set: (key, value) => localStorage.setItem(key, value), }; Spicetify._getStyledClassName = (args, component) => { @@ -1048,19 +1046,19 @@ Spicetify._getStyledClassName = (args, component) => { "$semanticColor", "$buttonSize", "$position", - "$iconSize" + "$iconSize", ]; const customKeys = ["blocksize"]; const customExactKeys = ["$padding", "$paddingBottom", "paddingBottom", "padding"]; const element = Array.from(args).find( - e => + (e) => e?.children || e?.dangerouslySetInnerHTML || typeof e?.className !== "undefined" || - includedKeys.some(key => typeof e?.[key] !== "undefined") || - customExactKeys.some(key => typeof e?.[key] !== "undefined") || - customKeys.some(key => Object.keys(e).some(k => k.toLowerCase().includes(key))) + includedKeys.some((key) => typeof e?.[key] !== "undefined") || + customExactKeys.some((key) => typeof e?.[key] !== "undefined") || + customKeys.some((key) => Object.keys(e).some((k) => k.toLowerCase().includes(key))) ); if (!element) return; @@ -1083,18 +1081,18 @@ Spicetify._getStyledClassName = (args, component) => { if (element[key]) className += `-${sanitizedKey}`; } - const booleanKeys = Object.keys(element).filter(key => typeof element[key] === "boolean" && element[key]); + const booleanKeys = Object.keys(element).filter((key) => typeof element[key] === "boolean" && element[key]); for (const key of booleanKeys) { if (excludedKeys.includes(key)) continue; - if (excludedPrefix.some(prefix => key.startsWith(prefix))) continue; + if (excludedPrefix.some((prefix) => key.startsWith(prefix))) continue; const sanitizedKey = key.startsWith("$") ? key.slice(1) : key; className += `-${sanitizedKey}`; } const customEntries = Object.entries(element).filter( ([key, value]) => - (customKeys.some(k => key.toLowerCase().includes(k)) || customExactKeys.some(k => key === k)) && typeof value === "string" && value.length + (customKeys.some((k) => key.toLowerCase().includes(k)) || customExactKeys.some((k) => key === k)) && typeof value === "string" && value.length ); for (const [key, value] of customEntries) { @@ -1105,33 +1103,6 @@ Spicetify._getStyledClassName = (args, component) => { return className; }; -Spicetify.getFontStyle = font => { - if (!font || !Spicetify._fontStyle) return; - let rawStyle = Spicetify._fontStyle({ variant: font, $variant: font }) - .filter(style => typeof style === "string") - .join(""); - // Clean up empty rulesets - rawStyle = rawStyle.replace(/\w+-\w+:;/g, "").trim(); - // Split special rulesets - const mediaStyle = rawStyle.split("@"); - let returnStyle = `.main-type-${font}`; - - mediaStyle.map((ruleset, index) => { - if (index === 0) { - returnStyle += `{${ruleset}}`; - return; - } - let newRuleset; - if (ruleset.endsWith(";")) newRuleset = ruleset.slice(0, -1); - newRuleset = ruleset.split(")").join(`){.main-type-${font}`); - returnStyle += `@${newRuleset}}`; - return; - }); - - if (returnStyle.endsWith(";")) returnStyle = returnStyle.slice(0, -1); - return returnStyle.replaceAll(";;", ";"); -}; - (function waitMouseTrap() { if (!Spicetify.Mousetrap) { setTimeout(waitMouseTrap, 10); @@ -1243,7 +1214,7 @@ Spicetify.getFontStyle = font => { "<": ",", ">": ".", "?": "/", - "|": "\\" + "|": "\\", }; function formatKeys(keys) { @@ -1270,18 +1241,18 @@ Spicetify.getFontStyle = font => { registerShortcut: (keys, callback) => { Spicetify.Mousetrap.bind(formatKeys(keys), callback); }, - _deregisterShortcut: keys => { + _deregisterShortcut: (keys) => { Spicetify.Mousetrap.unbind(formatKeys(keys)); }, changeShortcut: (keys, newKeys) => { if (!keys || !newKeys) throw "Spicetify.Keyboard.changeShortcut: Invalid keys"; - const callback = Object.keys(Spicetify.Mousetrap.trigger()._directMap).find(key => key.startsWith(formatKeys(keys))); + const callback = Object.keys(Spicetify.Mousetrap.trigger()._directMap).find((key) => key.startsWith(formatKeys(keys))); if (!callback) throw "Spicetify.Keyboard.changeShortcut: Shortcut not found"; Spicetify.Keyboard.registerShortcut(newKeys, Spicetify.Mousetrap.trigger()._directMap[callback]); Spicetify.Keyboard._deregisterShortcut(keys); - } + }, }; Spicetify.Keyboard.registerIsolatedShortcut = Spicetify.Keyboard.registerShortcut; Spicetify.Keyboard.registerImportantShortcut = Spicetify.Keyboard.registerShortcut; @@ -1419,7 +1390,7 @@ Spicetify.SVGIcons = { '', watch: '', - x: '' + x: '', }; (async function waitUserAPI() { @@ -1437,7 +1408,7 @@ Spicetify.SVGIcons = { Spicetify.Platform?.ProductStateAPI.productStateApi; Spicetify.AppTitle = { - set: async name => { + set: async (name) => { if (subRequest) subRequest.cancel(); await productState.putOverridesValues({ pairs: { name } }); subRequest = productState.subValues({ keys: ["name"] }, ({ pairs }) => { @@ -1455,28 +1426,14 @@ Spicetify.SVGIcons = { if (subRequest) subRequest.cancel(); await productState.delOverridesValues({ keys: ["name"] }); }, - sub: callback => { + sub: (callback) => { return productState.subValues({ keys: ["name"] }, ({ pairs }) => { callback(pairs.name); }); - } + }, }; })(); -(function appendAllFontStyle() { - if (!Spicetify._fontStyle) { - setTimeout(appendAllFontStyle, 1000); - return; - } - const fontList = (Spicetify._fontStyleList?.toString() ?? Spicetify._fontStyle.toString()).match(/"\w+"/g).map(font => font.replace(/"/g, "")); - const fontStyle = document.createElement("style"); - fontStyle.className = "spicetify-font"; - for (const font of fontList) { - fontStyle.innerHTML += Spicetify.getFontStyle(font); - } - return document.head.appendChild(fontStyle); -})(); - function parseIcon(icon, size = 16) { if (icon && Spicetify.SVGIcons[icon]) { return `${Spicetify.SVGIcons[icon]}`; @@ -1490,8 +1447,8 @@ function createIconComponent(icon, size = 16) { { iconSize: size, dangerouslySetInnerHTML: { - __html: parseIcon(icon) - } + __html: parseIcon(icon), + }, }, null ); @@ -1555,12 +1512,12 @@ Spicetify.ContextMenuV2 = (() => { return Spicetify.React.createElement(Spicetify.ReactComponent.MenuItem, { disabled: _disabled, divider: _divider, - onClick: e => { + onClick: (e) => { onClick(context, this, e); }, leadingIcon: _leadingIcon && createIconComponent(_leadingIcon), trailingIcon: _trailingIcon && createIconComponent(_trailingIcon), - children: _children + children: _children, }); }, {}); } @@ -1614,7 +1571,7 @@ Spicetify.ContextMenuV2 = (() => { } class ItemSubMenu { - static itemsToComponents = items => items.map(item => item._element); + static itemsToComponents = (items) => items.map((item) => item._element); constructor({ text, disabled = false, leadingIcon, divider, items, shouldAdd = () => true }) { this.shouldAdd = shouldAdd; @@ -1654,7 +1611,7 @@ Spicetify.ContextMenuV2 = (() => { onClick: () => undefined, disabled: _disabled, leadingIcon: _leadingIcon && createIconComponent(_leadingIcon), - children: ItemSubMenu.itemsToComponents(_items) + children: ItemSubMenu.itemsToComponents(_items), }); }, {}); } @@ -1786,14 +1743,14 @@ Spicetify.ContextMenu = (() => { disabled, leadingIcon: icon, trailingIcon, - onClick: context => { + onClick: (context) => { const [uris, uids, contextUri] = Spicetify.ContextMenuV2.parseProps(context.props); onClick(uris, uids, contextUri); }, - shouldAdd: props => { + shouldAdd: (props) => { const parsedProps = Spicetify.ContextMenuV2.parseProps(props); return parsedProps && shouldAdd(...parsedProps); - } + }, }); } @@ -1821,10 +1778,10 @@ Spicetify.ContextMenu = (() => { disabled, leadingIcon: icon, items, - shouldAdd: props => { + shouldAdd: (props) => { const parsedProps = Spicetify.ContextMenuV2.parseProps(props); return parsedProps && shouldAdd(...parsedProps); - } + }, }); } @@ -1843,7 +1800,7 @@ let navLinkFactoryCtx = null; let refreshNavLinks = null; Spicetify._renderNavLinks = (list, isTouchScreenUi, isPreLibX = false) => { - const [refreshCount, refresh] = Spicetify.React.useReducer(x => x + 1, 0); + const [refreshCount, refresh] = Spicetify.React.useReducer((x) => x + 1, 0); refreshNavLinks = refresh; if ( @@ -1884,33 +1841,37 @@ Spicetify._renderNavLinks = (list, isTouchScreenUi, isPreLibX = false) => { registered.push({ appProper, appRoutePath, icon, activeIcon }); } - const style = document.createElement("style"); - style.innerHTML = ` -:root { - --max-custom-navlink-count: 4; -} + (function addStyling() { + if (document.querySelector("style.spicetify-navlinks")) return; + const style = document.createElement("style"); + style.className = "spicetify-navlinks"; + style.innerHTML = ` + :root { + --max-custom-navlink-count: 4; + } -.custom-navlinks-scrollable_container { - max-width: calc(48px * var(--max-custom-navlink-count) + 8px * (var(--max-custom-navlink-count) - 1)); - -webkit-app-region: no-drag; -} + .custom-navlinks-scrollable_container { + max-width: calc(48px * var(--max-custom-navlink-count) + 8px * (var(--max-custom-navlink-count) - 1)); + -webkit-app-region: no-drag; + } -.custom-navlinks-scrollable_container div[role="presentation"] > *:not(:last-child) { - margin-inline-end: 8px; -} + .custom-navlinks-scrollable_container div[role="presentation"] > *:not(:last-child) { + margin-inline-end: 8px; + } -.custom-navlinks-scrollable_container div[role="presentation"] { - display: flex; - flex-direction: row; -} + .custom-navlinks-scrollable_container div[role="presentation"] { + display: flex; + flex-direction: row; + } -.custom-navlink { - -webkit-app-region: unset; -} - `; - document.head.appendChild(style); + .custom-navlink { + -webkit-app-region: unset; + } + `; + document.head.appendChild(style); + })(); - const wrapScrollableContainer = element => + const wrapScrollableContainer = (element) => Spicetify.React.createElement( "div", { className: "custom-navlinks-scrollable_container" }, @@ -1921,7 +1882,7 @@ Spicetify._renderNavLinks = (list, isTouchScreenUi, isPreLibX = false) => { Spicetify.React.createElement( navLinkFactoryCtx.Provider, { value: navLinkFactory }, - registered.map(NavLinkElement => Spicetify.React.createElement(NavLink, NavLinkElement, null)) + registered.map((NavLinkElement) => Spicetify.React.createElement(NavLink, NavLinkElement, null)) ); return isTouchScreenUi ? wrapScrollableContainer(NavLinks()) : NavLinks(); @@ -1949,10 +1910,10 @@ const NavLinkSidebarLegacy = ({ appProper, appRoutePath, createIcon, isActive }) to: appRoutePath, referrer: "other", className: Spicetify.classnames("link-subtle", "main-navBar-navBarLink", { - "main-navBar-navBarLinkActive active": isActive + "main-navBar-navBarLinkActive active": isActive, }), onClick: () => undefined, - "aria-label": appProper + "aria-label": appProper, }, createIcon(), Spicetify.React.createElement(Spicetify.ReactComponent.TextComponent, { variant: "mestoBold" }, appProper) @@ -1976,10 +1937,10 @@ const NavLinkSidebar = ({ appProper, appRoutePath, createIcon, isActive }) => { to: appRoutePath, referrer: "other", className: Spicetify.classnames("link-subtle", "main-yourLibraryX-navLink", { - "main-yourLibraryX-navLinkActive": isActive + "main-yourLibraryX-navLinkActive": isActive, }), onClick: () => undefined, - "aria-label": appProper + "aria-label": appProper, }, createIcon(), !isSidebarCollapsed && Spicetify.React.createElement(Spicetify.ReactComponent.TextComponent, { variant: "balladBold" }, appProper) @@ -1995,10 +1956,10 @@ const NavLinkGlobal = ({ appProper, appRoutePath, createIcon, isActive }) => { Spicetify.React.createElement(Spicetify.ReactComponent.ButtonTertiary, { iconOnly: createIcon, className: Spicetify.classnames("link-subtle", "main-globalNav-navLink", "main-globalNav-link-icon", "custom-navlink", { - "main-globalNav-navLinkActive": isActive + "main-globalNav-navLinkActive": isActive, }), "aria-label": appProper, - onClick: () => Spicetify.Platform.History.push(appRoutePath) + onClick: () => Spicetify.Platform.History.push(appRoutePath), }) ); }; @@ -2030,7 +1991,7 @@ class _HTMLGenericModal extends HTMLElement { const hidePopup = this.hide.bind(this); // Listen for click events on Overlay - this.querySelector(".GenericModal__overlay").addEventListener("click", event => { + this.querySelector(".GenericModal__overlay").addEventListener("click", (event) => { if (!this.querySelector(".GenericModal").contains(event.target)) hidePopup(); }); @@ -2084,9 +2045,9 @@ Object.defineProperty(Spicetify, "TippyProps", { instance.popper.firstChild.classList.remove("main-contextMenu-tippyEnterActive"); instance.unmount(); }); - } + }, }, - writable: false + writable: false, }); Spicetify.Topbar = (() => { @@ -2105,7 +2066,7 @@ Spicetify.Topbar = (() => { this.disabled = disabled; this.tippy = Spicetify.Tippy?.(this.element, { content: label, - ...Spicetify.TippyProps + ...Spicetify.TippyProps, }); this.label = label; @@ -2227,7 +2188,7 @@ Spicetify.Playbar = (() => { addClassname(this.element); this.tippy = Spicetify.Tippy?.(this.element, { content: label, - ...Spicetify.TippyProps + ...Spicetify.TippyProps, }); this.label = label; registerOnCreate && this.register(); @@ -2320,7 +2281,7 @@ Spicetify.Playbar = (() => { this.active = active; this.tippy = Spicetify.Tippy?.(this.element, { content: label, - ...Spicetify.TippyProps + ...Spicetify.TippyProps, }); this.label = label; registerOnCreate && this.register(); @@ -2394,7 +2355,7 @@ Spicetify.Playbar = (() => { return; } waitForWidgetMounted(); - const observer = new MutationObserver(mutations => { + const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.removedNodes.length > 0) { nowPlayingWidget = null; @@ -2425,7 +2386,7 @@ Spicetify.Playbar = (() => { const changelogRawDataOld = body.match(/## What's Changed([\s\S]*?)\r\n\r/)?.[1]; if (changelogRawDataOld) { changelog = [...changelogRawDataOld.matchAll(/\r\n\*\s(.+?)\sin\shttps/g)] - .map(match => { + .map((match) => { const featureData = match[1].split("@"); const feature = featureData[0]; const committerID = featureData[1]; @@ -2434,10 +2395,10 @@ Spicetify.Playbar = (() => { .join("\n"); } else { const sections = body.split("\n## "); - const filteredSections = sections.filter(section => !section.startsWith("Compatibility")); + const filteredSections = sections.filter((section) => !section.startsWith("Compatibility")); const filteredText = filteredSections.join("\n## "); changelog = [...filteredText.matchAll(/- (?:\*\*(.+?)\*\*:? )?(.+?) \(\[(.+?)\]\((.+?)\)\)/g)] - .map(match => { + .map((match) => { const feature = match[1]; const description = match[2]; const prNumber = match[3]; @@ -2526,7 +2487,7 @@ Spicetify.Playbar = (() => { const tippy = Spicetify.Tippy(content.querySelectorAll("pre"), { content: "Click to copy", hideOnClick: false, - ...Spicetify.TippyProps + ...Spicetify.TippyProps, }); for (const instance of tippy) { @@ -2541,7 +2502,7 @@ Spicetify.Playbar = (() => { const updateModal = { title: "Update Spicetify", content, - isLarge: true + isLarge: true, }; new Spicetify.Topbar.Button( diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 7ea8081882..a148df2b8d 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -616,18 +616,6 @@ func exposeAPIs_vendor(input string) string { } } - // for >=1.2.43 - utils.ReplaceOnce(&input, `,(\w+)=(function\(\w+\)\{[^}]*?case"ballad"[^}]*?\})`, func(submatches ...string) string { - return fmt.Sprintf(",%s=Spicetify._fontStyleList=%s", submatches[1], submatches[2]) - }) - - utils.ReplaceOnce( - &input, - `\(function\(\w+\)\{return \w+\.\$?variant\?(\s*\w+\(\w+\.\$variant\)|function\(\w+\)\{)`, - func(submatches ...string) string { - return fmt.Sprintf("Spicetify._fontStyle=%s", submatches[0]) - }) - // Mapping styled-components classes utils.Replace( &input, From f7d07a16b0376884c0fdb2e9351081c22f859875 Mon Sep 17 00:00:00 2001 From: em Date: Sun, 4 Aug 2024 14:21:09 +0200 Subject: [PATCH 017/146] feat(lyrics-plus/lrclib): use `x-user-agent` CORS accepts it and is logged by lrclib: https://github.com/tranxuanthang/lrclib/blob/main/server/src/lib.rs#L97 --- CustomApps/lyrics-plus/ProviderLRCLIB.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CustomApps/lyrics-plus/ProviderLRCLIB.js b/CustomApps/lyrics-plus/ProviderLRCLIB.js index 0ff4576ced..c72a9c2217 100644 --- a/CustomApps/lyrics-plus/ProviderLRCLIB.js +++ b/CustomApps/lyrics-plus/ProviderLRCLIB.js @@ -15,7 +15,7 @@ const ProviderLRCLIB = (() => { const body = await fetch(finalURL, { headers: { - "user-agent": `spicetify v${Spicetify.Config.version} (https://github.com/spicetify/cli)`, + "x-user-agent": `spicetify v${Spicetify.Config.version} (https://github.com/spicetify/cli)`, }, }); From c573da29ebd9a97142faf04e801a6856a6f82100 Mon Sep 17 00:00:00 2001 From: em Date: Mon, 5 Aug 2024 17:08:47 +0200 Subject: [PATCH 018/146] fix(preprocess): disable sentry correctly and replace hex value (#3125) --- src/preprocess/preprocess.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index a148df2b8d..ac6c8302c8 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -186,7 +186,7 @@ func colorVariableReplace(content string) string { utils.Replace(&content, "#121212", func(submatches ...string) string { return "var(--spice-main)" }) - utils.Replace(&content, "#242424", func(submatches ...string) string { + utils.Replace(&content, `#(242424|1f1f1f)`, func(submatches ...string) string { return "var(--spice-main-elevated)" }) @@ -306,7 +306,7 @@ func colorVariableReplaceForJS(content string) string { } func disableSentry(input string) string { - utils.Replace(&input, `(\("[^"]+sentry.io)/`, func(submatches ...string) string { + utils.Replace(&input, `\(([^,]+),([^,]+),\{sampleRate:([^,]+),tracesSampleRate:([^,]+)(,.*?)?\}`, func(submatches ...string) string { return fmt.Sprintf(",%s", submatches[0]) }) return input From 1f4caf0a74200a4716b07be08266df7a3fc9146d Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 5 Aug 2024 18:06:41 +0200 Subject: [PATCH 019/146] fix(preprocess/sentry): disable sentry based on days --- src/preprocess/preprocess.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index ac6c8302c8..804b247b47 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -306,8 +306,12 @@ func colorVariableReplaceForJS(content string) string { } func disableSentry(input string) string { - utils.Replace(&input, `\(([^,]+),([^,]+),\{sampleRate:([^,]+),tracesSampleRate:([^,]+)(,.*?)?\}`, func(submatches ...string) string { - return fmt.Sprintf(",%s", submatches[0]) + //utils.Replace(&input, `\(([^,]+),([^,]+),\{sampleRate:([^,]+),tracesSampleRate:([^,]+)(,.*?)?\}`, func(submatches ...string) string { + // return fmt.Sprintf(",%s", submatches[0]) + //}) + // Spotify enables sentry only for versions that are newer than 30 days old. + utils.Replace(&input, "/864e5<30", func(submatches ...string) string { + return "<0" }) return input } From 4495458bbfb0afae347143faba341ea31d59f5fb Mon Sep 17 00:00:00 2001 From: em Date: Tue, 6 Aug 2024 21:52:16 +0200 Subject: [PATCH 020/146] feat(update): run `backup apply` for stock spotify (#3124) --- spicetify.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/spicetify.go b/spicetify.go index b59dd7d4ea..d42a5009a5 100644 --- a/spicetify.go +++ b/spicetify.go @@ -232,13 +232,13 @@ func main() { utils.PrintBold("spicetify v" + version) if slices.Contains(commands, "upgrade") || slices.Contains(commands, "update") { updateStatus := cmd.Update(version) - if updateStatus { - ex, err := os.Executable() - if err != nil { - ex = "spicetify" - } + spotifyPath := filepath.Join(utils.FindAppPath(), "Apps") + ex, err := os.Executable() + if err != nil { + ex = "spicetify" + } - spotifyPath := filepath.Join(utils.FindAppPath(), "Apps") + if updateStatus { spotStat := spotifystatus.Get(spotifyPath) cmds := []string{"backup", "apply"} if !spotStat.IsBackupable() { @@ -251,6 +251,17 @@ func main() { cmd = exec.Command(ex, strings.Join(commands[:], " ")) utils.CmdScanner(cmd) } + + spotStat := spotifystatus.Get(spotifyPath) + if spotStat.IsBackupable() { + utils.PrintNote("spicetify is already up-to-date! If you ran this command because spicetify disappeared after Spotify updated, we'll attempt to fix it for you right now.") + cmd.Backup(version) + cmd.CheckStates() + cmd.InitSetting() + cmd.Apply(version) + restartSpotify() + } + return } else { cmd.CheckUpdate(version) From aa6b3ee11745810336f8caf00e2b0ec1dd88d519 Mon Sep 17 00:00:00 2001 From: machinemessiah Date: Tue, 6 Aug 2024 15:52:37 -0400 Subject: [PATCH 021/146] fix(new-releases): use `getContents` for `getArtistsList` (#3127) Co-authored-by: em --- CustomApps/new-releases/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CustomApps/new-releases/index.js b/CustomApps/new-releases/index.js index 7e6b4e19ff..c2c2b9c7cf 100644 --- a/CustomApps/new-releases/index.js +++ b/CustomApps/new-releases/index.js @@ -269,8 +269,14 @@ class Grid extends react.Component { } async function getArtistList() { - const base = await Spicetify.Platform.LibraryAPI.getArtists(); - const artists = await Spicetify.Platform.LibraryAPI.getArtists({ limit: base.totalLength }); + const config = { + filters: ["1"], + sortOrder: ["0"], + textFilter: "", + offset: 0, + limit: (await Spicetify.Platform.LibraryAPI.getContents())?.totalLength ?? 0, + }; + const artists = await Spicetify.Platform.LibraryAPI.getContents(config); count(true); return artists.items ?? []; } From 3d61360cc1cfabd7b80aa4026d0d7bcfc461c78c Mon Sep 17 00:00:00 2001 From: em Date: Tue, 6 Aug 2024 21:55:32 +0200 Subject: [PATCH 022/146] fix(new-releases): use `50000` as a limit --- CustomApps/new-releases/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CustomApps/new-releases/index.js b/CustomApps/new-releases/index.js index c2c2b9c7cf..ab2817dcf4 100644 --- a/CustomApps/new-releases/index.js +++ b/CustomApps/new-releases/index.js @@ -274,7 +274,7 @@ async function getArtistList() { sortOrder: ["0"], textFilter: "", offset: 0, - limit: (await Spicetify.Platform.LibraryAPI.getContents())?.totalLength ?? 0, + limit: 50000, }; const artists = await Spicetify.Platform.LibraryAPI.getContents(config); count(true); From 7670f7cc87353eaec05f597f1a0a20583a0379b8 Mon Sep 17 00:00:00 2001 From: irlbunny <87990853+irlbunny@users.noreply.github.com> Date: Thu, 8 Aug 2024 01:59:25 -0400 Subject: [PATCH 023/146] fix(lyrics-plus/genius): remove `encodeURIComponent` (#3131) --- CustomApps/lyrics-plus/ProviderGenius.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CustomApps/lyrics-plus/ProviderGenius.js b/CustomApps/lyrics-plus/ProviderGenius.js index 35e8b7170a..d4e254b0fc 100644 --- a/CustomApps/lyrics-plus/ProviderGenius.js +++ b/CustomApps/lyrics-plus/ProviderGenius.js @@ -104,7 +104,7 @@ const ProviderGenius = (() => { let lyrics; let hits; for (const title of titles) { - const query = new URLSearchParams({ per_page: 20, q: encodeURIComponent(`${title} ${info.artist}`) }); + const query = new URLSearchParams({ per_page: 20, q: `${info.artist} ${title}` }); const url = `https://genius.com/api/search/song?${query.toString()}`; const geniusSearch = await Spicetify.CosmosAsync.get(url); From e3c48325c21c3cf0cf485ad3cc558a02e973b1c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:02:09 +0200 Subject: [PATCH 024/146] chore(deps): bump golang.org/x/net from 0.27.0 to 0.28.0 (#3129) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5bcbc1d808..638ee113a9 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.13 - golang.org/x/net v0.27.0 + golang.org/x/net v0.28.0 ) require ( github.com/mattn/go-isatty v0.0.16 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/sys v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index ba201ee399..ec34b67a62 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f02128b271954ac97abc3638ac706f277c018dc6 Mon Sep 17 00:00:00 2001 From: irlbunny <87990853+irlbunny@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:11:02 -0400 Subject: [PATCH 025/146] style(lyrics-plus): adjust lyric animation in `karaoke` mode (#3130) --- CustomApps/lyrics-plus/Pages.js | 2 ++ CustomApps/lyrics-plus/style.css | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CustomApps/lyrics-plus/Pages.js b/CustomApps/lyrics-plus/Pages.js index 98dbe44187..5f99b25156 100755 --- a/CustomApps/lyrics-plus/Pages.js +++ b/CustomApps/lyrics-plus/Pages.js @@ -70,6 +70,8 @@ const KaraokeLine = ({ text, isActive, position, startTime }) => { className: `lyrics-lyricsContainer-Karaoke-Word${isWordActive ? " lyrics-lyricsContainer-Karaoke-WordActive" : ""}`, style: { "--word-duration": `${time}ms`, + // don't animate unless we have to + transition: !isWordActive ? "all 0s linear" : "", }, }, word diff --git a/CustomApps/lyrics-plus/style.css b/CustomApps/lyrics-plus/style.css index 1a72252c3d..56f12b94ca 100644 --- a/CustomApps/lyrics-plus/style.css +++ b/CustomApps/lyrics-plus/style.css @@ -303,11 +303,23 @@ div.lyrics-tabBar-headerItemLink { .lyrics-lyricsContainer-Karaoke-WordActive { color: var(--lyrics-color-active) !important; + background-position: top left !important; } .lyrics-lyricsContainer-Karaoke-Word { color: var(--lyrics-color-inactive); - transition: var(--word-duration) color ease; + background-image: linear-gradient(to right, + var(--lyrics-color-active), + var(--lyrics-color-active) 45%, + var(--lyrics-color-inactive) 55%, + var(--lyrics-color-inactive)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-size: 225% 100%; + background-position: top left 100%; + transition-property: color, background-position; + transition-duration: calc(var(--word-duration) + 0.05s); + transition-timing-function: linear; } .lyrics-lyricsContainer-LyricsLine a { From b63a9c767b8674265d4c0bfa3fd6ec2abb13adb0 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 12 Aug 2024 23:10:50 +0200 Subject: [PATCH 026/146] fix(preprocess): adapt `_platform` regex for `1.2.44` thanks @machinemessiah --- src/preprocess/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 804b247b47..35448140f9 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -488,7 +488,7 @@ func exposeAPIs_main(input string) string { // Spicetify._platform utils.Replace( &input, - `(setTitlebarHeight[\w(){}.,&$!=;"" ]+)(\{version:[\w$]+,)`, + `(setTitlebarHeight[\w(){}>:.,&$!=;"" ]+)(\{version:[a-zA-Z_\$][\w\$]*,)`, func(submatches ...string) string { return fmt.Sprintf("%sSpicetify._platform=%s", submatches[1], submatches[2]) }) From 24630891ffbd47d87aab3cba7ea74f322af31e4c Mon Sep 17 00:00:00 2001 From: harbassan <84175605+harbassan@users.noreply.github.com> Date: Wed, 14 Aug 2024 07:12:56 +1200 Subject: [PATCH 027/146] fix(wrapper): replace find string for `Slider` component (#3134) --- jsHelper/spicetifyWrapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index ac5d0ea1c3..d9476c3ef6 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -567,7 +567,7 @@ window.Spicetify = { Menu: functionModules.find((m) => m.toString().includes("getInitialFocusElement") && m.toString().includes("children")), MenuItem: functionModules.find((m) => m.toString().includes("handleMouseEnter") && m.toString().includes("onClick")), MenuSubMenuItem: functionModules.find((f) => f.toString().includes("subMenuIcon")), - Slider: wrapProvider(functionModules.find((m) => m.toString().includes("onStepBackward") && !m.toString().includes("volume"))), + Slider: wrapProvider(functionModules.find((m) => m.toString().includes("progressBarRef"))), RemoteConfigProvider: functionModules.find((m) => m.toString().includes("resolveSuspense") && m.toString().includes("configuration")), RightClickMenu: functionModules.find( (m) => From ece3590bfb3f4b6684c766665dc47bd6c10734e5 Mon Sep 17 00:00:00 2001 From: Delusoire Date: Thu, 15 Aug 2024 15:40:24 +0100 Subject: [PATCH 028/146] fix(workflows): only run build & linter on main branches (#3137) --- .github/workflows/build.yml | 8 ++++---- .github/workflows/linter.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c85f6ed06f..28fb52d0ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,12 +3,12 @@ name: Build on: pull_request: branches: - - main - - "*/main/**" + - "main" + - "*/main/*/**" push: branches: - - main - - "*/main/**" + - "main" + - "*/main/*/**" release: types: [published] diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 35b39971fc..b8dc272ebe 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -3,12 +3,12 @@ name: Code quality on: pull_request: branches: - - main - - "*/main/**" + - "main" + - "*/main/*/**" push: branches: - - main - - "*/main/**" + - "main" + - "*/main/*/**" jobs: linter: From 78b60fe57c0cf27a8afb6fb5b2c4cbb68c26cf5b Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 24 Aug 2024 19:15:35 +0200 Subject: [PATCH 029/146] fix(preprocess): match more special characters for `_platform` --- src/preprocess/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 35448140f9..fe43e3dcdf 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -488,7 +488,7 @@ func exposeAPIs_main(input string) string { // Spicetify._platform utils.Replace( &input, - `(setTitlebarHeight[\w(){}>:.,&$!=;"" ]+)(\{version:[a-zA-Z_\$][\w\$]*,)`, + `(setTitlebarHeight[\w(){}<>:.,&$!=;""?!#% ]+)(\{version:[a-zA-Z_\$][\w\$]*,)`, func(submatches ...string) string { return fmt.Sprintf("%sSpicetify._platform=%s", submatches[1], submatches[2]) }) From 3e382d952a3db811c0efec8b77d708672208b905 Mon Sep 17 00:00:00 2001 From: Nam Anh Date: Sun, 25 Aug 2024 17:58:33 +0700 Subject: [PATCH 030/146] fix(wrapper): wait until all modules loaded (#3143) --- jsHelper/spicetifyWrapper.js | 55 ++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index d9476c3ef6..f8df3ff4b5 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -185,6 +185,7 @@ window.Spicetify = { "ContextMenuV2", "ReactJSX", "_renderNavLinks", + "Events", ]), }, { @@ -347,7 +348,7 @@ window.Spicetify = { if (Spicetify.Events.platformLoaded.callbacks.length) Spicetify.Events.platformLoaded.fire(); })(); -(function addProxyCosmos() { +(async function addProxyCosmos() { if (!Spicetify.Player.origin?._cosmos && !Spicetify.Platform?.Registry) { setTimeout(addProxyCosmos, 50); return; @@ -436,6 +437,7 @@ window.Spicetify = { }, }; + while (!Spicetify.Player.origin) await new Promise((r) => setTimeout(r, 50)); Spicetify.Player.origin._cosmos = new Proxy(_cosmos, handler); Object.defineProperty(Spicetify, "CosmosAsync", { get: () => { @@ -444,19 +446,54 @@ window.Spicetify = { }); })(); -(function hotloadWebpackModules() { +(async function hotloadWebpackModules() { if (!window?.webpackChunkclient_web) { - setTimeout(hotloadWebpackModules, 50); + await new Promise((r) => setTimeout(r, 50)); return; } + // Force all webpack modules to load const require = webpackChunkclient_web.push([[Symbol()], {}, (re) => re]); - const chunks = require.m ? Object.entries(require.m) : []; - if (!chunks) { - setTimeout(hotloadWebpackModules, 50); - return; - } - const cache = Object.keys(require.m).map((id) => require(id)); + while (!require.m) await new Promise((r) => setTimeout(r, 50)); + + let chunks = Object.entries(require.m); + let cache = Object.keys(require.m).map((id) => require(id)); + + // For _renderNavLinks to work + Spicetify.React = cache.find((m) => m?.useMemo); + + // Get all script tags matching root directory + // Some link tags modules are not included in require.m/unused + const scripts = [...document.querySelectorAll("script")] + // Get scripts from root dir + .filter((script) => script.src?.includes("xpui.app.spotify.com")) + // Filter out non-webpack scripts + .filter((script) => ["extensions", "spicetify", "helper", "theme"].every((str) => !script.src?.includes(str))); + + await Promise.all( + scripts.map(async (script) => { + try { + const res = await fetch(script.src); + const text = await res.text(); + const src = script.src.split("/").pop(); + console.log(`[spicetifyWrapper] Waiting for ${src}`); + for (const pack of text.match(/(?:,|{)(\d+): ?\(.,.,./g).map((str) => str.slice(0, -7).slice(1))) { + // console.debug(`[spicetifyWrapper] Waiting for ${pack} of ${src}`); + while (!require.m || !Object.keys(require.m).includes(pack)) { + await new Promise((r) => setTimeout(r, 100)); + } + } + console.log(`[spicetifyWrapper] Loaded ${src}`); + } catch (e) { + return console.error(e); + } + }) + ).then(() => { + console.log("[spicetifyWrapper] All required webpack modules loaded"); + chunks = Object.entries(require.m); + cache = Object.keys(require.m).map((id) => require(id)); + }); + const modules = cache .filter((module) => typeof module === "object") .flatMap((module) => { From 5cee0d43569f04390e0f88a0a910448cfe43b86e Mon Sep 17 00:00:00 2001 From: ririxi Date: Sun, 25 Aug 2024 14:55:20 +0200 Subject: [PATCH 031/146] fix(wrapper/webpack): do not return while webpack is loading --- jsHelper/spicetifyWrapper.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index f8df3ff4b5..89b90c5d0c 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -447,9 +447,8 @@ window.Spicetify = { })(); (async function hotloadWebpackModules() { - if (!window?.webpackChunkclient_web) { + while (!window?.webpackChunkclient_web) { await new Promise((r) => setTimeout(r, 50)); - return; } // Force all webpack modules to load From 5730254247e70b1d84bad0976b2621f7b254a6a8 Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Tue, 27 Aug 2024 02:19:53 +0900 Subject: [PATCH 032/146] feat(popupLyrics): add lrclib provider (#3133) --- Extensions/popupLyrics.js | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index 0660d30634..abde9ec914 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -248,6 +248,62 @@ function PopupLyrics() { return { lyrics }; }, + + async fetchLrclib(info) { + const baseURL = "https://lrclib.net/api/get"; + const durr = info.duration / 1000; + const params = { + track_name: info.title, + artist_name: info.artist, + album_name: info.album, + duration: durr, + }; + + const finalURL = `${baseURL}?${Object.keys(params) + .map((key) => `${key}=${encodeURIComponent(params[key])}`) + .join("&")}`; + + const body = await fetch(finalURL, { + headers: { + "x-user-agent": `spicetify v${Spicetify.Config.version} (https://github.com/spicetify/cli)`, + }, + }); + + if (body.status !== 200) { + return { error: "Request error: Track wasn't found" }; + } + + const meta = await body.json(); + if (meta?.instrumental) { + return { error: "Instrumental" }; + } + if (!meta?.syncedLyrics) { + return { error: "No synced lyrics" }; + } + + // Preprocess lyrics by removing [tags] and empty lines + const lines = meta?.syncedLyrics + .replaceAll(/\[[a-zA-Z]+:.+\]/g, "") + .trim() + .split("\n"); + + const syncedTimestamp = /\[([0-9:.]+)\]/; + const isSynced = lines[0].match(syncedTimestamp); + + const lyrics = lines.map((line) => { + const time = line.match(syncedTimestamp)?.[1]; + const lyricContent = line.replace(syncedTimestamp, "").trim(); + const lyric = lyricContent.replaceAll(/\<([0-9:.]+)\>/g, "").trim(); + const [min, sec] = time.replace(/\[\]\<\>/, "").split(":"); + + if (line.trim() !== "" && isSynced && time) { + return { text: lyric || "♪", startTime: Number(min) * 60 + Number(sec) }; + } + return; + }); + + return { lyrics }; + }, }; const userConfigs = { @@ -276,6 +332,11 @@ function PopupLyrics() { call: LyricProviders.fetchSpotify, desc: "Lyrics sourced from official Spotify API.", }, + lrclib: { + on: boolLocalStorage("popup-lyrics:services:lrclib:on"), + call: LyricProviders.fetchLrclib, + desc: "Lyrics sourced from lrclib.net. Supports both synced and unsynced lyrics. LRCLIB is a free and open-source lyrics provider.", + }, }, servicesOrder: [], }; From af872eaf5bc66471435b3c92549460df3f06ccf1 Mon Sep 17 00:00:00 2001 From: Thomas Fitzpatrick <22730962+ohitstom@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:20:13 +0100 Subject: [PATCH 033/146] feat(css-map): map `search-searchCategory-carouselButtonVisible` (#3146) --- css-map.json | 1 + 1 file changed, 1 insertion(+) diff --git a/css-map.json b/css-map.json index 3a8a85180c..d0030ebc0e 100644 --- a/css-map.json +++ b/css-map.json @@ -1229,6 +1229,7 @@ "ijZQH9pePkbB2MbJHCJV": "search-searchCategory-carousel", "XTk61Y8OkBdUT6Wj4F6i": "search-searchCategory-carouselButton", "VfDGbMWaJe9rcefizTNk": "search-searchCategory-carouselButtonVisible", + "Nd2dSpwo9xYae8YuQIkb": "search-searchCategory-carouselButtonVisible", "ZWI7JsjzJaR_G8Hy4W6J": "search-searchCategory-categoryGridItem", "UnwG2v9ISmcUhnjKj22Y": "search-searchCategory-categoryGridItem", "KjPUGV8uMbl_0bvk9ePv": "search-searchCategory-catergoryGrid", From 18aea02a21596f41f602e2dded612c6a0098c128 Mon Sep 17 00:00:00 2001 From: ririxi Date: Tue, 27 Aug 2024 17:16:03 +0200 Subject: [PATCH 034/146] fix(wrapper): fire `platformLoaded` event later --- jsHelper/spicetifyWrapper.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 89b90c5d0c..eac1e93875 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -319,8 +319,6 @@ window.Spicetify = { Spicetify.Platform[key] = _platform[key]; } } - - if (!Spicetify.Platform.Registry) Spicetify.Events.platformLoaded.fire(); })(); (function addMissingPlatformAPIs() { @@ -344,8 +342,6 @@ window.Spicetify = { Spicetify.Platform[symbolName] = resolvedAPI; console.debug(`[spicetifyWrapper] Resolved PlatformAPI from Registry: ${symbolName}`); } - - if (Spicetify.Events.platformLoaded.callbacks.length) Spicetify.Events.platformLoaded.fire(); })(); (async function addProxyCosmos() { @@ -491,6 +487,9 @@ window.Spicetify = { console.log("[spicetifyWrapper] All required webpack modules loaded"); chunks = Object.entries(require.m); cache = Object.keys(require.m).map((id) => require(id)); + + // Fire platformLoaded event there because of the sleep functions before + Spicetify.Events.platformLoaded.fire(); }); const modules = cache From 975d66d8bdd9e17f388def8e1fc362096e31bed2 Mon Sep 17 00:00:00 2001 From: ririxi Date: Tue, 27 Aug 2024 20:02:53 +0200 Subject: [PATCH 035/146] fix(wrapper): ignore chunks in strings --- jsHelper/spicetifyWrapper.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index eac1e93875..0c6b1c7b2f 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -472,8 +472,8 @@ window.Spicetify = { const text = await res.text(); const src = script.src.split("/").pop(); console.log(`[spicetifyWrapper] Waiting for ${src}`); - for (const pack of text.match(/(?:,|{)(\d+): ?\(.,.,./g).map((str) => str.slice(0, -7).slice(1))) { - // console.debug(`[spicetifyWrapper] Waiting for ${pack} of ${src}`); + for (const pack of text.match(/(? str.slice(0, -7).slice(1))) { + //console.debug(`[spicetifyWrapper] Waiting for ${pack} of ${src}`); while (!require.m || !Object.keys(require.m).includes(pack)) { await new Promise((r) => setTimeout(r, 100)); } From d5dfb110c3626c9c2b481f2d5782b4ce667adc0c Mon Sep 17 00:00:00 2001 From: ririxi Date: Tue, 27 Aug 2024 20:57:15 +0200 Subject: [PATCH 036/146] fix(wrapper): sanitize text content to remove all strings --- jsHelper/spicetifyWrapper.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 0c6b1c7b2f..5d5514d4ec 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -465,14 +465,17 @@ window.Spicetify = { // Filter out non-webpack scripts .filter((script) => ["extensions", "spicetify", "helper", "theme"].every((str) => !script.src?.includes(str))); + //console.time("sanitize"); await Promise.all( scripts.map(async (script) => { try { const res = await fetch(script.src); const text = await res.text(); + // remove every string from the content + const sanitizedText = text.replace(/(["'`])(?:\\.|[^\\\1])*?\1/g, ""); const src = script.src.split("/").pop(); console.log(`[spicetifyWrapper] Waiting for ${src}`); - for (const pack of text.match(/(? str.slice(0, -7).slice(1))) { + for (const pack of sanitizedText.match(/(? str.slice(0, -7).slice(1))) { //console.debug(`[spicetifyWrapper] Waiting for ${pack} of ${src}`); while (!require.m || !Object.keys(require.m).includes(pack)) { await new Promise((r) => setTimeout(r, 100)); @@ -485,6 +488,7 @@ window.Spicetify = { }) ).then(() => { console.log("[spicetifyWrapper] All required webpack modules loaded"); + //console.timeEnd("sanitize"); chunks = Object.entries(require.m); cache = Object.keys(require.m).map((id) => require(id)); From 8eaa83e27766791e2e114941e4fbd807fea19e02 Mon Sep 17 00:00:00 2001 From: machinemessiah Date: Fri, 30 Aug 2024 19:19:36 -0400 Subject: [PATCH 037/146] fix(css-map): just fixing a typo (#3152) --- css-map.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css-map.json b/css-map.json index d0030ebc0e..ffbac34e04 100644 --- a/css-map.json +++ b/css-map.json @@ -1232,7 +1232,7 @@ "Nd2dSpwo9xYae8YuQIkb": "search-searchCategory-carouselButtonVisible", "ZWI7JsjzJaR_G8Hy4W6J": "search-searchCategory-categoryGridItem", "UnwG2v9ISmcUhnjKj22Y": "search-searchCategory-categoryGridItem", - "KjPUGV8uMbl_0bvk9ePv": "search-searchCategory-catergoryGrid", + "KjPUGV8uMbl_0bvk9ePv": "search-searchCategory-categoryGrid", "e179_Eg8r7Ub6yjjxctr": "search-searchCategory-container", "XAwhJzXeTM5Iv2jLMCEj": "search-searchCategory-container", "bMurPtRDRv5LuN78MTVG": "search-searchCategory-contentArea", From ca0ab6b7d943a259ee792709cde46b14cd863955 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:30:07 +0100 Subject: [PATCH 038/146] chore(deps): bump golang.org/x/net from 0.28.0 to 0.29.0 (#3156) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 638ee113a9..28bdb49057 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.13 - golang.org/x/net v0.28.0 + golang.org/x/net v0.29.0 ) require ( github.com/mattn/go-isatty v0.0.16 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/sys v0.23.0 // indirect + golang.org/x/sys v0.25.0 // indirect ) diff --git a/go.sum b/go.sum index ec34b67a62..7cf903b3b1 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f005f8a3701c35f1f6fe51bd44ce63f647f5c188 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 20:08:16 +0200 Subject: [PATCH 039/146] revert: check for spotify.exe existence instead of app folder --- src/cmd/cmd.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/cmd/cmd.go b/src/cmd/cmd.go index 7256806f76..9e5b4b3ded 100644 --- a/src/cmd/cmd.go +++ b/src/cmd/cmd.go @@ -59,13 +59,8 @@ func InitPaths() { spotifyPath = utils.ReplaceEnvVarsInString(spotifyPath) prefsPath = utils.ReplaceEnvVarsInString(prefsPath) - testPath := spotifyPath - if runtime.GOOS == "windows" { - testPath = filepath.Join(spotifyPath, "Spotify.exe") - } - - if _, err := os.Stat(testPath); err != nil { + if _, err := os.Stat(spotifyPath); err != nil { actualSpotifyPath := utils.FindAppPath() if len(actualSpotifyPath) == 0 { From 3e5dc5b9b6cdfe7c58eb4c7441bb15f718f44838 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 20:12:49 +0200 Subject: [PATCH 040/146] feat(cmd): add `block-updates` cmd to block spotify updates Co-authored-by: Delusoire --- spicetify.go | 19 ++++++++++++++- src/cmd/block-updates.go | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/cmd/block-updates.go diff --git a/spicetify.go b/spicetify.go index d42a5009a5..90860c92b2 100644 --- a/spicetify.go +++ b/spicetify.go @@ -150,6 +150,22 @@ func main() { } return + case "block-updates": + commands = commands[1:] + if len(commands) == 0 { + utils.PrintError("No parameter given. It has to be \"on\" or \"off\".") + return + } + param := commands[0] + if param == "on" { + cmd.BlockSpotifyUpdates(true) + } else if param == "off" { + cmd.BlockSpotifyUpdates(false) + } else { + utils.PrintError("Invalid parameter. It has to be \"on\" or \"off\".") + } + return + case "path": commands = commands[1:] path, err := (func() (string, error) { @@ -355,6 +371,8 @@ watch Enter watch mode. restart Restart Spotify client. ` + utils.Bold("NON-CHAINABLE COMMANDS") + ` +block-updates Blocks Spotify updates. Patches spotify executable. Accepts "on" or "off" as parameter. + path Prints path of Spotify's executable, userdata, and more. 1. Print executable path: spicetify path @@ -381,7 +399,6 @@ path Prints path of Spotify's executable, userdata, and more. "-c" (for config.ini) options: N/A. - config 1. Print all config fields and values: spicetify config diff --git a/src/cmd/block-updates.go b/src/cmd/block-updates.go new file mode 100644 index 0000000000..061143810c --- /dev/null +++ b/src/cmd/block-updates.go @@ -0,0 +1,51 @@ +package cmd + +import ( + "bytes" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/spicetify/cli/src/utils" +) + +// Block spotify updates. Taken from https://github.com/Delusoire/bespoke-cli/blob/main/cmd/spotify/update.go +func BlockSpotifyUpdates(enabled bool) { + spotifyExecPath := GetSpotifyPath() + switch runtime.GOOS { + case "windows": + spotifyExecPath = filepath.Join(spotifyExecPath, "Spotify.exe") + case "linux": + spotifyExecPath = filepath.Join(spotifyExecPath, "spotify") + case "darwin": + spotifyExecPath = filepath.Join(spotifyExecPath, "Spotify") + } + + file, err := os.OpenFile(spotifyExecPath, os.O_RDWR, 0644) + if err != nil { + utils.Fatal(err) + return + } + defer file.Close() + + buf := new(bytes.Buffer) + buf.ReadFrom(file) + content := buf.String() + + i := strings.Index(content, "desktop-update/") + if i == -1 { + utils.PrintError("Can't find update endpoint in executable") + return + } + var str, msg string + if enabled { + str = "v2/update" + msg = "Enabled" + } else { + str = "no/thanks" + msg = "Disabled" + } + file.WriteAt([]byte(str), int64(i+15)) + utils.PrintSuccess(msg + " Spotify updates!") +} From ca670fae1c853597f82c2280bca33d389eb8913f Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 20:43:00 +0200 Subject: [PATCH 041/146] feat(wrapper): set buttons heigh for 1.2.45 and older for Library X --- jsHelper/spicetifyWrapper.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 5d5514d4ec..50b9d8ffde 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -1019,6 +1019,21 @@ Spicetify.Events = (() => { Spicetify.Platform.UserAPI._product_state_service = productStateApi; })(); + (async function setButtonsHeight() { + while (!Spicetify.CosmosAsync) { + await new Promise((res) => setTimeout(res, 100)); + } + const expFeatures = JSON.parse(localStorage.getItem("spicetify-exp-features") || "{}"); + const isGlobalNavbar = expFeatures?.enableGlobalNavBar?.value; + + if (typeof isGlobalNavbar !== "undefined" && isGlobalNavbar === "control") { + await Spicetify.CosmosAsync.post("sp://messages/v1/container/control", { + type: "update_titlebar", + height: Spicetify.Platform.PlatformData.os_name === "windows" ? "40" : "42", + }); + } + })(); + setInterval(() => { if (playerState.cache?.isPaused === false) { const event = new Event("onprogress"); From 35e407d5a2555a38983bb06786cb0cc9a1fa2cba Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 21:13:59 +0200 Subject: [PATCH 042/146] feat: rename `block-updates` to `spotify-updates` --- spicetify.go | 12 ++++++------ src/cmd/block-updates.go | 16 +++++++++------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/spicetify.go b/spicetify.go index 90860c92b2..10cd793f5a 100644 --- a/spicetify.go +++ b/spicetify.go @@ -150,19 +150,19 @@ func main() { } return - case "block-updates": + case "spotify-updates": commands = commands[1:] if len(commands) == 0 { - utils.PrintError("No parameter given. It has to be \"on\" or \"off\".") + utils.PrintError("No parameter given. It has to be \"block\" or \"unblock\".") return } param := commands[0] - if param == "on" { + if param == "block" { cmd.BlockSpotifyUpdates(true) - } else if param == "off" { + } else if param == "unblock" { cmd.BlockSpotifyUpdates(false) } else { - utils.PrintError("Invalid parameter. It has to be \"on\" or \"off\".") + utils.PrintError("Invalid parameter. It has to be \"block\" or \"unblock\".") } return @@ -371,7 +371,7 @@ watch Enter watch mode. restart Restart Spotify client. ` + utils.Bold("NON-CHAINABLE COMMANDS") + ` -block-updates Blocks Spotify updates. Patches spotify executable. Accepts "on" or "off" as parameter. +spotify-updates Blocks Spotify updates. Patches spotify executable. Accepts "block" or "unblock" as parameter. path Prints path of Spotify's executable, userdata, and more. 1. Print executable path: diff --git a/src/cmd/block-updates.go b/src/cmd/block-updates.go index 061143810c..d680fe655f 100644 --- a/src/cmd/block-updates.go +++ b/src/cmd/block-updates.go @@ -11,13 +11,15 @@ import ( ) // Block spotify updates. Taken from https://github.com/Delusoire/bespoke-cli/blob/main/cmd/spotify/update.go -func BlockSpotifyUpdates(enabled bool) { +func BlockSpotifyUpdates(disabled bool) { + if runtime.GOOS == "linux" { + utils.PrintError("Auto-updates on linux should be disabled in package manager you installed spotify with.") + return + } spotifyExecPath := GetSpotifyPath() switch runtime.GOOS { case "windows": spotifyExecPath = filepath.Join(spotifyExecPath, "Spotify.exe") - case "linux": - spotifyExecPath = filepath.Join(spotifyExecPath, "spotify") case "darwin": spotifyExecPath = filepath.Join(spotifyExecPath, "Spotify") } @@ -39,12 +41,12 @@ func BlockSpotifyUpdates(enabled bool) { return } var str, msg string - if enabled { - str = "v2/update" - msg = "Enabled" - } else { + if disabled { str = "no/thanks" msg = "Disabled" + } else { + str = "v2/update" + msg = "Enabled" } file.WriteAt([]byte(str), int64(i+15)) utils.PrintSuccess(msg + " Spotify updates!") From 6e250c689ea312fc757385c9cbccd7bf5b2df81a Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 21:24:08 +0200 Subject: [PATCH 043/146] feat: remove support for pre-Library X UI --- jsHelper/spicetifyWrapper.js | 29 ++--------------------------- src/apply/apply.go | 14 -------------- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 50b9d8ffde..b0d3687e37 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -1853,7 +1853,7 @@ Spicetify.ContextMenu = (() => { let navLinkFactoryCtx = null; let refreshNavLinks = null; -Spicetify._renderNavLinks = (list, isTouchScreenUi, isPreLibX = false) => { +Spicetify._renderNavLinks = (list, isTouchScreenUi) => { const [refreshCount, refresh] = Spicetify.React.useReducer((x) => x + 1, 0); refreshNavLinks = refresh; @@ -1866,7 +1866,7 @@ Spicetify._renderNavLinks = (list, isTouchScreenUi, isPreLibX = false) => { ) return; - const navLinkFactory = isTouchScreenUi ? NavLinkGlobal : isPreLibX ? NavLinkSidebarLegacy : NavLinkSidebar; + const navLinkFactory = isTouchScreenUi ? NavLinkGlobal : NavLinkSidebar; if (!navLinkFactoryCtx) navLinkFactoryCtx = Spicetify.React.createContext(null); const registered = []; @@ -1951,31 +1951,6 @@ const NavLink = ({ appProper, appRoutePath, icon, activeIcon }) => { return NavLinkFactory && Spicetify.React.createElement(NavLinkFactory, { appProper, appRoutePath, createIcon, isActive }, null); }; -const NavLinkSidebarLegacy = ({ appProper, appRoutePath, createIcon, isActive }) => { - return Spicetify.React.createElement( - "li", - { className: "main-navBar-navBarItem InvalidDropTarget" }, - Spicetify.React.createElement( - Spicetify.ReactComponent.TooltipWrapper, - { label: appProper, placement: "right" }, - Spicetify.React.createElement( - Spicetify.ReactComponent.Navigation, - { - to: appRoutePath, - referrer: "other", - className: Spicetify.classnames("link-subtle", "main-navBar-navBarLink", { - "main-navBar-navBarLinkActive active": isActive, - }), - onClick: () => undefined, - "aria-label": appProper, - }, - createIcon(), - Spicetify.React.createElement(Spicetify.ReactComponent.TextComponent, { variant: "mestoBold" }, appProper) - ) - ) - ); -}; - const NavLinkSidebar = ({ appProper, appRoutePath, createIcon, isActive }) => { const isSidebarCollapsed = Spicetify.Platform.LocalStorageAPI.getItem("ylx-sidebar-state") === 1; diff --git a/src/apply/apply.go b/src/apply/apply.go index 47f13465db..5be5a4c827 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -335,20 +335,6 @@ func insertNavLink(str string, appNameArray string) string { 1) } - // pre-Library X - sidebarItemMatch := utils.SeekToCloseParen( - str, - `\("li",\{className:[\w$\.]+\}?,(?:children:)?[\w$\.,()]+\(\w+,\{uri:"spotify:user:@:collection",to:"/collection"`, - '(', ')') - - if sidebarItemMatch != "" { - str = strings.Replace( - str, - sidebarItemMatch, - fmt.Sprintf("%s,Spicetify._renderNavLinks([%s], false, true)", sidebarItemMatch, appNameArray), - 1) - } - // Global Navbar utils.ReplaceOnce(&str, `(,[a-zA-Z_\$][\w\$]*===(?:[a-zA-Z_\$][\w\$]*\.){2}HOME_NEXT_TO_NAVIGATION&&.+?)\]`, func(submatches ...string) string { return fmt.Sprintf("%s,Spicetify._renderNavLinks([%s], true)]", submatches[1], appNameArray) From e108fac0175542e495998d233c4df47307f06af3 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 21:26:30 +0200 Subject: [PATCH 044/146] feat(sidebarConfig): remove support for pre-Library X --- jsHelper/sidebarConfig.js | 73 ++------------------------------------- 1 file changed, 2 insertions(+), 71 deletions(-) diff --git a/jsHelper/sidebarConfig.js b/jsHelper/sidebarConfig.js index 3c6e89255f..c0b97e8308 100644 --- a/jsHelper/sidebarConfig.js +++ b/jsHelper/sidebarConfig.js @@ -187,82 +187,13 @@ color: var(--spice-button-disabled); function initConfig() { const libraryX = document.querySelector(".main-yourLibraryX-navItems"); - const libraryLegacy = document.querySelector(".main-navBar-entryPoints"); - if (!libraryLegacy && !libraryX) { + if (!libraryX) { setTimeout(initConfig, 300); return; } - if (libraryX) InitSidebarXConfig(); - else InitSidebarConfig(); - } - - function InitSidebarConfig() { - // STICKY container - const legacyAppItems = document.querySelector(".main-navBar-entryPoints"); - const rootList = document.querySelector(".main-rootlist-rootlist"); - const playlistItems = document.querySelector(".main-navBar-navBar .os-content"); - - if (!legacyAppItems || !playlistItems || !rootList) { - setTimeout(InitSidebarConfig, 300); - return; - } - - appItems = legacyAppItems; - buttons = []; - ordered = []; - isYLX = false; - - appItems.id = "spicetify-sticky-list"; - // SHOW container - list = document.createElement("ul"); - list.id = "spicetify-show-list"; - // HIDDEN container - hiddenList = document.createElement("ul"); - hiddenList.id = "spicetify-hidden-list"; - hiddenList.classList.add("hidden-visually"); - const playlistList = playlistItems.querySelector("ul"); - playlistList.id = "spicetify-playlist-list"; - playlistItems.prepend(list, hiddenList); - - for (const ele of appItems.children) { - ele.dataset.id = ele.querySelector("a")?.pathname ?? "/add"; - buttons.push(ele); - } - - for (const ele of rootList.querySelectorAll("div.GlueDropTarget")) { - if (ele.classList.contains("GlueDropTarget--playlists")) break; - const link = ele.querySelector("a"); - if (!link) { - ele.dataset.id = "/add"; - } else { - ele.dataset.id = link.pathname; - } - ele.classList.add("personal-library"); - new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === "attributes" && mutation.attributeName === "class") { - if (!mutation.target.classList.contains("personal-library")) { - mutation.target.classList.add("personal-library"); - } - } - } - }).observe(ele, { attributes: true, attributeFilter: ["class"] }); - - buttons.push(ele); - } - - let storage = []; - try { - storage = JSON.parse(localStorage.getItem("spicetify-sidebar-config")); - if (!Array.isArray(storage)) throw ""; - } catch { - storage = buttons.map((el) => [el.dataset.id, STICKY]); - } - - arrangeItems(storage); - appendItems(); + InitSidebarXConfig(); } function InitSidebarXConfig() { From 9d02e7f9e11a64d75096c200fc2d1229b238a0bc Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 21:32:16 +0200 Subject: [PATCH 045/146] fix(sidebarConfig): remove remaining legacy stuff --- jsHelper/sidebarConfig.js | 42 ++++++++++----------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/jsHelper/sidebarConfig.js b/jsHelper/sidebarConfig.js index c0b97e8308..25f97dadae 100644 --- a/jsHelper/sidebarConfig.js +++ b/jsHelper/sidebarConfig.js @@ -10,7 +10,6 @@ let list; let hiddenList; - let isYLX; let YLXSidebarState = 0; // Store sidebar buttons @@ -56,8 +55,7 @@ function writeStorage() { const array = ordered.map((a) => [a[0].dataset.id, a[1]]); - if (isYLX) return localStorage.setItem("spicetify-sidebar-config:ylx", JSON.stringify(array)); - return localStorage.setItem("spicetify-sidebar-config", JSON.stringify(array)); + return localStorage.setItem("spicetify-sidebar-config:ylx", JSON.stringify(array)); } const container = document.createElement("div"); @@ -114,10 +112,8 @@ color: var(--spice-button-disabled); appendItems(); } - if (isYLX) { - YLXSidebarState = Spicetify.Platform.LocalStorageAPI.getItem("ylx-sidebar-state"); - if (YLXSidebarState === 1) document.querySelector(".main-yourLibraryX-collapseButton > button")?.click(); - } + YLXSidebarState = Spicetify.Platform.LocalStorageAPI.getItem("ylx-sidebar-state"); + if (YLXSidebarState === 1) document.querySelector(".main-yourLibraryX-collapseButton > button")?.click(); document.documentElement.style.setProperty("--nav-bar-width", "280px"); @@ -155,16 +151,14 @@ color: var(--spice-button-disabled); for (const a of ordered) { a[0].onmouseover = undefined; } - if (isYLX) { - if (YLXSidebarState === 1) document.querySelector(".main-yourLibraryX-collapseButton > button")?.click(); - else - document.documentElement.style.setProperty( - "--nav-bar-width", - `${Spicetify.Platform.LocalStorageAPI.getItem( - YLXSidebarState === 2 ? "ylx-expanded-state-nav-bar-width" : "ylx-default-state-nav-bar-width" - )}px` - ); - } else document.documentElement.style.setProperty("--nav-bar-width", `${Spicetify.Platform.LocalStorageAPI.getItem("nav-bar-width")}px`); + if (YLXSidebarState === 1) document.querySelector(".main-yourLibraryX-collapseButton > button")?.click(); + else + document.documentElement.style.setProperty( + "--nav-bar-width", + `${Spicetify.Platform.LocalStorageAPI.getItem( + YLXSidebarState === 2 ? "ylx-expanded-state-nav-bar-width" : "ylx-default-state-nav-bar-width" + )}px` + ); writeStorage(); } @@ -209,7 +203,6 @@ color: var(--spice-button-disabled); appItems = YLXAppItems; buttons = []; ordered = []; - isYLX = true; appItems.id = "spicetify-sticky-list"; // SHOW container @@ -251,19 +244,6 @@ color: var(--spice-button-disabled); initConfig(); - // Rearrange sidebar when dynamically switching in Experimental Features - new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.attributeName === "class") { - if (mutation.target.classList.contains("hasYLXSidebar") || !!mutation.target.querySelector(".main-yourLibraryX-entryPoints")) { - InitSidebarXConfig(); - } else { - InitSidebarConfig(); - } - } - } - }).observe(sidebar, { childList: true, attributes: true, attributeFilter: ["class"] }); - const customButtonStyle = document.createElement("style"); customButtonStyle.innerHTML = ` div.GlueDropTarget.personal-library { From 8862426cb6b8f3e6d5122a9733a42159a0e6c117 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 21:59:22 +0200 Subject: [PATCH 046/146] fix(new-releases): use `PlatformAPI` to fetch podcasts Fixes #3149 --- CustomApps/new-releases/index.js | 33 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/CustomApps/new-releases/index.js b/CustomApps/new-releases/index.js index ab2817dcf4..c312be7b6b 100644 --- a/CustomApps/new-releases/index.js +++ b/CustomApps/new-releases/index.js @@ -237,6 +237,13 @@ class Grid extends react.Component { } render() { + const expFeatures = JSON.parse(localStorage.getItem("spicetify-exp-features") || "{}"); + const isGlobalNav = expFeatures?.enableGlobalNavBar?.value !== "control"; + const version = Spicetify.Platform.version.split(".").map((i) => Number.parseInt(i)); + + const tabBarMargin = { + marginTop: isGlobalNav || (version[0] === 1 && version[1] === 2 && version[2] >= 45) ? "60px" : "0px", + }; return react.createElement( "section", { @@ -246,6 +253,7 @@ class Grid extends react.Component { "div", { className: "new-releases-header", + style: tabBarMargin, }, react.createElement("h1", null, Spicetify.Locale.get("new_releases")), react.createElement( @@ -318,14 +326,12 @@ async function getArtistEverything(artist) { } async function getPodcastList() { - const body = await CosmosAsync.get("sp://core-collection/unstable/@/list/shows/all?responseFormat=protobufJson"); - return body.item ?? []; + const body = await Spicetify.Platform.LibraryAPI.getShows({ limit: 50000 }); + return body.items ?? []; } async function getPodcastRelease(uri) { - const body = await CosmosAsync.get(`sp://core-show/v1/shows/${uri}?responseFormat=protobufJson`, { - policy: { list: { link: true, name: true, publishDate: true } }, - }); + const body = await Spicetify.Platform.ShowAPI.getContents(uri, { limit: 50000 }); return body.items; } @@ -372,28 +378,25 @@ async function fetchTracks() { async function fetchPodcasts() { const items = []; const itemTypeStr = Spicetify.Locale.get("card.tag.episode"); - for (const obj of await getPodcastList()) { - const podcast = obj.showMetadata; - const id = podcast.link.replace("spotify:show:", ""); - - const tracks = await getPodcastRelease(id); + for (const podcast of await getPodcastList()) { + const tracks = await getPodcastRelease(podcast.uri); if (!tracks) continue; for (const track of tracks) { - const time = new Date(track.episodeMetadata.publishDate * 1000); + const time = new Date(track.releaseDate.isoString); if (today - time.getTime() > limitInMs) { break; } items.push({ - uri: track.episodeMetadata.link, - title: track.episodeMetadata.name, + uri: track.uri, + title: track.name, artist: { name: podcast.name, - uri: podcast.link, + uri: podcast.uri, }, - imageURL: podcast.covers.standardLink, + imageURL: track.coverArt.reduce((prev, curr) => (prev.width > curr.width ? prev : curr)).url, time, type: itemTypeStr, }); From eb8e1f2ca87e7a621b497123efa7a0f24d552fe6 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 22:13:20 +0200 Subject: [PATCH 047/146] fix(reddit/tabBar): showing context menu in `More` Fixes #3136 --- CustomApps/reddit/OptionsMenu.js | 2 +- CustomApps/reddit/index.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CustomApps/reddit/OptionsMenu.js b/CustomApps/reddit/OptionsMenu.js index 5f4ad345be..7e579393b7 100644 --- a/CustomApps/reddit/OptionsMenu.js +++ b/CustomApps/reddit/OptionsMenu.js @@ -55,7 +55,7 @@ const OptionsMenu = react.memo(({ options, onSelect, selected, defaultValue, bol ), trigger: "click", action: "toggle", - renderInline: true, + renderInline: false, }, react.createElement( "button", diff --git a/CustomApps/reddit/index.js b/CustomApps/reddit/index.js index b5e0d7ef9b..1578c9c1f2 100644 --- a/CustomApps/reddit/index.js +++ b/CustomApps/reddit/index.js @@ -237,6 +237,13 @@ class Grid extends react.Component { } render() { + const expFeatures = JSON.parse(localStorage.getItem("spicetify-exp-features") || "{}"); + const isGlobalNav = expFeatures?.enableGlobalNavBar?.value !== "control"; + const version = Spicetify.Platform.version.split(".").map((i) => Number.parseInt(i)); + + const tabBarMargin = { + marginTop: isGlobalNav || (version[0] === 1 && version[1] === 2 && version[2] >= 45) ? "60px" : "0px", + }; return react.createElement( "section", { @@ -246,6 +253,7 @@ class Grid extends react.Component { "div", { className: "reddit-header", + style: tabBarMargin, }, react.createElement("h1", null, this.props.title), react.createElement(SortBox, { From 6a1363aaa63f7aa736f021f496dd46618e1f02ce Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 7 Sep 2024 22:20:48 +0200 Subject: [PATCH 048/146] fix(InitPaths): check for `Apps` folder --- src/cmd/cmd.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cmd/cmd.go b/src/cmd/cmd.go index 9e5b4b3ded..377d6a858d 100644 --- a/src/cmd/cmd.go +++ b/src/cmd/cmd.go @@ -59,8 +59,9 @@ func InitPaths() { spotifyPath = utils.ReplaceEnvVarsInString(spotifyPath) prefsPath = utils.ReplaceEnvVarsInString(prefsPath) + testPath := filepath.Join(spotifyPath, "Apps") - if _, err := os.Stat(spotifyPath); err != nil { + if _, err := os.Stat(testPath); err != nil { actualSpotifyPath := utils.FindAppPath() if len(actualSpotifyPath) == 0 { From d20754971bd1e108f28bb05e0e67759f3675503c Mon Sep 17 00:00:00 2001 From: ririxi Date: Sun, 8 Sep 2024 01:00:28 +0200 Subject: [PATCH 049/146] chore(wrapper): change check for `os_name` to `osx` --- jsHelper/spicetifyWrapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index b0d3687e37..2ae81cbeb1 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -1029,7 +1029,7 @@ Spicetify.Events = (() => { if (typeof isGlobalNavbar !== "undefined" && isGlobalNavbar === "control") { await Spicetify.CosmosAsync.post("sp://messages/v1/container/control", { type: "update_titlebar", - height: Spicetify.Platform.PlatformData.os_name === "windows" ? "40" : "42", + height: Spicetify.Platform.PlatformData.os_name === "osx" ? "42" : "40", }); } })(); From aa4ac602360f06836ac69e7bc995a9f9ae33159d Mon Sep 17 00:00:00 2001 From: ririxi Date: Sun, 8 Sep 2024 23:01:39 +0200 Subject: [PATCH 050/146] fix(preprocess/contextmenu): adapt regex for `1.2.46` --- src/preprocess/preprocess.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index fe43e3dcdf..3887b40582 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -561,25 +561,22 @@ func exposeAPIs_main(input string) string { return fmt.Sprintf("%s[Spicetify.ContextMenuV2.renderItems(),%s].flat()", submatches[1], submatches[2]) }) - croppedInput := utils.FindFirstMatch(input, `"context-menu".*value:"contextmenu"`)[0] + croppedInput := utils.FindFirstMatch(input, `\({menu:[^,]+,.*?triggerRef:[^,]+,[^}]*}\).*value:"contextmenu"`)[0] + fmt.Println(croppedInput) - react := utils.FindFirstMatch(croppedInput, `([\w_$]+)\.useRef`)[1] - var menu string - var trigger string - var target string - - menuCandidates := utils.FindMatch(croppedInput, `menu:([\w_$]+)`) - if len(menuCandidates) == 0 { - // v1.2.13 fix - menu = utils.FindFirstMatch(croppedInput, `([\w_$]+)=[\w_$]+\.menu,`)[1] - trigger = utils.FindFirstMatch(croppedInput, `([\w_$]+)=[\w_$]+\.trigger,`)[1] - target = utils.FindFirstMatch(croppedInput, `([\w_$]+)=[\w_$]+\.triggerRef,`)[1] + reactRef := utils.FindFirstMatch(croppedInput, `([\w_$]+)\.useRef`) + var react, menu, trigger, target string + if len(reactRef) == 1 { + react = reactRef[0] } else { - menu = menuCandidates[0][1] - trigger = utils.FindFirstMatch(croppedInput, `trigger:([\w_$]+)`)[1] - target = utils.FindFirstMatch(croppedInput, `triggerRef:([\w_$]+)`)[1] + react = reactRef[1] } + menuCandidates := utils.FindMatch(croppedInput, `menu:([\w_$]+)`) + menu = menuCandidates[0][1] + trigger = utils.FindFirstMatch(croppedInput, `trigger:([\w_$]+)`)[1] + target = utils.FindFirstMatch(croppedInput, `triggerRef:([\w_$]+)`)[1] + 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]) }) From f1c31a8e12180886dc0b07e4dc0cf55d9a70a06e Mon Sep 17 00:00:00 2001 From: ririxi Date: Sun, 8 Sep 2024 23:02:14 +0200 Subject: [PATCH 051/146] chore(preprocess): remove console log --- src/preprocess/preprocess.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 3887b40582..4b2f2f2d0e 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -562,7 +562,6 @@ func exposeAPIs_main(input string) string { }) croppedInput := utils.FindFirstMatch(input, `\({menu:[^,]+,.*?triggerRef:[^,]+,[^}]*}\).*value:"contextmenu"`)[0] - fmt.Println(croppedInput) reactRef := utils.FindFirstMatch(croppedInput, `([\w_$]+)\.useRef`) var react, menu, trigger, target string From ffa71c350c69b114edf09eca82280a6267151ab9 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sun, 8 Sep 2024 23:19:34 +0200 Subject: [PATCH 052/146] fix(apply): move expFeatures regex to the `insertExpFeatures` --- src/apply/apply.go | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/apply/apply.go b/src/apply/apply.go index 5be5a4c827..5333a67869 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -29,6 +29,8 @@ func AdditionalOptions(appsFolderPath string, flags Flag) { filesToModified := map[string]func(path string, flags Flag){ filepath.Join(appsFolderPath, "xpui", "index.html"): htmlMod, filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertCustomApp, + filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertExpFeatures, + filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertSidebarConfig, filepath.Join(appsFolderPath, "xpui", "vendor~xpui.js"): insertExpFeatures, filepath.Join(appsFolderPath, "xpui", "home-v2.js"): insertHomeConfig, filepath.Join(appsFolderPath, "xpui", "xpui-desktop-modals.js"): insertVersionInfo, @@ -298,24 +300,6 @@ func insertCustomApp(jsPath string, flags Flag) { return fmt.Sprintf("%s%s", submatches[0], cssEnableMap) }) - if flags.SidebarConfig { - utils.ReplaceOnce( - &content, - `return null!=\w+&&\w+\.totalLength(\?\w+\(\)\.createElement\(\w+,\{contextUri:)(\w+)\.uri`, - func(submatches ...string) string { - return fmt.Sprintf(`return true%s%s?.uri||""`, submatches[1], submatches[2]) - }) - } - - if flags.ExpFeatures { - utils.ReplaceOnce( - &content, - `(([\w$.]+\.fromJSON)\(\w+\)+;)(return ?[\w{}().,]+[\w$]+\.Provider,)(\{value:\{localConfiguration)`, - func(submatches ...string) string { - return fmt.Sprintf("%sSpicetify.createInternalMap=%s;%sSpicetify.RemoteConfigResolver=%s", submatches[1], submatches[2], submatches[3], submatches[4]) - }) - } - return content }) } @@ -355,6 +339,7 @@ func insertHomeConfig(jsPath string, flags Flag) { func(submatches ...string) string { return fmt.Sprintf("%sSpicetifyHomeConfig.arrange(%s)%s", submatches[1], submatches[2], submatches[3]) }) + return content }) } @@ -369,6 +354,23 @@ func getAssetsPath(themeFolder string) string { return dir } +func insertSidebarConfig(jsPath string, flags Flag) { + if !flags.SidebarConfig { + return + } + + utils.ModifyFile(jsPath, func(content string) string { + utils.ReplaceOnce( + &content, + `return null!=\w+&&\w+\.totalLength(\?\w+\(\)\.createElement\(\w+,\{contextUri:)(\w+)\.uri`, + func(submatches ...string) string { + return fmt.Sprintf(`return true%s%s?.uri||""`, submatches[1], submatches[2]) + }) + + return content + }) +} + func insertExpFeatures(jsPath string, flags Flag) { if !flags.ExpFeatures { return @@ -381,6 +383,13 @@ func insertExpFeatures(jsPath string, flags Flag) { func(submatches ...string) string { return fmt.Sprintf("%s%s=Spicetify.expFeatureOverride(%s);%s", submatches[1], submatches[2], submatches[2], submatches[3]) }) + + utils.ReplaceOnce( + &content, + `(([\w$.]+\.fromJSON)\(\w+\)+;)(return ?[\w{}().,]+[\w$]+\.Provider,)(\{value:\{localConfiguration)`, + func(submatches ...string) string { + return fmt.Sprintf("%sSpicetify.createInternalMap=%s;%sSpicetify.RemoteConfigResolver=%s", submatches[1], submatches[2], submatches[3], submatches[4]) + }) return content }) } From 0bbbec5eda49390e088238fafd53442bbc663e41 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sun, 8 Sep 2024 23:27:51 +0200 Subject: [PATCH 053/146] feat(sidebarConfig): disable functionality when Global navbar is used --- jsHelper/sidebarConfig.js | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/jsHelper/sidebarConfig.js b/jsHelper/sidebarConfig.js index 25f97dadae..a64eb7b2ca 100644 --- a/jsHelper/sidebarConfig.js +++ b/jsHelper/sidebarConfig.js @@ -1,6 +1,7 @@ (function SidebarConfig() { const sidebar = document.querySelector(".Root__nav-bar"); if (!sidebar) return setTimeout(SidebarConfig, 100); + let isGlobalNavbar = false; // Status enum const HIDDEN = 0; const SHOW = 1; @@ -164,19 +165,29 @@ color: var(--spice-button-disabled); (async () => { await new Promise((res) => Spicetify.Events.webpackLoaded.on(res)); - new Spicetify.Menu.Item( - "Sidebar config", - false, - (self) => { - self.setState(!self.isEnabled); - if (self.isEnabled) { - injectInteraction(); - } else { - removeInteraction(); - } - }, - `` - ).register(); + if (document.querySelector(".Root__globalNav")) { + Spicetify.showNotification( + "Sidebar config is not supported on global navbar. Please disable it with `spicetify config sidebar_config 0` command.", + false, + 7000 + ); + isGlobalNavbar = true; + } + if (!isGlobalNavbar) { + new Spicetify.Menu.Item( + "Sidebar config", + false, + (self) => { + self.setState(!self.isEnabled); + if (self.isEnabled) { + injectInteraction(); + } else { + removeInteraction(); + } + }, + `` + ).register(); + } })(); function initConfig() { From 2acdc4ad4ded64bef9789474ed2cae363a1244e4 Mon Sep 17 00:00:00 2001 From: Thomas Fitzpatrick <22730962+ohitstom@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:29:19 +0100 Subject: [PATCH 054/146] fix(home-config): support up to 1.2.45 (#3161) Co-authored-by: em --- css-map.json | 2 ++ jsHelper/homeConfig.js | 12 +++++++----- src/apply/apply.go | 9 +++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/css-map.json b/css-map.json index ffbac34e04..9623156d3b 100644 --- a/css-map.json +++ b/css-map.json @@ -510,6 +510,8 @@ "liYe8rZ0FEQBy8j8XGJH": "main-heroCard-isPlaying", "pgwIORyBdf4nbb4G5_Jx": "main-heroCard-PlayButtonContainer", "I3EivnXTjYMpSbPUiYEg": "main-home-content", + "ZLSAuA1tn0bSdQRwhhj6": "main-home-content", + "Le0q6vXGEvilJEjOqgF9": "main-home-content", "zbU90jX5VWUhVlpUda7B": "main-home-filterChipsContainer", "cj6vRk3nFAi80HSVqX91": "main-home-filterChipsContainer", "rX_OmqCngvY5ZCoYBZgb": "main-home-filterChipsSection", diff --git a/jsHelper/homeConfig.js b/jsHelper/homeConfig.js index f6e1cd73b6..a9c5b3b94e 100644 --- a/jsHelper/homeConfig.js +++ b/jsHelper/homeConfig.js @@ -21,19 +21,21 @@ SpicetifyHomeConfig = {}; const stickSections = []; const lowSections = []; for (const uri of stickList) { - const index = sections.findIndex((a) => a?.uri === uri); + const index = sections.findIndex((a) => a?.uri === uri || a?.item.uri === uri); if (index !== -1) { const item = sections[index]; - statusDic[item.uri] = STICKY; + const uri = item.item.uri || item.uri; + statusDic[uri] = STICKY; stickSections.push(item); sections[index] = undefined; } } for (const uri of lowList) { - const index = sections.findIndex((a) => a?.uri === uri); + const index = sections.findIndex((a) => a?.uri === uri || a?.item.uri === uri); if (index !== -1) { const item = sections[index]; - statusDic[item.uri] = LOWERED; + const uri = item.item.uri || item.uri; + statusDic[uri] = LOWERED; lowSections.push(item); sections[index] = undefined; } @@ -90,7 +92,7 @@ SpicetifyHomeConfig = {}; const main = document.querySelector(".main-home-content"); elem = [...main.querySelectorAll("section")]; for (const [index, item] of elem.entries()) { - item.dataset.uri = list[index].uri; + item.dataset.uri = list[index].uri ?? list[index].item.uri; } function appendItems() { diff --git a/src/apply/apply.go b/src/apply/apply.go index 5333a67869..66e50e7108 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -31,6 +31,7 @@ func AdditionalOptions(appsFolderPath string, flags Flag) { filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertCustomApp, filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertExpFeatures, filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertSidebarConfig, + filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertHomeConfig, filepath.Join(appsFolderPath, "xpui", "vendor~xpui.js"): insertExpFeatures, filepath.Join(appsFolderPath, "xpui", "home-v2.js"): insertHomeConfig, filepath.Join(appsFolderPath, "xpui", "xpui-desktop-modals.js"): insertVersionInfo, @@ -340,6 +341,14 @@ func insertHomeConfig(jsPath string, flags Flag) { return fmt.Sprintf("%sSpicetifyHomeConfig.arrange(%s)%s", submatches[1], submatches[2], submatches[3]) }) + // >= 1.2.45 + utils.ReplaceOnce( + &content, + `(&&"HomeShortsSectionData".*\],)([a-zA-Z])(\}\)\()`, + func(submatches ...string) string { + return fmt.Sprintf("%sSpicetifyHomeConfig.arrange(%s)%s", submatches[1], submatches[2], submatches[3]) + }) + return content }) } From d1fc0895004ecff342b483c207b0f348c848f541 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sun, 8 Sep 2024 23:40:59 +0200 Subject: [PATCH 055/146] feat(apply): add ability to call multiple functions to one file --- src/apply/apply.go | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/apply/apply.go b/src/apply/apply.go index 66e50e7108..be346eb5c0 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -26,23 +26,35 @@ type Flag struct { // AdditionalOptions . func AdditionalOptions(appsFolderPath string, flags Flag) { - filesToModified := map[string]func(path string, flags Flag){ - filepath.Join(appsFolderPath, "xpui", "index.html"): htmlMod, - filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertCustomApp, - filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertExpFeatures, - filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertSidebarConfig, - filepath.Join(appsFolderPath, "xpui", "xpui.js"): insertHomeConfig, - filepath.Join(appsFolderPath, "xpui", "vendor~xpui.js"): insertExpFeatures, - filepath.Join(appsFolderPath, "xpui", "home-v2.js"): insertHomeConfig, - filepath.Join(appsFolderPath, "xpui", "xpui-desktop-modals.js"): insertVersionInfo, + filesToModified := map[string][]func(path string, flags Flag){ + filepath.Join(appsFolderPath, "xpui", "index.html"): { + htmlMod, + }, + filepath.Join(appsFolderPath, "xpui", "xpui.js"): { + insertCustomApp, + insertExpFeatures, + insertSidebarConfig, + insertHomeConfig, + }, + filepath.Join(appsFolderPath, "xpui", "vendor~xpui.js"): { + insertExpFeatures, + }, + filepath.Join(appsFolderPath, "xpui", "home-v2.js"): { + insertHomeConfig, + }, + filepath.Join(appsFolderPath, "xpui", "xpui-desktop-modals.js"): { + insertVersionInfo, + }, } - for file, call := range filesToModified { + for file, calls := range filesToModified { if _, err := os.Stat(file); os.IsNotExist(err) { continue } - call(file, flags) + for _, call := range calls { + call(file, flags) + } } if flags.SidebarConfig { @@ -397,6 +409,7 @@ func insertExpFeatures(jsPath string, flags Flag) { &content, `(([\w$.]+\.fromJSON)\(\w+\)+;)(return ?[\w{}().,]+[\w$]+\.Provider,)(\{value:\{localConfiguration)`, func(submatches ...string) string { + fmt.Println(submatches) return fmt.Sprintf("%sSpicetify.createInternalMap=%s;%sSpicetify.RemoteConfigResolver=%s", submatches[1], submatches[2], submatches[3], submatches[4]) }) return content From 4e947adbfb2b4a54ab342cb091ca6b5a2942d240 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 9 Sep 2024 00:54:42 +0200 Subject: [PATCH 056/146] fix(navlinks): add support for `1.2.46`'s global navbar --- src/apply/apply.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/apply/apply.go b/src/apply/apply.go index be346eb5c0..6341a9fc49 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -332,11 +332,16 @@ func insertNavLink(str string, appNameArray string) string { 1) } - // Global Navbar + // Global Navbar <= 1.2.45 utils.ReplaceOnce(&str, `(,[a-zA-Z_\$][\w\$]*===(?:[a-zA-Z_\$][\w\$]*\.){2}HOME_NEXT_TO_NAVIGATION&&.+?)\]`, func(submatches ...string) string { return fmt.Sprintf("%s,Spicetify._renderNavLinks([%s], true)]", submatches[1], appNameArray) }) + // Global Navbar >= 1.2.46 + utils.ReplaceOnce(&str, `("global-nav-bar".*?)(\(0,\s*[a-zA-Z_\$][\w\$]*\.jsx\))(\(\s*\w+,\s*\{\s*className:\w*\s*\}\s*\))\s*\}\),\s*(\(0,)`, func(submatches ...string) string { + return fmt.Sprintf("%s[%s%s,Spicetify._renderNavLinks([%s], true)].flat()}),%s", submatches[1], submatches[2], submatches[3], appNameArray, submatches[4]) + }) + return str } @@ -409,7 +414,6 @@ func insertExpFeatures(jsPath string, flags Flag) { &content, `(([\w$.]+\.fromJSON)\(\w+\)+;)(return ?[\w{}().,]+[\w$]+\.Provider,)(\{value:\{localConfiguration)`, func(submatches ...string) string { - fmt.Println(submatches) return fmt.Sprintf("%sSpicetify.createInternalMap=%s;%sSpicetify.RemoteConfigResolver=%s", submatches[1], submatches[2], submatches[3], submatches[4]) }) return content From c0bb5d8f05935ad20de26c1aa108b433fb584dc8 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 9 Sep 2024 01:02:19 +0200 Subject: [PATCH 057/146] fix(apply): remove 4th submatch since it's unnecessary --- src/apply/apply.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apply/apply.go b/src/apply/apply.go index 6341a9fc49..cbcb1b7c8d 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -338,8 +338,8 @@ func insertNavLink(str string, appNameArray string) string { }) // Global Navbar >= 1.2.46 - utils.ReplaceOnce(&str, `("global-nav-bar".*?)(\(0,\s*[a-zA-Z_\$][\w\$]*\.jsx\))(\(\s*\w+,\s*\{\s*className:\w*\s*\}\s*\))\s*\}\),\s*(\(0,)`, func(submatches ...string) string { - return fmt.Sprintf("%s[%s%s,Spicetify._renderNavLinks([%s], true)].flat()}),%s", submatches[1], submatches[2], submatches[3], appNameArray, submatches[4]) + utils.ReplaceOnce(&str, `("global-nav-bar".*?)(\(0,\s*[a-zA-Z_\$][\w\$]*\.jsx\))(\(\s*\w+,\s*\{\s*className:\w*\s*\}\s*\))`, func(submatches ...string) string { + return fmt.Sprintf("%s[%s%s,Spicetify._renderNavLinks([%s], true)].flat()", submatches[1], submatches[2], submatches[3], appNameArray) }) return str From e2fd1e656bfc20e3979ad28caf8ad7b70f84f122 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 9 Sep 2024 01:19:57 +0200 Subject: [PATCH 058/146] feat(preprocess/contextmenu): simplify matches --- src/preprocess/preprocess.go | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 4b2f2f2d0e..4e42663be6 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -561,20 +561,11 @@ func exposeAPIs_main(input string) string { return fmt.Sprintf("%s[Spicetify.ContextMenuV2.renderItems(),%s].flat()", submatches[1], submatches[2]) }) - croppedInput := utils.FindFirstMatch(input, `\({menu:[^,]+,.*?triggerRef:[^,]+,[^}]*}\).*value:"contextmenu"`)[0] - - reactRef := utils.FindFirstMatch(croppedInput, `([\w_$]+)\.useRef`) - var react, menu, trigger, target string - if len(reactRef) == 1 { - react = reactRef[0] - } else { - react = reactRef[1] - } - - menuCandidates := utils.FindMatch(croppedInput, `menu:([\w_$]+)`) - menu = menuCandidates[0][1] - trigger = utils.FindFirstMatch(croppedInput, `trigger:([\w_$]+)`)[1] - target = utils.FindFirstMatch(croppedInput, `triggerRef:([\w_$]+)`)[1] + croppedInput := utils.FindFirstMatch(input, `.*value:"contextmenu"`)[0] + react := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)\.useRef`)[1] + menu := utils.FindLastMatch(croppedInput, `menu:([\w_$]+)`)[1] + trigger := utils.FindLastMatch(croppedInput, `trigger:([\w_$]+)`)[1] + target := utils.FindLastMatch(croppedInput, `triggerRef:([\w_$]+)`)[1] 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]) From 8b7596f45cd1874565c403be10c6fd4a4753fc9e Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 9 Sep 2024 01:20:08 +0200 Subject: [PATCH 059/146] fix(homeConfig): check if `uri` exists too --- jsHelper/homeConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsHelper/homeConfig.js b/jsHelper/homeConfig.js index a9c5b3b94e..59fc1db80d 100644 --- a/jsHelper/homeConfig.js +++ b/jsHelper/homeConfig.js @@ -21,7 +21,7 @@ SpicetifyHomeConfig = {}; const stickSections = []; const lowSections = []; for (const uri of stickList) { - const index = sections.findIndex((a) => a?.uri === uri || a?.item.uri === uri); + const index = sections.findIndex((a) => a?.uri === uri || a?.item?.uri === uri); if (index !== -1) { const item = sections[index]; const uri = item.item.uri || item.uri; @@ -31,7 +31,7 @@ SpicetifyHomeConfig = {}; } } for (const uri of lowList) { - const index = sections.findIndex((a) => a?.uri === uri || a?.item.uri === uri); + const index = sections.findIndex((a) => a?.uri === uri || a?.item?.uri === uri); if (index !== -1) { const item = sections[index]; const uri = item.item.uri || item.uri; From 761fe0f389cd619d1679ca1a952b299883ad7c44 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 9 Sep 2024 02:04:10 +0200 Subject: [PATCH 060/146] fix(block-updates): go back one folder --- src/cmd/block-updates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/block-updates.go b/src/cmd/block-updates.go index d680fe655f..c65877c185 100644 --- a/src/cmd/block-updates.go +++ b/src/cmd/block-updates.go @@ -21,7 +21,7 @@ func BlockSpotifyUpdates(disabled bool) { case "windows": spotifyExecPath = filepath.Join(spotifyExecPath, "Spotify.exe") case "darwin": - spotifyExecPath = filepath.Join(spotifyExecPath, "Spotify") + spotifyExecPath = filepath.Join(spotifyExecPath, "..", "MacOS", "Spotify") } file, err := os.OpenFile(spotifyExecPath, os.O_RDWR, 0644) From d0c9272e7638698d160eabdfba401c2ad534fbfd Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 9 Sep 2024 02:59:41 +0200 Subject: [PATCH 061/146] fix(preprocess/contextmenu): fallback when regex doesn't find candicates --- src/preprocess/preprocess.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 4e42663be6..8e7dbea7a7 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -563,9 +563,17 @@ func exposeAPIs_main(input string) string { croppedInput := utils.FindFirstMatch(input, `.*value:"contextmenu"`)[0] react := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)\.useRef`)[1] - menu := utils.FindLastMatch(croppedInput, `menu:([\w_$]+)`)[1] - trigger := utils.FindLastMatch(croppedInput, `trigger:([\w_$]+)`)[1] - target := utils.FindLastMatch(croppedInput, `triggerRef:([\w_$]+)`)[1] + candicates := utils.FindLastMatch(croppedInput, `\(\{[^}]*menu:([a-zA-Z_\$][\w\$]*),[^}]*trigger:([a-zA-Z_\$][\w\$]*),[^}]*triggerRef:([a-zA-Z_\$][\w\$]*)`) + var menu, trigger, target string + if len(candicates) == 0 { + menu = "e.menu" + trigger = "e.trigger" + target = "e.triggerRef" + } else { + menu = candicates[1] + trigger = candicates[2] + target = candicates[3] + } 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]) From 2b46b98913bebc18b495b29a2388729041ed1614 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 9 Sep 2024 03:44:53 +0200 Subject: [PATCH 062/146] fix(block-updates): block update folder on `macOS` --- src/cmd/block-updates.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/cmd/block-updates.go b/src/cmd/block-updates.go index c65877c185..2979498752 100644 --- a/src/cmd/block-updates.go +++ b/src/cmd/block-updates.go @@ -3,6 +3,7 @@ package cmd import ( "bytes" "os" + "os/exec" "path/filepath" "runtime" "strings" @@ -24,6 +25,30 @@ func BlockSpotifyUpdates(disabled bool) { spotifyExecPath = filepath.Join(spotifyExecPath, "..", "MacOS", "Spotify") } + var str, msg string + if runtime.GOOS == "darwin" { + homeDir, err := os.UserHomeDir() + if err != nil { + utils.PrintError("Cannot get user home directory") + return + } + updateDir := homeDir + "/Library/Application Support/Spotify/PersistentCache/Update" + if disabled { + exec.Command("pkill", "Spotify").Run() + exec.Command("mkdir", "-p", updateDir).Run() + exec.Command("chflags", "uchg", updateDir).Run() + msg = "Disabled" + } else { + exec.Command("pkill", "Spotify").Run() + exec.Command("mkdir", "-p", updateDir).Run() + exec.Command("chflags", "nouchg", updateDir).Run() + msg = "Enabled" + } + + utils.PrintSuccess(msg + " Spotify updates!") + return + } + file, err := os.OpenFile(spotifyExecPath, os.O_RDWR, 0644) if err != nil { utils.Fatal(err) @@ -40,7 +65,6 @@ func BlockSpotifyUpdates(disabled bool) { utils.PrintError("Can't find update endpoint in executable") return } - var str, msg string if disabled { str = "no/thanks" msg = "Disabled" From 11171db866f637b6484ffc86e39e10f76797bcfe Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 9 Sep 2024 19:21:03 +0200 Subject: [PATCH 063/146] feat(wrapper): relay on webpack's `OnChunksLoadedRuntimeModule` Co-authored-by: Delusoire --- jsHelper/spicetifyWrapper.js | 56 ++++++++++++------------------------ 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 2ae81cbeb1..d40a939bc1 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -450,6 +450,17 @@ window.Spicetify = { // Force all webpack modules to load const require = webpackChunkclient_web.push([[Symbol()], {}, (re) => re]); while (!require.m) await new Promise((r) => setTimeout(r, 50)); + console.log("[spicetifyWrapper] Waiting for required webpack modules to load"); + let webpackDidCallback = false; + // https://github.com/webpack/webpack/blob/main/lib/runtime/OnChunksLoadedRuntimeModule.js + require.O( + null, + [], + () => { + webpackDidCallback = true; + }, + 6 + ); let chunks = Object.entries(require.m); let cache = Object.keys(require.m).map((id) => require(id)); @@ -457,44 +468,13 @@ window.Spicetify = { // For _renderNavLinks to work Spicetify.React = cache.find((m) => m?.useMemo); - // Get all script tags matching root directory - // Some link tags modules are not included in require.m/unused - const scripts = [...document.querySelectorAll("script")] - // Get scripts from root dir - .filter((script) => script.src?.includes("xpui.app.spotify.com")) - // Filter out non-webpack scripts - .filter((script) => ["extensions", "spicetify", "helper", "theme"].every((str) => !script.src?.includes(str))); - - //console.time("sanitize"); - await Promise.all( - scripts.map(async (script) => { - try { - const res = await fetch(script.src); - const text = await res.text(); - // remove every string from the content - const sanitizedText = text.replace(/(["'`])(?:\\.|[^\\\1])*?\1/g, ""); - const src = script.src.split("/").pop(); - console.log(`[spicetifyWrapper] Waiting for ${src}`); - for (const pack of sanitizedText.match(/(? str.slice(0, -7).slice(1))) { - //console.debug(`[spicetifyWrapper] Waiting for ${pack} of ${src}`); - while (!require.m || !Object.keys(require.m).includes(pack)) { - await new Promise((r) => setTimeout(r, 100)); - } - } - console.log(`[spicetifyWrapper] Loaded ${src}`); - } catch (e) { - return console.error(e); - } - }) - ).then(() => { - console.log("[spicetifyWrapper] All required webpack modules loaded"); - //console.timeEnd("sanitize"); - chunks = Object.entries(require.m); - cache = Object.keys(require.m).map((id) => require(id)); - - // Fire platformLoaded event there because of the sleep functions before - Spicetify.Events.platformLoaded.fire(); - }); + while (!webpackDidCallback) { + await new Promise((r) => setTimeout(r, 100)); + } + console.log("[spicetifyWrapper] All required webpack modules loaded"); + chunks = Object.entries(require.m); + cache = Object.keys(require.m).map((id) => require(id)); + Spicetify.Events.platformLoaded.fire(); const modules = cache .filter((module) => typeof module === "object") From ae905881c00ac6594651fa540197fd36f756f399 Mon Sep 17 00:00:00 2001 From: Thomas Fitzpatrick <22730962+ohitstom@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:42:37 +0100 Subject: [PATCH 064/146] feat(topbar): cross-version classname support (#3162) Co-authored-by: ririxi --- jsHelper/spicetifyWrapper.js | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index d40a939bc1..386e64ddd2 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -2060,11 +2060,12 @@ Object.defineProperty(Spicetify, "TippyProps", { }); Spicetify.Topbar = (() => { + let leftGeneratedClassName; + let rightGeneratedClassName; let leftContainer; let rightContainer; const leftButtonsStash = new Set(); const rightButtonsStash = new Set(); - const generatedClassName = "Button-medium-medium-buttonTertiary-iconOnly-condensed-disabled-isUsingKeyboard-useBrowserDefaultFocusStyle"; class Button { constructor(label, icon, onClick, disabled = false, isRight = false) { @@ -2080,19 +2081,12 @@ Spicetify.Topbar = (() => { this.label = label; this.element.appendChild(this.button); - const globalHistoryButtons = document.querySelector(".main-globalNav-historyButtons"); if (isRight) { - this.button.classList.add("encore-over-media-set", "main-topBar-buddyFeed"); - if (globalHistoryButtons) this.button.classList.add("main-globalNav-buddyFeed"); - + this.button.className = rightGeneratedClassName; rightButtonsStash.add(this.element); rightContainer?.prepend(this.element); } else { - this.button.classList.add("main-topBar-button"); - if (globalHistoryButtons) { - this.button.classList.add("main-globalNav-icon", generatedClassName); - } - + this.button.className = leftGeneratedClassName; leftButtonsStash.add(this.element); leftContainer?.append(this.element); } @@ -2136,9 +2130,15 @@ Spicetify.Topbar = (() => { function waitForTopbarMounted() { const globalHistoryButtons = document.querySelector(".main-globalNav-historyButtons"); + leftGeneratedClassName = document.querySelector( + ".main-topBar-historyButtons .main-topBar-button, .main-globalNav-historyButtons .main-globalNav-icon" + )?.className; + rightGeneratedClassName = document.querySelector( + ".main-topBar-container .main-topBar-buddyFeed, .main-actionButtons .main-topBar-buddyFeed, .main-actionButtons .main-globalNav-buddyFeed" + )?.className; leftContainer = document.querySelector(".main-topBar-historyButtons") ?? globalHistoryButtons; rightContainer = document.querySelector(".main-actionButtons"); - if (!leftContainer || !rightContainer) { + if (!leftContainer || !rightContainer || !leftGeneratedClassName || !rightGeneratedClassName) { setTimeout(waitForTopbarMounted, 100); return; } @@ -2148,19 +2148,14 @@ Spicetify.Topbar = (() => { if (button.parentNode) button.parentNode.removeChild(button); const buttonElement = button.querySelector("button"); - if (globalHistoryButtons) { - buttonElement.classList.add("main-globalNav-icon", generatedClassName); - } else { - buttonElement.classList.remove("main-globalNav-icon", generatedClassName); - } + buttonElement.className = leftGeneratedClassName; } leftContainer.append(...leftButtonsStash); for (const button of rightButtonsStash) { if (button.parentNode) button.parentNode.removeChild(button); const buttonElement = button.querySelector("button"); - if (globalHistoryButtons) buttonElement.classList.add("main-globalNav-buddyFeed"); - else buttonElement.classList.remove("main-globalNav-buddyFeed"); + buttonElement.className = rightGeneratedClassName; } rightContainer.prepend(...rightButtonsStash); } From 768cb4be44438205b482eef622fdf4f98e0b0232 Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 11 Sep 2024 23:25:34 +0200 Subject: [PATCH 065/146] fix(apply): check for old global navbar existence --- src/apply/apply.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/apply/apply.go b/src/apply/apply.go index cbcb1b7c8d..e6d9d4fc50 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -333,14 +333,17 @@ func insertNavLink(str string, appNameArray string) string { } // Global Navbar <= 1.2.45 + globalNavBarMatch := utils.FindMatch(str, `(,[a-zA-Z_\$][\w\$]*===(?:[a-zA-Z_\$][\w\$]*\.){2}HOME_NEXT_TO_NAVIGATION&&.+?)\]`) utils.ReplaceOnce(&str, `(,[a-zA-Z_\$][\w\$]*===(?:[a-zA-Z_\$][\w\$]*\.){2}HOME_NEXT_TO_NAVIGATION&&.+?)\]`, func(submatches ...string) string { return fmt.Sprintf("%s,Spicetify._renderNavLinks([%s], true)]", submatches[1], appNameArray) }) // Global Navbar >= 1.2.46 - utils.ReplaceOnce(&str, `("global-nav-bar".*?)(\(0,\s*[a-zA-Z_\$][\w\$]*\.jsx\))(\(\s*\w+,\s*\{\s*className:\w*\s*\}\s*\))`, func(submatches ...string) string { - return fmt.Sprintf("%s[%s%s,Spicetify._renderNavLinks([%s], true)].flat()", submatches[1], submatches[2], submatches[3], appNameArray) - }) + if len(globalNavBarMatch) == 0 { + utils.ReplaceOnce(&str, `("global-nav-bar".*?)(\(0,\s*[a-zA-Z_\$][\w\$]*\.jsx\))(\(\s*\w+,\s*\{\s*className:\w*\s*\}\s*\))`, func(submatches ...string) string { + return fmt.Sprintf("%s[%s%s,Spicetify._renderNavLinks([%s], true)].flat()", submatches[1], submatches[2], submatches[3], appNameArray) + }) + } return str } From 94e5c63c0f92df849c30719c92a09fa6cba3628e Mon Sep 17 00:00:00 2001 From: Thomas Fitzpatrick <22730962+ohitstom@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:32:02 +0100 Subject: [PATCH 066/146] fix(new-releases): use hardcoded `queryArtistDiscographyAll` def (#3166) --- CustomApps/lyrics-plus/style.css | 20 +++++++++++--------- CustomApps/new-releases/index.js | 21 ++++++++++++++------- CustomApps/new-releases/style.css | 8 ++------ 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/CustomApps/lyrics-plus/style.css b/CustomApps/lyrics-plus/style.css index 56f12b94ca..c07fc38a4b 100644 --- a/CustomApps/lyrics-plus/style.css +++ b/CustomApps/lyrics-plus/style.css @@ -65,14 +65,14 @@ .lyrics-lyricsContainer-Loading { align-self: center; - grid-area: 1/1/-1/-1; + grid-area: 1 / 1 / -1 / -1; } .lyrics-lyricsContainer-LyricsUnavailablePage { align-items: center; color: var(--lyrics-color-inactive); display: flex; - grid-area: 1/1/-1/-1; + grid-area: 1 / 1 / -1 / -1; height: 100%; justify-content: center; padding: 20px; @@ -82,7 +82,7 @@ } .lyrics-lyricsContainer-UnsyncedLyricsPage { - grid-area: 1/1/-1/-1; + grid-area: 1 / 1 / -1 / -1; grid-template-rows: 1fr 20px; user-select: text; text-align: var(--lyrics-align-text); @@ -100,7 +100,7 @@ .lyrics-lyricsContainer-SyncedLyricsPage { display: grid; - grid-area: 1/1/-1/-1; + grid-area: 1 / 1 / -1 / -1; grid-template-rows: 1fr 30px; overflow: hidden; text-align: var(--lyrics-align-text); @@ -110,14 +110,14 @@ .lyrics-lyricsContainer-LyricsBackground { background-color: var(--lyrics-color-background); background-image: var(--lyrics-background-noise); - grid-area: 1/1/-1/-1; + grid-area: 1 / 1 / -1 / -1; transition: background-color 0.25s ease-out; } .lyrics-lyricsContainer-Provider { align-self: end; color: var(--lyrics-color-inactive); - grid-area: 2/1/-1/-1; + grid-area: 2 / 1 / -1 / -1; justify-self: stretch; height: 25px; overflow: hidden; @@ -129,7 +129,7 @@ .lyrics-lyricsContainer-SyncedLyrics { --lyrics-line-height: calc(4px + var(--lyrics-font-size)); - grid-area: 1/1/-2/-1; + grid-area: 1 / 1 / -2 / -1; height: 0; } @@ -308,11 +308,13 @@ div.lyrics-tabBar-headerItemLink { .lyrics-lyricsContainer-Karaoke-Word { color: var(--lyrics-color-inactive); - background-image: linear-gradient(to right, + background-image: linear-gradient( + to right, var(--lyrics-color-active), var(--lyrics-color-active) 45%, var(--lyrics-color-inactive) 55%, - var(--lyrics-color-inactive)); + var(--lyrics-color-inactive) + ); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 225% 100%; diff --git a/CustomApps/new-releases/index.js b/CustomApps/new-releases/index.js index c312be7b6b..0acd5ba216 100644 --- a/CustomApps/new-releases/index.js +++ b/CustomApps/new-releases/index.js @@ -290,13 +290,20 @@ async function getArtistList() { } async function getArtistEverything(artist) { - const { queryArtistDiscographyAll } = Spicetify.GraphQL.Definitions; - const { data, errors } = await Spicetify.GraphQL.Request(queryArtistDiscographyAll, { - uri: artist.uri, - offset: 0, - // Limit 100 since GraphQL has resource limit - limit: 100, - }); + const { data, errors } = await Spicetify.GraphQL.Request( + { + name: "queryArtistDiscographyAll", + operation: "query", + sha256Hash: "9380995a9d4663cbcb5113fef3c6aabf70ae6d407ba61793fd01e2a1dd6929b0", + value: null, + }, + { + uri: artist.uri, + offset: 0, + // Limit 100 since GraphQL has resource limit + limit: 100, + } + ); if (errors) throw errors; const releases = data?.artistUnion.discography.all.items.flatMap((r) => r.releases.items); diff --git a/CustomApps/new-releases/style.css b/CustomApps/new-releases/style.css index 944814e715..f5637dec0d 100644 --- a/CustomApps/new-releases/style.css +++ b/CustomApps/new-releases/style.css @@ -152,9 +152,7 @@ option { width: 28px; visibility: hidden; opacity: 0; - transition: - visibility 0s, - opacity 0.3s ease; + transition: visibility 0s, opacity 0.3s ease; } .main-card-closeButton:active { @@ -167,9 +165,7 @@ option { .main-card-card:hover .main-card-closeButton { visibility: visible; opacity: 1; - transition: - visibility 0s, - opacity 0.3s ease; + transition: visibility 0s, opacity 0.3s ease; } .new-releases-header + .main-gridContainer-gridContainer { From bef76be550e966a1c0108d0adb08ef962a40e992 Mon Sep 17 00:00:00 2001 From: ririxi Date: Fri, 27 Sep 2024 20:44:58 +0200 Subject: [PATCH 067/146] chore(sidebarConfig): change how msg is shown --- jsHelper/sidebarConfig.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/jsHelper/sidebarConfig.js b/jsHelper/sidebarConfig.js index a64eb7b2ca..2c3eba1bb5 100644 --- a/jsHelper/sidebarConfig.js +++ b/jsHelper/sidebarConfig.js @@ -166,13 +166,26 @@ color: var(--spice-button-disabled); (async () => { await new Promise((res) => Spicetify.Events.webpackLoaded.on(res)); if (document.querySelector(".Root__globalNav")) { - Spicetify.showNotification( - "Sidebar config is not supported on global navbar. Please disable it with `spicetify config sidebar_config 0` command.", - false, - 7000 - ); + Spicetify.Snackbar?.enqueueCustomSnackbar("sidebar-config", { + keyPrefix: "sidebar-config", + autoHideDuration: 7500, + children: Spicetify.ReactComponent.Snackbar.wrapper({ + children: Spicetify.ReactComponent.Snackbar.simpleLayout({ + center: Spicetify.React.createElement("div", { + dangerouslySetInnerHTML: { + __html: + "Sidebar config is not supported when Global Navbar is enabled. In Powershell, please run spicetify config sidebar_config 0 command and then re-apply spicetify with spicetify apply.", + }, + style: { + "text-size": "12px", + }, + }), + }), + }), + }); isGlobalNavbar = true; } + if (!isGlobalNavbar) { new Spicetify.Menu.Item( "Sidebar config", From 8e1926d2994c01e71dc32f666eef12675b0f2102 Mon Sep 17 00:00:00 2001 From: ririxi Date: Fri, 27 Sep 2024 20:46:51 +0200 Subject: [PATCH 068/146] fix(preprocess): add new regex to catch variable names for context menu --- src/preprocess/preprocess.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 8e7dbea7a7..6ea27620ef 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -564,15 +564,20 @@ func exposeAPIs_main(input string) string { croppedInput := utils.FindFirstMatch(input, `.*value:"contextmenu"`)[0] react := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)\.useRef`)[1] candicates := utils.FindLastMatch(croppedInput, `\(\{[^}]*menu:([a-zA-Z_\$][\w\$]*),[^}]*trigger:([a-zA-Z_\$][\w\$]*),[^}]*triggerRef:([a-zA-Z_\$][\w\$]*)`) + oldCandicates := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)=[\w_$]+\.menu[^}]*,([a-zA-Z_\$][\w\$]*)=[\w_$]+\.trigger[^}]*,([a-zA-Z_\$][\w\$]*)=[\w_$]+\.triggerRef`) var menu, trigger, target string - if len(candicates) == 0 { - menu = "e.menu" - trigger = "e.trigger" - target = "e.triggerRef" - } else { + if len(oldCandicates) != 0 { + menu = oldCandicates[1] + trigger = oldCandicates[2] + target = oldCandicates[3] + } else if len(candicates) != 0 { menu = candicates[1] trigger = candicates[2] target = candicates[3] + } else { + menu = "e.menu" + trigger = "e.trigger" + target = "e.triggerRef" } utils.Replace(&input, `\(0,([\w_$]+)\.jsx\)\([\w_$]+\.[\w_$]+,\{value:"contextmenu"[^\}]+\}\)\}\)`, func(submatches ...string) string { From bb767a9059143fe183c1c577acff335dc6a462b7 Mon Sep 17 00:00:00 2001 From: million1156 Date: Mon, 30 Sep 2024 16:11:41 -0500 Subject: [PATCH 069/146] chore(sidebarConfig): make phrasing more os inclusive (#3190) --- jsHelper/sidebarConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsHelper/sidebarConfig.js b/jsHelper/sidebarConfig.js index 2c3eba1bb5..23f2e53193 100644 --- a/jsHelper/sidebarConfig.js +++ b/jsHelper/sidebarConfig.js @@ -174,7 +174,7 @@ color: var(--spice-button-disabled); center: Spicetify.React.createElement("div", { dangerouslySetInnerHTML: { __html: - "Sidebar config is not supported when Global Navbar is enabled. In Powershell, please run spicetify config sidebar_config 0 command and then re-apply spicetify with spicetify apply.", + "Sidebar config is not supported when Global Navbar is enabled. In your terminal, please run spicetify config sidebar_config 0 command and then re-apply spicetify with spicetify apply.", }, style: { "text-size": "12px", From 156e38f5eb54c71b284da07f1c5c777245eba9d6 Mon Sep 17 00:00:00 2001 From: Julien <182520+JulienMaille@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:11:38 +0200 Subject: [PATCH 070/146] fix: svg url in readme.md (#3204) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89d72ce631..1ad36819b2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

- + From 8061895ffb25db0c93fec6d7b15dfdb31b4e7ea4 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Tue, 22 Oct 2024 13:48:58 +0100 Subject: [PATCH 071/146] chore: remove stale labeler (#3207) --- .github/workflows/stale.yml | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index db291ac26e..0000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Mark stale issues and pull requests - -on: - schedule: - - cron: "20 4 * * *" - -jobs: - stale: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - - steps: - - uses: actions/stale@v9.0.0 - with: - # Token for the repository. Can be passed in using `{{ secrets.GITHUB_TOKEN }}`. - repo-token: ${{ github.token }} - # The message to post on the issue when tagging it. If none provided, will not mark issues stale. - stale-issue-message: "Marking this issue as stale.\n\nLet someone know if I'm wrong, after all, I'm just a bot!" - # The message to post on the pull request when tagging it. If none provided, will not mark pull requests stale. - stale-pr-message: "Marking this PR as stale.\n\nLet someone know if I'm wrong, after all, I'm just a bot!" - # The message to post on the issue when closing it. If none provided, will not comment when closing an issue. - close-issue-message: "Closing this issue due to inactivity.\n\nLet someone know if I'm wrong, after all, I'm just a bot!" - # The message to post on the pull request when closing it. If none provided, will not comment when closing a pull requests. - close-pr-message: "Closing this PR due to inactivity.\n\nLet someone know if I'm wrong, after all, I'm just a bot!" - # The number of days old an issue or a pull request can be before marking it stale. Set to -1 to never mark issues or pull requests as stale automatically. - days-before-stale: 30 - # The number of days to wait to close an issue or a pull request after it being marked stale. Set to -1 to never close stale issues or pull requests. - days-before-close: -1 - # The label to apply when an issue is stale. - stale-issue-label: "☠️ stale" - # Only issues with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the issues. - only-issue-labels: "🐛 bug" - # Display some statistics at the end regarding the stale workflow (only when the logs are enabled). - enable-statistics: true From d614adbc8c81374de1fa5298a47936fced659750 Mon Sep 17 00:00:00 2001 From: Thomas Fitzpatrick <22730962+ohitstom@users.noreply.github.com> Date: Sat, 26 Oct 2024 16:38:40 +0100 Subject: [PATCH 072/146] feat(css-map): add header 1.2.49 classnames (#3208) --- css-map.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/css-map.json b/css-map.json index 9623156d3b..ac6fd35fc5 100644 --- a/css-map.json +++ b/css-map.json @@ -159,6 +159,7 @@ "E4q8ogfdWtye7YgotBlN": "main-actionBar-ActionBar", "eSg4ntPU2KQLfpLGXAww": "main-actionBar-ActionBarRow", "CoLO4pdSl8LGWyVZA00t": "main-actionBarBackground-background", + "PkOz5g82CaoKk1J3GX0e": "main-actionBarBackground-background", "GTAFfOA_w5vh_bDaGJAG": "main-actionButtons", "NPvLJSRWfv1Joo8dF0D8": "main-actionButtons", "WIGgdAaAzrXm7f_loaXi": "main-actionButtons-button", @@ -432,7 +433,9 @@ "gB6AcMixPEmdr96SUSBM": "main-embedWidgetGenerator-visible", "P4iG24p5ttZDxkQJsiDb": "main-embedWidgetGenerator-widthField", "MyW8tKEekj9lKQsviDdP": "main-entityHeader-background", + "i_kMOBXfnweAkv8s97Yc": "main-entityHeader-background", "gHImFiUWOg93pvTefeAD": "main-entityHeader-backgroundColor", + "PeLrpasyfBW8ql_bmoAi": "main-entityHeader-backgroundColor", "H0vWBc23fJOetym6NudG": "main-entityHeader-bold", "ta4ePOlmGXjBYPTd90lh": "main-entityHeader-circle", "NXiYChVp4Oydfxd7rT5r": "main-entityHeader-container", @@ -441,6 +444,7 @@ "Ydwa1P5GkCggtLlSvphs": "main-entityHeader-detailsText", "n4hTP7ZeAOT_UQEkRUR7": "main-entityHeader-divider", "k2I8B0MzXkAJ6_s8okM7": "main-entityHeader-gradient", + "sv5suqIPUwjgUF_BzM41": "main-entityHeader-gradient", "PUIUCdIR_h05BC2EDgIP": "main-entityHeader-gray", "RP2rRchy4i8TIp1CTmb7": "main-entityHeader-headerText", "CmkY1Ag0tJDfnFXbGgju": "main-entityHeader-image", @@ -459,6 +463,8 @@ "ByDYHUfzUKVgFwT5bTxp": "main-entityHeader-newEntriesIndicator", "RMDSGDMFrx8eXHpFphqG": "main-entityHeader-nonWrapped", "xYgjMpAjE5XT05aRIezb": "main-entityHeader-overlay", + "I0bVSxvqA3rm5HvciMap": "main-entityHeader-overlay", + "yhlH4Dsjqw56Z58EOwvQ": "main-entityHeader-overlay", "YW4dYEf5ZuLzMfSjsqZk": "main-entityHeader-piled", "lp9Tfm4rsM9_pfbIE0zd": "main-entityHeader-pretitle", "YbDIZ84mS7tzHr1tgWE9": "main-entityHeader-roundedCorners", From 8663efe2ff8c809b800c5e72c2421f10a09d596a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:05:15 +0100 Subject: [PATCH 073/146] chore(deps): bump golang.org/x/net from 0.29.0 to 0.30.0 (#3196) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 28bdb49057..b350763334 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.13 - golang.org/x/net v0.29.0 + golang.org/x/net v0.30.0 ) require ( github.com/mattn/go-isatty v0.0.16 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index 7cf903b3b1..b8ce878284 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 6e93b53ef4888b9cba0f176339147360e1ea7481 Mon Sep 17 00:00:00 2001 From: "Patryk L." Date: Sat, 2 Nov 2024 20:06:36 +0100 Subject: [PATCH 074/146] feat(fullAppDisplay): seekable progress and aligned buttons (#3168) --- Extensions/fullAppDisplay.js | 148 +++++++++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 26 deletions(-) diff --git a/Extensions/fullAppDisplay.js b/Extensions/fullAppDisplay.js index b0642cc531..bf163b7fe4 100644 --- a/Extensions/fullAppDisplay.js +++ b/Extensions/fullAppDisplay.js @@ -11,7 +11,7 @@ } const { React: react, ReactDOM: reactDOM } = Spicetify; - const { useState, useEffect } = react; + const { useState, useEffect, useRef } = react; const CONFIG = getConfig(); let updateVisual; @@ -90,27 +90,47 @@ border: 0; color: #fff; padding: 0 5px; + cursor: pointer; } #fad-progress-container { - width: 100%; display: flex; align-items: center; + justify-content: center; + flex-grow: 1; + gap: 10px; } #fad-progress { width: 100%; height: 6px; border-radius: 6px; background-color: #ffffff50; - overflow: hidden; + flex-grow: 1; + min-width: 150px; +} +#fad-progress:hover #fad-progress-inner { + background-color: var(--spice-button); +} +#fad-progress:hover #fad-thumb { + visibility: visible; } #fad-progress-inner { + width: var(--progress-width); height: 100%; border-radius: 6px; background-color: #ffffff; box-shadow: 4px 0 12px rgba(0, 0, 0, 0.8); + position: relative; } -#fad-duration { - margin-left: 10px; +#fad-thumb { + position: absolute; + top: -3px; + right: -6px; + width: 12px; + height: 12px; + border-radius: 50%; + background-color: #ffffff; + cursor: pointer; + visibility: hidden; } #fad-background { position: absolute; @@ -134,6 +154,13 @@ body.video-full-screen.video-full-screen--hide-ui { border: 0; color: currentColor; padding: 0 5px; + cursor: pointer; +} +#fad-controls button svg { + vertical-align: middle; +} +#fad-elapsed, #fad-duration { + font-variant-numeric: tabular-nums; } #fad-artist svg, #fad-album svg, #fad-release-date svg { display: inline-block; @@ -173,19 +200,16 @@ body.video-full-screen.video-full-screen--hide-ui { } #fad-status { display: flex; - min-width: 400px; - max-width: 400px; align-items: center; + flex-wrap: wrap; + gap: 10px; } #fad-status.active { margin-top: 20px; } #fad-controls { display: flex; - margin-right: 10px; -} -#fad-elapsed { - min-width: 52px; + margin: 0 auto; }`, ` #fad-art { @@ -229,10 +253,8 @@ body.video-full-screen.video-full-screen--hide-ui { margin-top: 20px; order: 2 } -#fad-elapsed { - min-width: 56px; - margin-right: 10px; - text-align: right; +#fad-progress-container { + width: 100%; }`, ]; @@ -257,7 +279,6 @@ body.video-full-screen.video-full-screen--hide-ui { } #fad-art { max-width: 210px; - margin-left: 50px; }`, "", ]; @@ -297,26 +318,99 @@ body.video-full-screen.video-full-screen--hide-ui { }; const ProgressBar = () => { - const [value, setValue] = useState(Spicetify.Player.getProgress()); + const [progress, setProgress] = useState(Spicetify.Player.getProgress()); + const duration = Spicetify.Platform.PlayerAPI._state.duration; + + const progressDivRef = useRef(null); + const [isDragging, setIsDragging] = useState(false); + useEffect(() => { - const update = ({ data }) => setValue(data); + if (isDragging) { + return; + } + + const update = ({ data }) => setProgress(data); Spicetify.Player.addEventListener("onprogress", update); return () => Spicetify.Player.removeEventListener("onprogress", update); - }); - const duration = Spicetify.Platform.PlayerAPI._state.duration; + }, [isDragging]); + + // Handle click on progress bar to set progress + const handleClick = (e) => { + const container = progressDivRef.current; + if (isDragging || !container) { + return; + } + + const containerRect = container.getBoundingClientRect(); + const clickX = e.clientX - containerRect.left; + const newProgress = (clickX / containerRect.width) * duration; + Spicetify.Player.seek(newProgress); + setProgress(newProgress); + }; + + // Handle dragging functionality + const handleMouseDown = () => setIsDragging(true); + const handleMouseMove = (e) => { + const container = progressDivRef.current; + if (!isDragging || !container) { + return; + } + + const containerRect = container.getBoundingClientRect(); + const offsetX = e.clientX - containerRect.left; + const newProgress = (offsetX / containerRect.width) * duration; + setProgress(newProgress); + }; + const handleMouseUp = () => { + if (!isDragging) { + return; + } + + Spicetify.Player.seek(progress); + setIsDragging(false); + }; + + // Attach mousemove and mouseup listeners when dragging starts + useEffect(() => { + if (isDragging) { + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + } else { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + } + + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + }; + }, [isDragging]); + + // Calculate the thumb position + const thumbPosition = (progress / duration) * 100; + return react.createElement( "div", { id: "fad-progress-container" }, - react.createElement("span", { id: "fad-elapsed" }, Spicetify.Player.formatTime(value)), + react.createElement("span", { id: "fad-elapsed" }, Spicetify.Player.formatTime(progress)), react.createElement( "div", - { id: "fad-progress" }, - react.createElement("div", { - id: "fad-progress-inner", + { + id: "fad-progress", + ref: progressDivRef, + onClick: handleClick, style: { - width: `${(value / duration) * 100}%`, + "--progress-width": `${thumbPosition}%`, }, - }) + }, + react.createElement( + "div", + { id: "fad-progress-inner" }, + react.createElement("div", { + id: "fad-thumb", + onMouseDown: handleMouseDown, + }) + ) ), react.createElement("span", { id: "fad-duration" }, Spicetify.Player.formatTime(duration)) ); @@ -421,6 +515,7 @@ body.video-full-screen.video-full-screen--hide-ui { artist: artistName || "", album: albumText || "", releaseDate: releaseDate || "", + heart: Spicetify.Player.getHeart(), }); return; } @@ -439,6 +534,7 @@ body.video-full-screen.video-full-screen--hide-ui { album: albumText || "", releaseDate: releaseDate || "", cover: bgImage, + heart: Spicetify.Player.getHeart(), }); }; this.currTrackImg.onerror = () => { From e7333a714c848465e59edeea77e156c2be2b9e9f Mon Sep 17 00:00:00 2001 From: MrBiscuit <122865263+MrBiscuit921@users.noreply.github.com> Date: Sun, 3 Nov 2024 08:09:42 +1300 Subject: [PATCH 075/146] style(trashbin): update used icon (#3212) --- Extensions/trashbin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extensions/trashbin.js b/Extensions/trashbin.js index 863ca41dee..a624bdcd10 100644 --- a/Extensions/trashbin.js +++ b/Extensions/trashbin.js @@ -166,7 +166,7 @@ settingsContent(); const trashbinIcon = - ''; + ''; const THROW_TEXT = "Place in Trashbin"; const UNTHROW_TEXT = "Remove from Trashbin"; From e3ec2ecc61e04f248ca185141eb7977ce3c44adb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 21:12:16 +0100 Subject: [PATCH 076/146] chore(deps): bump golang.org/x/net from 0.30.0 to 0.31.0 (#3218) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b350763334..f5c5a050ab 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.13 - golang.org/x/net v0.30.0 + golang.org/x/net v0.31.0 ) require ( github.com/mattn/go-isatty v0.0.16 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/sys v0.26.0 // indirect + golang.org/x/sys v0.27.0 // indirect ) diff --git a/go.sum b/go.sum index b8ce878284..03bf4555bb 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From b26a60e41dd4296ba337b58f68ec2b1de2b422cf Mon Sep 17 00:00:00 2001 From: Guillaume BOEHM Date: Tue, 26 Nov 2024 20:24:59 +0100 Subject: [PATCH 077/146] fix(new-releases): stop close button propagation (#3224) --- CustomApps/new-releases/Card.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CustomApps/new-releases/Card.js b/CustomApps/new-releases/Card.js index fe14f84c3f..8095c62abe 100644 --- a/CustomApps/new-releases/Card.js +++ b/CustomApps/new-releases/Card.js @@ -30,6 +30,8 @@ class Card extends react.Component { } closeButtonClicked(event) { + event.stopPropagation(); + removeCards(this.props.uri); Spicetify.Snackbar.enqueueCustomSnackbar @@ -55,8 +57,6 @@ class Card extends react.Component { }), }) : Spicetify.showNotification(`Dismissed ${this.title} from
${this.artist.name}`); - - event.stopPropagation(); } render() { From 631146a0d10b12f53c88cf5d84fd71f80bf30a7d Mon Sep 17 00:00:00 2001 From: TheGoldenPatrik1 <55322484+TheGoldenPatrik1@users.noreply.github.com> Date: Sun, 1 Dec 2024 13:43:38 +0000 Subject: [PATCH 078/146] fix(shuffle+): update GraphQL handling (#3229) --- Extensions/shuffle+.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Extensions/shuffle+.js b/Extensions/shuffle+.js index 86fb9942ef..0a4f07f8f8 100755 --- a/Extensions/shuffle+.js +++ b/Extensions/shuffle+.js @@ -338,7 +338,9 @@ if (errors) throw errors[0].message; if (data.albumUnion.playability.playable === false) throw "Album is not playable"; - return data.albumUnion.tracks.items.filter(({ track }) => track.playability.playable).map(({ track }) => (includeMetadata ? track : track.uri)); + return (data.albumUnion?.tracksV2 ?? data.albumUnion?.tracks ?? []).items + .filter(({ track }) => track.playability.playable) + .map(({ track }) => (includeMetadata ? track : track.uri)); } const artistFetchTypeCount = { album: 0, single: 0 }; @@ -368,14 +370,19 @@ } async function fetchArtistTracks(uri) { - const { queryArtistDiscographyAll } = Spicetify.GraphQL.Definitions; - // Definition from older Spotify version + // Definitions from older Spotify version const queryArtistOverview = { name: "queryArtistOverview", operation: "query", sha256Hash: "35648a112beb1794e39ab931365f6ae4a8d45e65396d641eeda94e4003d41497", value: null, }; + const queryArtistDiscographyAll = { + name: "queryArtistDiscographyAll", + operation: "query", + sha256Hash: "9380995a9d4663cbcb5113fef3c6aabf70ae6d407ba61793fd01e2a1dd6929b0", + value: null, + }; const discography = await Spicetify.GraphQL.Request(queryArtistDiscographyAll, { uri, From cf47f29268a97377a2a6ef280c34b013fa7d54ba Mon Sep 17 00:00:00 2001 From: ririxi Date: Sun, 1 Dec 2024 16:34:57 +0100 Subject: [PATCH 079/146] fix(update): use `GetSpotifyPath` from `cmd` --- spicetify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spicetify.go b/spicetify.go index 10cd793f5a..59de163f32 100644 --- a/spicetify.go +++ b/spicetify.go @@ -248,7 +248,7 @@ func main() { utils.PrintBold("spicetify v" + version) if slices.Contains(commands, "upgrade") || slices.Contains(commands, "update") { updateStatus := cmd.Update(version) - spotifyPath := filepath.Join(utils.FindAppPath(), "Apps") + spotifyPath := filepath.Join(cmd.GetSpotifyPath(), "Apps") ex, err := os.Executable() if err != nil { ex = "spicetify" From 5a3ba291a73093d111a874c9f97f6aa32a4a5720 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 18:44:11 +0100 Subject: [PATCH 080/146] chore(deps): bump actions/attest-build-provenance from 1 to 2 (#3235) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28fb52d0ec..5b561fc02d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,7 +77,7 @@ jobs: CGO_ENABLED: 0 - name: Attest output - uses: actions/attest-build-provenance@v1 + uses: actions/attest-build-provenance@v2 if: env.IS_UNIX == 'true' || env.IS_WIN == 'true' with: subject-path: "./spicetify${{ matrix.os == 'windows' && '.exe' || '' }}" From 440b6c9386cc9f6aa557135da86219ddd87eb475 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 18:44:24 +0100 Subject: [PATCH 081/146] chore(deps): bump golang.org/x/net from 0.31.0 to 0.32.0 (#3236) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index f5c5a050ab..b191f8b7dd 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.13 - golang.org/x/net v0.31.0 + golang.org/x/net v0.32.0 ) require ( github.com/mattn/go-isatty v0.0.16 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/sys v0.27.0 // indirect + golang.org/x/sys v0.28.0 // indirect ) diff --git a/go.sum b/go.sum index 03bf4555bb..ec91c7d5d9 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 4fd7841d584b530d18087923fc2dc32c38b9e82c Mon Sep 17 00:00:00 2001 From: ririxi Date: Thu, 19 Dec 2024 14:38:07 +0100 Subject: [PATCH 082/146] feat(css-map): add classes for `navLinks` in `1.2.53` --- css-map.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/css-map.json b/css-map.json index ac6fd35fc5..ffc58ba9a6 100644 --- a/css-map.json +++ b/css-map.json @@ -495,8 +495,10 @@ "jdlOKroADlFeZZQeTdp8": "main-globalNav-link-icon", "dIfr5oVr5kotAi0HsIsW": "main-globalNav-link-icon", "bWBqSiXEceAj1SnzqusU": "main-globalNav-navLink", + "obd_bH64Snp1npdw29XM": "main-globalNav-navLink", "voA9ZoTTlPFyLpckNw3S": "main-globalNav-navLinkActive", "ETjtwGvAB4lRVqSzm8nA": "main-globalNav-navLinkActive", + "AonZ39aVKATRTjY28Uww": "main-globalNav-navLinkActive", "QrpHSphgBSqzODEHqr_t": "main-globalNav-searchContainer", "lj0eGI6WEtfxFX7irC03": "main-globalNav-searchContainer", "fksI89zEXwqKWm1O6sJm": "main-globalNav-searchInputContainer", From 3f6c823599853d6ee8488977113a8de3ae39bffc Mon Sep 17 00:00:00 2001 From: ririxi Date: Thu, 19 Dec 2024 18:10:51 +0100 Subject: [PATCH 083/146] feat: add ability to change cors proxy server --- CustomApps/lyrics-plus/Settings.js | 29 +++++++++++++++++++++++++++++ jsHelper/spicetifyWrapper.js | 16 ++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CustomApps/lyrics-plus/Settings.js b/CustomApps/lyrics-plus/Settings.js index 4d4df0d18c..80109ec889 100644 --- a/CustomApps/lyrics-plus/Settings.js +++ b/CustomApps/lyrics-plus/Settings.js @@ -452,6 +452,22 @@ const ServiceList = ({ itemsList, onListChange = () => {}, onToggle = () => {}, }); }; +const corsProxyTemplate = () => { + const [proxyValue, setProxyValue] = react.useState(localStorage.getItem("spicetify:corsProxyTemplate") || "https://cors-proxy.spicetify.app/{url}"); + + return react.createElement("input", { + placeholder: "CORS Proxy Template", + value: proxyValue, + onChange: (event) => { + const value = event.target.value; + setProxyValue(value); + + if (value === "" || !value) return localStorage.removeItem("spicetify:corsProxyTemplate"); + localStorage.setItem("spicetify:corsProxyTemplate", value); + }, + }); +}; + const OptionList = ({ type, items, onChange }) => { const [itemList, setItemList] = useState(items); const [, forceUpdate] = useState(); @@ -646,6 +662,19 @@ function openConfig() { CONFIG.providers[name].token = value; localStorage.setItem(`${APP_NAME}:provider:${name}:token`, value); }, + }), + react.createElement("h2", null, "CORS Proxy Template"), + react.createElement("span", { + dangerouslySetInnerHTML: { + __html: + "Use this to bypass CORS restrictions. Replace the URL with your cors proxy server of your choice. {url} will be replaced with the request URL.", + }, + }), + react.createElement(corsProxyTemplate), + react.createElement("span", { + dangerouslySetInnerHTML: { + __html: "Spotify will reload its webview after applying. Leave empty to restore default: https://cors-proxy.spicetify.app/{url}", + }, }) ); diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 386e64ddd2..ee39422aac 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -352,7 +352,6 @@ window.Spicetify = { const _cosmos = Spicetify.Player.origin?._cosmos ?? Spicetify.Platform?.Registry.resolve(Symbol.for("Cosmos")); - const corsProxyURL = "https://cors-proxy.spicetify.app"; const allowedMethodsMap = { get: "get", post: "post", @@ -374,6 +373,7 @@ window.Spicetify = { return async function (url, body) { const urlObj = new URL(url); + const corsProxyURLTemplate = window.localStorage.getItem("spicetify:corsProxyTemplate") ?? "https://cors-proxy.spicetify.app/{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); @@ -396,10 +396,18 @@ window.Spicetify = { if (body) { if (method === "get") { const params = new URLSearchParams(body); - finalURL += `?${params.toString()}`; + const useSeparator = shouldUseCORSProxy && new URL(finalURL).search.startsWith("?"); + finalURL += `${useSeparator ? "&" : "?"}${params.toString()}`; } else options.body = !Array.isArray(body) && typeof body === "object" ? JSON.stringify(body) : body; } - if (shouldUseCORSProxy) finalURL = `${corsProxyURL}/${finalURL}`; + if (shouldUseCORSProxy) { + finalURL = corsProxyURLTemplate.replace(/{url}/, finalURL); + try { + new URL(finalURL); + } catch { + console.error("[spicetifyWrapper] Invalid CORS Proxy URL template"); + } + } const Authorization = `Bearer ${Spicetify.Platform.AuthorizationAPI.getState().token.accessToken}`; let injectedHeaders = {}; @@ -459,7 +467,7 @@ window.Spicetify = { () => { webpackDidCallback = true; }, - 6 + 12 ); let chunks = Object.entries(require.m); From fa1070dbde6e2360efbbfaab4861b76caaef51f9 Mon Sep 17 00:00:00 2001 From: ririxi Date: Thu, 19 Dec 2024 18:12:40 +0100 Subject: [PATCH 084/146] fix(wrapper): set `priority` to 1 in `OnChunksLoaded` --- jsHelper/spicetifyWrapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index ee39422aac..b9a58ae46d 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -467,7 +467,7 @@ window.Spicetify = { () => { webpackDidCallback = true; }, - 12 + 1 ); let chunks = Object.entries(require.m); From cd8808dd7b303a5d6b923186fa4e960b47a6a9fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 21:47:07 +0100 Subject: [PATCH 085/146] chore(deps): bump golang.org/x/net from 0.32.0 to 0.33.0 (#3242) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b191f8b7dd..4be4d5217c 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.13 - golang.org/x/net v0.32.0 + golang.org/x/net v0.33.0 ) require ( diff --git a/go.sum b/go.sum index ec91c7d5d9..54429604c0 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 39b8c75ec93b7948c90124761b48713229cb25fb Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 21 Dec 2024 17:46:05 +0100 Subject: [PATCH 086/146] fix: indefinitely wait for necessary chunks --- jsHelper/spicetifyWrapper.js | 65 ++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index b9a58ae46d..1df0334f53 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -673,6 +673,68 @@ window.Spicetify = { if (!Spicetify.ContextMenuV2._context) Spicetify.ContextMenuV2._context = Spicetify.React.createContext({}); + (function waitForChunks() { + const listOfComponents = [ + "ScrollableContainer", + "Slider", + "Toggle", + "Cards.Artist", + "Cards.Audiobook", + "Cards.Profile", + "Cards.Show", + "Cards.Track", + ]; + if (listOfComponents.every((component) => Spicetify.ReactComponent[component] !== undefined)) return; + const cache = Object.keys(require.m).map((id) => require(id)); + const modules = cache + .filter((module) => typeof module === "object") + .flatMap((module) => { + try { + return Object.values(module); + } catch {} + }); + const functionModules = modules.filter((module) => typeof module === "function"); + const cardTypesToFind = ["artist", "audiobook", "profile", "show", "track"]; + const cards = [ + ...functionModules + .flatMap((m) => { + return cardTypesToFind.map((type) => { + if (m.toString().includes(`featureIdentifier:"${type}"`)) { + cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1); + return [type[0].toUpperCase() + type.slice(1), m]; + } + }); + }) + .filter(Boolean), + ...modules + .flatMap((m) => { + return cardTypesToFind.map((type) => { + try { + if (m?.type?.toString().includes(`featureIdentifier:"${type}"`)) { + cardTypesToFind.splice(cardTypesToFind.indexOf(type), 1); + return [type[0].toUpperCase() + type.slice(1), m]; + } + } catch {} + }); + }) + .filter(Boolean), + ]; + + Spicetify.ReactComponent.Slider = wrapProvider(functionModules.find((m) => m.toString().includes("progressBarRef"))); + Spicetify.ReactComponent.Toggle = functionModules.find((m) => m.toString().includes("onSelected") && m.toString().includes('type:"checkbox"')); + Spicetify.ReactComponent.ScrollableContainer = functionModules.find( + (m) => m.toString().includes("scrollLeft") && m.toString().includes("showButtons") + ); + Object.assign(Spicetify.ReactComponent.Cards, Object.fromEntries(cards)); + + if (!listOfComponents.every((component) => Spicetify.ReactComponent[component] !== undefined)) { + setTimeout(waitForChunks, 100); + return; + } + + if (Spicetify.ReactComponent.ScrollableContainer) setTimeout(refreshNavLinks?.(), 100); + })(); + (function waitForSnackbar() { if (!Object.keys(Spicetify.Snackbar).length) { setTimeout(waitForSnackbar, 100); @@ -1845,10 +1907,13 @@ Spicetify._renderNavLinks = (list, isTouchScreenUi) => { const [refreshCount, refresh] = Spicetify.React.useReducer((x) => x + 1, 0); refreshNavLinks = refresh; + console.log(Spicetify.ReactComponent.ScrollableContainer); + if ( !Spicetify.ReactComponent.ButtonTertiary || !Spicetify.ReactComponent.Navigation || !Spicetify.ReactComponent.TooltipWrapper || + !Spicetify.ReactComponent.ScrollableContainer || !Spicetify.Platform.History || !Spicetify.Platform.LocalStorageAPI ) From 01f5cc5427627d83f08f16ad9fec9f1e69636532 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 21 Dec 2024 17:46:51 +0100 Subject: [PATCH 087/146] chore: remove console log --- jsHelper/spicetifyWrapper.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 1df0334f53..988ff2c874 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -1907,8 +1907,6 @@ Spicetify._renderNavLinks = (list, isTouchScreenUi) => { const [refreshCount, refresh] = Spicetify.React.useReducer((x) => x + 1, 0); refreshNavLinks = refresh; - console.log(Spicetify.ReactComponent.ScrollableContainer); - if ( !Spicetify.ReactComponent.ButtonTertiary || !Spicetify.ReactComponent.Navigation || From cfda93b30bcdab47dca26b5f0570dae8b0c8660d Mon Sep 17 00:00:00 2001 From: lunegh <89926504+lunegh@users.noreply.github.com> Date: Fri, 24 Jan 2025 19:36:38 +0100 Subject: [PATCH 088/146] fix(keyboardShortcut): change called function to skip song (#3273) --- Extensions/keyboardShortcut.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extensions/keyboardShortcut.js b/Extensions/keyboardShortcut.js index 80f367cac2..46f3712a59 100644 --- a/Extensions/keyboardShortcut.js +++ b/Extensions/keyboardShortcut.js @@ -55,7 +55,7 @@ "/": { callback: () => Spicetify.Platform.History.replace("/search") }, // CTRL + Arrow Left Next and CTRL + Arrow Right Previous Song - "ctrl+left": { callback: () => Spicetify.Player.prev() }, + "ctrl+left": { callback: () => Spicetify.Player.back() }, "ctrl+right": { callback: () => Spicetify.Player.next() }, // CTRL + Arrow Up Increase Volume CTRL + Arrow Down Decrease Volume From f1fc84243dc1df49c3deae5449ebbc3962b5f0a4 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 3 Feb 2025 20:09:20 +0100 Subject: [PATCH 089/146] fix: use new PlatformAPI in exp features --- jsHelper/expFeatures.js | 40 +++++++++++++++++++++++++++++----------- src/apply/apply.go | 8 ++++++++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/jsHelper/expFeatures.js b/jsHelper/expFeatures.js index a9475518d4..f8eff46ba3 100644 --- a/jsHelper/expFeatures.js +++ b/jsHelper/expFeatures.js @@ -132,16 +132,33 @@ (function waitForRemoteConfigResolver() { // Don't show options if hooks aren't patched/loaded - if (!hooksPatched || (!Spicetify.RemoteConfigResolver && !Spicetify.Platform?.RemoteConfiguration)) { + if (!hooksPatched || (!Spicetify.RemoteConfigResolver && !Spicetify.Platform?.RemoteConfigDebugAPI && !Spicetify.Platform?.RemoteConfiguration)) { setTimeout(waitForRemoteConfigResolver, 500); return; } let remoteConfiguration = Spicetify.RemoteConfigResolver?.value.remoteConfiguration || Spicetify.Platform?.RemoteConfiguration; - let setOverrides = () => {}; + const setOverrides = async (overrides) => { + if (Spicetify.Platform?.RemoteConfigDebugAPI) { + for (const [name, value] of Object.entries(overrides)) { + const feature = overrideList[name]; + const type = feature.values ? "enum" : typeof value === "number" ? "number" : "boolean"; + await Spicetify.Platform.RemoteConfigDebugAPI.setOverride( + { + source: "web", + type, + name, + }, + value + ); + } + } else if (Spicetify.RemoteConfigResolver?.value?.setOverrides) { + Spicetify.RemoteConfigResolver.value.setOverrides(Spicetify.createInternalMap?.(overrides)); + } + }; - (function waitForResolver() { - if (!Spicetify.RemoteConfigResolver) { + (async function waitForResolver() { + if (!Spicetify.RemoteConfigResolver && !Spicetify.Platform?.RemoteConfigDebugAPI) { isFallback = true; notice.innerText = "⚠️ Using fallback mode. Some features may not work."; setTimeout(waitForResolver, 500); @@ -149,8 +166,7 @@ } isFallback = false; notice.remove(); - remoteConfiguration = Spicetify.RemoteConfigResolver.value.remoteConfiguration; - setOverrides = Spicetify.RemoteConfigResolver.value.setOverrides; + remoteConfiguration = Spicetify?.RemoteConfigResolver?.remoteConfiguration ?? (await Spicetify.Platform?.RemoteConfigDebugAPI.getProperties()); })(); for (const key of Object.keys(overrideList)) { @@ -165,7 +181,7 @@ localStorage.setItem("spicetify-exp-features", JSON.stringify(overrideList)); featureMap[name] = value; - setOverrides(Spicetify.createInternalMap?.(featureMap)); + setOverrides({ [name]: value }); } function createSlider(name, desc, defaultVal) { @@ -254,8 +270,12 @@ ${Spicetify.SVGIcons.search} for (const name of Object.keys(overrideList)) { if (!prevSessionOverrideList.includes(name) && remoteConfiguration.values.has(name)) { - changeValue(name, remoteConfiguration.values.get(name)); - console.log(name, remoteConfiguration.values.get(name), overrideList[name]); + const currentValue = remoteConfiguration.values.get(name); + overrideList[name].value = currentValue; + localStorage.setItem("spicetify-exp-features", JSON.stringify(overrideList)); + + featureMap[name] = currentValue; + setOverrides({ [name]: currentValue }); } const feature = overrideList[name]; @@ -269,8 +289,6 @@ ${Spicetify.SVGIcons.search} } content.appendChild(resetButton); - - setOverrides(Spicetify.createInternalMap?.(featureMap)); })(); await new Promise((res) => Spicetify.Events.webpackLoaded.on(res)); diff --git a/src/apply/apply.go b/src/apply/apply.go index e6d9d4fc50..945f67c6bc 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -413,12 +413,20 @@ func insertExpFeatures(jsPath string, flags Flag) { return fmt.Sprintf("%s%s=Spicetify.expFeatureOverride(%s);%s", submatches[1], submatches[2], submatches[2], submatches[3]) }) + // utils.ReplaceOnce( + // &content, + // `(\w+\.fromJSON)(\s*=\s*function\b[^{]*{[^}]*})`, + // func(submatches ...string) string { + // return fmt.Sprintf("%s=Spicetify.createInternalMap%s", submatches[1], submatches[2]) + // }) + utils.ReplaceOnce( &content, `(([\w$.]+\.fromJSON)\(\w+\)+;)(return ?[\w{}().,]+[\w$]+\.Provider,)(\{value:\{localConfiguration)`, func(submatches ...string) string { return fmt.Sprintf("%sSpicetify.createInternalMap=%s;%sSpicetify.RemoteConfigResolver=%s", submatches[1], submatches[2], submatches[3], submatches[4]) }) + return content }) } From bc37b7cd427dd3d12be086b79813e25b78e4bb6f Mon Sep 17 00:00:00 2001 From: UnaTried <103455203+UnaTried@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:19:47 +0100 Subject: [PATCH 090/146] refactor: change folder location for `Backup` and `Extracted` (#3282) --- src/cmd/cmd.go | 4 ++-- src/utils/path-utils.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/cmd/cmd.go b/src/cmd/cmd.go index 377d6a858d..4f5e95febb 100644 --- a/src/cmd/cmd.go +++ b/src/cmd/cmd.go @@ -15,7 +15,7 @@ import ( var ( spicetifyFolder = utils.GetSpicetifyFolder() rawFolder, themedFolder = getExtractFolder() - backupFolder = utils.GetUserFolder("Backup") + backupFolder = utils.GetStateFolder("Backup") userThemesFolder = utils.GetUserFolder("Themes") quiet bool isAppX = false @@ -205,7 +205,7 @@ func GetSpotifyPath() string { } func getExtractFolder() (string, string) { - dir := utils.GetUserFolder("Extracted") + dir := utils.GetStateFolder("Extracted") raw := filepath.Join(dir, "Raw") utils.CheckExistAndCreate(raw) diff --git a/src/utils/path-utils.go b/src/utils/path-utils.go index 2105396367..f070d6c00e 100644 --- a/src/utils/path-utils.go +++ b/src/utils/path-utils.go @@ -65,6 +65,36 @@ func GetSpicetifyFolder() string { return result } +func GetStateFolder(name string) string { + result, isAvailable := os.LookupEnv("SPICETIFY_STATE") + defer func() { CheckExistAndCreate(result) }() + + if isAvailable && len(result) > 0 { + return result + } + + if runtime.GOOS == "windows" { + parent := os.Getenv("APPDATA") + + result = filepath.Join(parent, "spicetify") + } else if runtime.GOOS == "linux" { + parent, isAvailable := os.LookupEnv("XDG_STATE_HOME") + + if !isAvailable || len(parent) == 0 { + parent = filepath.Join(os.Getenv("HOME"), ".local", "state") + CheckExistAndCreate(parent) + } + + result = filepath.Join(parent, "spicetify") + } else if runtime.GOOS == "darwin" { + parent := filepath.Join(os.Getenv("HOME"), ".local", "state") + CheckExistAndCreate(parent) + + result = filepath.Join(parent, "spicetify") + } + return result +} + // getUserFolder checks if folder `name` is available in spicetifyFolder, // else creates then returns the path. func GetUserFolder(name string) string { From d22962fe68f4d60f3ca3053868ace64b3e6d4cc6 Mon Sep 17 00:00:00 2001 From: _eyewave <89079979+eye-wave@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:20:03 +0100 Subject: [PATCH 091/146] feat(trashbin): add a working export button (#3268) --- Extensions/trashbin.js | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Extensions/trashbin.js b/Extensions/trashbin.js index a624bdcd10..68d0ab34fa 100644 --- a/Extensions/trashbin.js +++ b/Extensions/trashbin.js @@ -71,7 +71,8 @@ header.innerText = "Local Storage"; content.appendChild(header); - content.appendChild(createButton("Export", "Copy all items in trashbin to clipboard, manually save to a .json file.", exportItems)); + content.appendChild(createButton("Copy", "Copy all items in trashbin to clipboard.", copyItems)); + content.appendChild(createButton("Export", "Save all items in trashbin to a .json file.", exportItems)); content.appendChild(createButton("Import", "Overwrite all items in trashbin via .json file.", importItems)); content.appendChild( createButton("Clear ", "Clear all items from trashbin (cannot be reverted).", () => { @@ -361,7 +362,7 @@ Spicetify.LocalStorage.set("TrashArtistList", JSON.stringify(trashArtistList)); } - function exportItems() { + function copyItems() { const data = { songs: trashSongList, artists: trashArtistList, @@ -370,6 +371,35 @@ Spicetify.showNotification("Copied to clipboard"); } + async function exportItems() { + const data = { + songs: trashSongList, + artists: trashArtistList, + }; + + try { + const handle = await window.showSaveFilePicker({ + suggestedName: "spicetify-trashbin.json", + types: [ + { + description: "Spicetify trashbin backup", + accept: { + "application/json": [".json"], + }, + }, + ], + }); + + const writable = await handle.createWritable(); + await writable.write(JSON.stringify(data)); + await writable.close(); + + Spicetify.showNotification("Backup saved succesfully."); + } catch { + Spicetify.showNotification("Failed to save, try copying trashbin contents to clipboard and creating a backup manually."); + } + } + function importItems() { const input = document.createElement("input"); input.type = "file"; From 7cb5498eb6670e3e3eb333c3119e1a901b6c9d98 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 3 Feb 2025 20:27:15 +0100 Subject: [PATCH 092/146] fix(GetStateFolder): put files in specific state folder --- src/cmd/cmd.go | 2 +- src/utils/path-utils.go | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/cmd/cmd.go b/src/cmd/cmd.go index 4f5e95febb..697ba1f772 100644 --- a/src/cmd/cmd.go +++ b/src/cmd/cmd.go @@ -16,7 +16,7 @@ var ( spicetifyFolder = utils.GetSpicetifyFolder() rawFolder, themedFolder = getExtractFolder() backupFolder = utils.GetStateFolder("Backup") - userThemesFolder = utils.GetUserFolder("Themes") + userThemesFolder = utils.GetSubFolder(spicetifyFolder, "Themes") quiet bool isAppX = false spotifyPath string diff --git a/src/utils/path-utils.go b/src/utils/path-utils.go index f070d6c00e..c158d66cea 100644 --- a/src/utils/path-utils.go +++ b/src/utils/path-utils.go @@ -92,20 +92,21 @@ func GetStateFolder(name string) string { result = filepath.Join(parent, "spicetify") } - return result + + return GetSubFolder(result, name) } -// getUserFolder checks if folder `name` is available in spicetifyFolder, +// GetSubFolder checks if folder `name` is available in specified folder, // else creates then returns the path. -func GetUserFolder(name string) string { - dir := filepath.Join(GetSpicetifyFolder(), name) +func GetSubFolder(folder string, name string) string { + dir := filepath.Join(folder, name) CheckExistAndCreate(dir) return dir } -var userAppsFolder = GetUserFolder("CustomApps") -var userExtensionsFolder = GetUserFolder("Extensions") +var userAppsFolder = GetSubFolder(GetSpicetifyFolder(), "CustomApps") +var userExtensionsFolder = GetSubFolder(GetSpicetifyFolder(), "Extensions") func GetCustomAppSubfolderPath(folderPath string) string { entries, err := os.ReadDir(folderPath) From 77455ba174dca0bb839546f52c9e4690a27a1b64 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 3 Feb 2025 20:54:41 +0100 Subject: [PATCH 093/146] fix(homeConfig): make it work on Spotify `1.2.56` --- jsHelper/homeConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsHelper/homeConfig.js b/jsHelper/homeConfig.js index 59fc1db80d..feebf9f903 100644 --- a/jsHelper/homeConfig.js +++ b/jsHelper/homeConfig.js @@ -92,7 +92,7 @@ SpicetifyHomeConfig = {}; const main = document.querySelector(".main-home-content"); elem = [...main.querySelectorAll("section")]; for (const [index, item] of elem.entries()) { - item.dataset.uri = list[index].uri ?? list[index].item.uri; + item.dataset.uri = list[index]?.uri ?? list[index].item?.uri; } function appendItems() { From 51c4bc51ef32d1adf9f90716a6c2c4cbfec9e3eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 02:06:08 +0100 Subject: [PATCH 094/146] chore(deps): bump golang.org/x/net from 0.33.0 to 0.34.0 (#3265) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4be4d5217c..c911100cd2 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.13 - golang.org/x/net v0.33.0 + golang.org/x/net v0.34.0 ) require ( github.com/mattn/go-isatty v0.0.16 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect ) diff --git a/go.sum b/go.sum index 54429604c0..bf87579320 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 7de3462298de4d9ad265170836d02c1bfe39161f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 02:08:10 +0100 Subject: [PATCH 095/146] chore(deps): bump github.com/mattn/go-colorable from 0.1.13 to 0.1.14 (#3267) --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index c911100cd2..259ffdd687 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,12 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 - github.com/mattn/go-colorable v0.1.13 + github.com/mattn/go-colorable v0.1.14 golang.org/x/net v0.34.0 ) require ( - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.7.1 // indirect golang.org/x/sys v0.29.0 // indirect ) diff --git a/go.sum b/go.sum index bf87579320..2e3c8436a2 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -13,7 +13,7 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 7f649dae6f1a04d6a7c76c381da8a436d6990576 Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 5 Feb 2025 16:07:03 +0100 Subject: [PATCH 096/146] feat: migrate state folders --- spicetify.go | 1 + src/utils/path-utils.go | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/spicetify.go b/spicetify.go index 59de163f32..1a6ee47b4f 100644 --- a/spicetify.go +++ b/spicetify.go @@ -111,6 +111,7 @@ func init() { } utils.MigrateConfigFolder() + utils.MigrateFolders() cmd.InitConfig(quiet) if len(commands) < 1 { diff --git a/src/utils/path-utils.go b/src/utils/path-utils.go index c158d66cea..fbb259e4d6 100644 --- a/src/utils/path-utils.go +++ b/src/utils/path-utils.go @@ -24,6 +24,52 @@ func MigrateConfigFolder() { } } +func MigrateFolders() { + backupPath := filepath.Join(GetSpicetifyFolder(), "Backup") + extractedPath := filepath.Join(GetSpicetifyFolder(), "Extracted") + + if _, err := os.Stat(backupPath); err == nil { + newBackupPath := GetStateFolder("Backup") + oldAbs, err := filepath.Abs(backupPath) + if err != nil { + Fatal(err) + } + newAbs, err := filepath.Abs(newBackupPath) + if err != nil { + Fatal(err) + } + + if oldAbs != newAbs { + PrintBold("Migrating spicetify state (Backup, Extracted) folders") + err := Copy(backupPath, newBackupPath, true, nil) + if err != nil { + Fatal(err) + } + os.RemoveAll(backupPath) + } + } + + if _, err := os.Stat(extractedPath); err == nil { + newExtractedPath := GetStateFolder("Extracted") + oldAbs, err := filepath.Abs(extractedPath) + if err != nil { + Fatal(err) + } + newAbs, err := filepath.Abs(newExtractedPath) + if err != nil { + Fatal(err) + } + if oldAbs != newAbs { + PrintBold("Migrating spicetify state (Backup, Extracted) folders") + err := Copy(extractedPath, newExtractedPath, true, nil) + if err != nil { + Fatal(err) + } + os.RemoveAll(extractedPath) + } + } +} + func ReplaceEnvVarsInString(input string) string { var replacements []string for _, v := range os.Environ() { From 741bcc112b81d4de8a7c1d872bab32abb21c247c Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 5 Feb 2025 23:50:09 +0100 Subject: [PATCH 097/146] fix(expFeatures): make it work on versions without `RemoteConfigDebugAPI` --- jsHelper/expFeatures.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jsHelper/expFeatures.js b/jsHelper/expFeatures.js index f8eff46ba3..a7e6f78f00 100644 --- a/jsHelper/expFeatures.js +++ b/jsHelper/expFeatures.js @@ -166,7 +166,8 @@ } isFallback = false; notice.remove(); - remoteConfiguration = Spicetify?.RemoteConfigResolver?.remoteConfiguration ?? (await Spicetify.Platform?.RemoteConfigDebugAPI.getProperties()); + remoteConfiguration = + Spicetify?.RemoteConfigResolver.value?.remoteConfiguration ?? (await Spicetify.Platform?.RemoteConfigDebugAPI.getProperties()); })(); for (const key of Object.keys(overrideList)) { From 923669e79ba6d14b57574f48d624d8b740cefc09 Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 5 Feb 2025 23:50:27 +0100 Subject: [PATCH 098/146] feat(wrapper): add smooth scrolling for Spotify 1.2.56 --- jsHelper/spicetifyWrapper.js | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 988ff2c874..aa68a5fec1 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -306,6 +306,51 @@ window.Spicetify = { Platform: {}, }; +// Based on https://blog.aziz.tn/2025/01/spotify-fix-lagging-issue-on-scrolling.html +function applyScrollingFix() { + const scrollableElements = Array.from(document.querySelectorAll("*")).filter((el) => { + if ( + el.id === "context-menu" || + el.closest("#context-menu") || + el.getAttribute("role") === "dialog" || + el.classList.contains("popup") || + el.getAttribute("aria-haspopup") === "true" + ) + return false; + + const style = window.getComputedStyle(el); + return style.overflow === "auto" || style.overflow === "scroll" || style.overflowY === "auto" || style.overflowY === "scroll"; + }); + + for (const el of scrollableElements) { + if (!el.hasAttribute("data-scroll-optimized")) { + el.style.willChange = "transform"; + el.style.transform = "translate3d(0, 0, 0)"; + el.setAttribute("data-scroll-optimized", "true"); + } + } +} + +const observer = new MutationObserver(applyScrollingFix); + +observer.observe(document.body, { + childList: true, + subtree: true, + attributes: false, +}); + +const originalPushState = history.pushState; +history.pushState = function (...args) { + originalPushState.apply(this, args); + setTimeout(applyScrollingFix, 100); +}; + +window.addEventListener("popstate", () => { + setTimeout(applyScrollingFix, 100); +}); + +applyScrollingFix(); + (function waitForPlatform() { if (!Spicetify._platform) { setTimeout(waitForPlatform, 50); From 7a53725144472b7e66bf2c3d0a0082715847ea48 Mon Sep 17 00:00:00 2001 From: ririxi Date: Thu, 6 Feb 2025 00:14:30 +0100 Subject: [PATCH 099/146] fix(homeConfig): make the regex work on more versions --- src/apply/apply.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apply/apply.go b/src/apply/apply.go index 945f67c6bc..7c424ba2aa 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -361,10 +361,10 @@ func insertHomeConfig(jsPath string, flags Flag) { return fmt.Sprintf("%sSpicetifyHomeConfig.arrange(%s)%s", submatches[1], submatches[2], submatches[3]) }) - // >= 1.2.45 + // >= 1.2.40 utils.ReplaceOnce( &content, - `(&&"HomeShortsSectionData".*\],)([a-zA-Z])(\}\)\()`, + `(&&"HomeShortsSectionData".*?[\],}])([a-zA-Z])(\}\)?\()`, func(submatches ...string) string { return fmt.Sprintf("%sSpicetifyHomeConfig.arrange(%s)%s", submatches[1], submatches[2], submatches[3]) }) From f345385b36294181fa26ed580d2c6000a9824bbc Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 8 Feb 2025 14:46:15 +0100 Subject: [PATCH 100/146] feat: patch only `xpui.js` on Spotify `1.2.57` and higher --- src/apply/apply.go | 22 +++++++++++++++++++--- src/cmd/backup.go | 3 ++- src/preprocess/preprocess.go | 20 +++++++++++++++++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/apply/apply.go b/src/apply/apply.go index 7c424ba2aa..2d910f62e0 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "github.com/spicetify/cli/src/utils" @@ -36,9 +37,6 @@ func AdditionalOptions(appsFolderPath string, flags Flag) { insertSidebarConfig, insertHomeConfig, }, - filepath.Join(appsFolderPath, "xpui", "vendor~xpui.js"): { - insertExpFeatures, - }, filepath.Join(appsFolderPath, "xpui", "home-v2.js"): { insertHomeConfig, }, @@ -47,6 +45,24 @@ func AdditionalOptions(appsFolderPath string, flags Flag) { }, } + verParts := strings.Split(flags.SpotifyVer, ".") + spotifyMajor, spotifyMinor, spotifyPatch := 0, 0, 0 + if len(verParts) > 0 { + spotifyMajor, _ = strconv.Atoi(verParts[0]) + } + if len(verParts) > 1 { + spotifyMinor, _ = strconv.Atoi(verParts[1]) + } + if len(verParts) > 2 { + spotifyPatch, _ = strconv.Atoi(verParts[2]) + } + + if spotifyMajor >= 1 && spotifyMinor >= 2 && spotifyPatch >= 57 { + filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")] = append(filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")], insertExpFeatures) + } else { + filesToModified[filepath.Join(appsFolderPath, "xpui", "vendor~xpui.js")] = []func(string, Flag){insertExpFeatures} + } + for file, calls := range filesToModified { if _, err := os.Stat(file); os.IsNotExist(err) { continue diff --git a/src/cmd/backup.go b/src/cmd/backup.go index 4c20e921c7..aaaed323d7 100644 --- a/src/cmd/backup.go +++ b/src/cmd/backup.go @@ -72,7 +72,8 @@ Modded Spotify cannot be launched using original Shortcut/Start menu tile. To co DisableSentry: preprocSection.Key("disable_sentry").MustBool(false), DisableLogging: preprocSection.Key("disable_ui_logging").MustBool(false), RemoveRTL: preprocSection.Key("remove_rtl_rule").MustBool(false), - ExposeAPIs: preprocSection.Key("expose_apis").MustBool(false)}, + ExposeAPIs: preprocSection.Key("expose_apis").MustBool(false), + SpotifyVer: utils.GetSpotifyVersion(prefsPath)}, ) utils.PrintGreen("OK") diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 6ea27620ef..dd1cc2e872 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -26,6 +26,7 @@ type Flag struct { RemoveRTL bool // ExposeAPIs leaks some Spotify's API, functions, objects to Spicetify global object. ExposeAPIs bool + SpotifyVer string } func readRemoteCssMap(tag string, cssTranslationMap *map[string]string) error { @@ -82,6 +83,18 @@ func Start(version string, extractedAppsPath string, flags Flag) { readLocalCssMap(&cssTranslationMap) } + verParts := strings.Split(flags.SpotifyVer, ".") + spotifyMajor, spotifyMinor, spotifyPatch := 0, 0, 0 + if len(verParts) > 0 { + spotifyMajor, _ = strconv.Atoi(verParts[0]) + } + if len(verParts) > 1 { + spotifyMinor, _ = strconv.Atoi(verParts[1]) + } + if len(verParts) > 2 { + spotifyPatch, _ = strconv.Atoi(verParts[2]) + } + filepath.Walk(appPath, func(path string, info os.FileInfo, err error) error { fileName := info.Name() extension := filepath.Ext(fileName) @@ -101,8 +114,13 @@ func Start(version string, extractedAppsPath string, flags Flag) { switch fileName { case "xpui.js": content = exposeAPIs_main(content) + if spotifyMajor >= 1 && spotifyMinor >= 2 && spotifyPatch >= 57 { + content = exposeAPIs_vendor(content) + } case "vendor~xpui.js": - content = exposeAPIs_vendor(content) + if spotifyMajor < 1 && spotifyMinor < 2 && spotifyPatch < 57 { + content = exposeAPIs_vendor(content) + } } } for k, v := range cssTranslationMap { From e5f9e987e147fe931e1fa6184fc489a9ca10f744 Mon Sep 17 00:00:00 2001 From: Ian P Date: Sat, 8 Feb 2025 20:49:30 +0700 Subject: [PATCH 101/146] feat(lyrics-plus): add select language translation for Musixmatch provider (#3288) --- CustomApps/lyrics-plus/OptionsMenu.js | 10 ++++++++-- CustomApps/lyrics-plus/ProviderMusixmatch.js | 11 ++++++++-- CustomApps/lyrics-plus/Settings.js | 21 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/CustomApps/lyrics-plus/OptionsMenu.js b/CustomApps/lyrics-plus/OptionsMenu.js index 30e986599d..9283eac16e 100644 --- a/CustomApps/lyrics-plus/OptionsMenu.js +++ b/CustomApps/lyrics-plus/OptionsMenu.js @@ -100,12 +100,18 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { ko: "Korean", }; - let modeOptions = {}; + let modeOptions = { + none: "None", + }; if (hasTranslation.musixmatch) { + const selectedLanguage = CONFIG.visual["musixmatch-translation-language"]; + const languageName = new Intl.DisplayNames([selectedLanguage], { + type: "language", + }).of(selectedLanguage); sourceOptions = { ...sourceOptions, - musixmatchTranslation: "English (Musixmatch)", + musixmatchTranslation: `${languageName} (Musixmatch)`, }; } diff --git a/CustomApps/lyrics-plus/ProviderMusixmatch.js b/CustomApps/lyrics-plus/ProviderMusixmatch.js index bee377b9a5..92db4dfbcd 100644 --- a/CustomApps/lyrics-plus/ProviderMusixmatch.js +++ b/CustomApps/lyrics-plus/ProviderMusixmatch.js @@ -162,11 +162,15 @@ const ProviderMusixmatch = (() => { const track_id = body?.["matcher.track.get"]?.message?.body?.track?.track_id; if (!track_id) return null; + const selectedLanguage = CONFIG.visual["musixmatch-translation-language"] || "none"; + if (selectedLanguage === "none") return null; + const baseURL = - "https://apic-desktop.musixmatch.com/ws/1.1/crowd.track.translations.get?translation_fields_set=minimal&selected_language=en&comment_format=text&format=json&app_id=web-desktop-app-v1.0&"; + "https://apic-desktop.musixmatch.com/ws/1.1/crowd.track.translations.get?translation_fields_set=minimal&comment_format=text&format=json&app_id=web-desktop-app-v1.0&"; const params = { track_id, + selected_language: selectedLanguage, usertoken: CONFIG.providers.musixmatch.token, }; @@ -184,7 +188,10 @@ const ProviderMusixmatch = (() => { if (!result.translations_list?.length) return null; - return result.translations_list.map(({ translation }) => ({ translation: translation.description, matchedLine: translation.matched_line })); + return result.translations_list.map(({ translation }) => ({ + translation: translation.description, + matchedLine: translation.matched_line, + })); } return { findLyrics, getKaraoke, getSynced, getUnsynced, getTranslation }; diff --git a/CustomApps/lyrics-plus/Settings.js b/CustomApps/lyrics-plus/Settings.js index 80109ec889..e5e3a037ec 100644 --- a/CustomApps/lyrics-plus/Settings.js +++ b/CustomApps/lyrics-plus/Settings.js @@ -512,6 +512,19 @@ const OptionList = ({ type, items, onChange }) => { ); }); }; +const languageCodes = + "none,en,af,ar,bg,bn,ca,zh,cs,da,de,el,es,et,fa,fi,fr,gu,he,hi,hr,hu,id,is,it,ja,jv,kn,ko,lt,lv,ml,mr,ms,nl,no,pl,pt,ro,ru,sk,sl,sr,su,sv,ta,te,th,tr,uk,ur,vi,zu".split( + "," + ); + +const displayNames = new Intl.DisplayNames(["en"], { type: "language" }); +const languageOptions = languageCodes.reduce((acc, code) => { + acc[code] = code === "none" ? "None" : displayNames.of(code); + return acc; +}, {}); + +const savedLanguage = localStorage.getItem(`${APP_NAME}:visual:musixmatch-translation-language`) || "none"; +CONFIG.visual["musixmatch-translation-language"] = savedLanguage; function openConfig() { const configContainer = react.createElement( @@ -630,6 +643,14 @@ function openConfig() { max: thresholdSizeLimit.max, step: thresholdSizeLimit.step, }, + { + desc: "Musixmatch Translation Language.", + info: "Choose the language you want to translate the lyrics to. Changes will take effect after the next track.", + key: "musixmatch-translation-language", + type: ConfigSelection, + options: languageOptions, + defaultValue: savedLanguage, + }, ], onChange: (name, value) => { CONFIG.visual[name] = value; From afd98f4f5b9bf11a4c15cb3b147a3fa32353778a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9as=20Leroux?= <72009553+AndreasLrx@users.noreply.github.com> Date: Sat, 8 Feb 2025 14:50:03 +0100 Subject: [PATCH 102/146] feat(lyrics-plus): allow to change display of lyrics translation (#3297) --- CustomApps/lyrics-plus/OptionsMenu.js | 15 ++ CustomApps/lyrics-plus/Pages.js | 198 +++++++++++++++++++------- CustomApps/lyrics-plus/index.js | 1 + 3 files changed, 159 insertions(+), 55 deletions(-) diff --git a/CustomApps/lyrics-plus/OptionsMenu.js b/CustomApps/lyrics-plus/OptionsMenu.js index 9283eac16e..46e2aaae49 100644 --- a/CustomApps/lyrics-plus/OptionsMenu.js +++ b/CustomApps/lyrics-plus/OptionsMenu.js @@ -92,6 +92,13 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { none: "None", }; + const savedTranslationDisplay = localStorage.getItem(`${APP_NAME}:visual:translate:display-mode`) || "replace"; + CONFIG.visual["translate:display-mode"] = savedTranslationDisplay; + const translationDisplayOptions = { + replace: "Replace", + below: "Below", + }; + const languageOptions = { off: "Off", "zh-hans": "Chinese (Simplified)", @@ -157,6 +164,14 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { options: sourceOptions, renderInline: true, }, + { + desc: "Display", + key: "translate:display-mode", + type: ConfigSelection, + options: translationDisplayOptions, + defaultValue: savedTranslationDisplay, + renderInline: true, + }, { desc: "Language Override", key: "translate:detect-language-override", diff --git a/CustomApps/lyrics-plus/Pages.js b/CustomApps/lyrics-plus/Pages.js index 5f99b25156..3bf7adcc38 100755 --- a/CustomApps/lyrics-plus/Pages.js +++ b/CustomApps/lyrics-plus/Pages.js @@ -140,7 +140,7 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara }, key: lyricsId, }, - activeLines.map(({ text, lineNumber, startTime }, i) => { + activeLines.map(({ text, lineNumber, startTime, originalText }, i) => { if (i === 1 && activeLineIndex === 1) { return react.createElement(IdlingIndicator, { progress: position / activeLines[2].startTime, @@ -169,33 +169,68 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara if (paddingLine) { className += " lyrics-lyricsContainer-LyricsLine-paddingLine"; } + const showTranslatedBelow = CONFIG.visual["translate:display-mode"] === "below"; + // If we have original text and we are showing translated below, we should show the original text + // Otherwise we should show the translated text + const lineText = originalText && showTranslatedBelow ? originalText : text; return react.createElement( - "p", + "div", { - className, - style: { - cursor: "pointer", - "--position-index": animationIndex, - "--animation-index": (animationIndex < 0 ? 0 : animationIndex) + 1, - "--blur-index": Math.abs(animationIndex), - }, - key: lineNumber, - dir: "auto", - ref, - onClick: (event) => { - if (startTime) { - Spicetify.Player.seek(startTime); - } - }, - onContextMenu: (event) => { - event.preventDefault(); - Spicetify.Platform.ClipboardAPI.copy(rawLyrics) - .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) - .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - }, + className: "lyrics-lyricsContainer-LyricsLineContainer", + key: i, }, - !isKara ? text : react.createElement(KaraokeLine, { text, startTime, position, isActive }) + react.createElement( + "p", + { + className, + style: { + cursor: "pointer", + "--position-index": animationIndex, + "--animation-index": (animationIndex < 0 ? 0 : animationIndex) + 1, + "--blur-index": Math.abs(animationIndex), + }, + key: lineNumber, + dir: "auto", + ref, + onClick: (event) => { + if (startTime) { + Spicetify.Player.seek(startTime); + } + }, + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(rawLyrics) + .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); + }, + }, + !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive }) + ), + showTranslatedBelow && + originalText && + originalText !== text && + react.createElement( + "p", + { + className, + style: { + opacity: 0.5, + cursor: "pointer", + "--position-index": animationIndex, + "--animation-index": (animationIndex < 0 ? 0 : animationIndex) + 1, + "--blur-index": Math.abs(animationIndex), + }, + dir: "auto", + ref, + onClick: (event) => { + if (startTime) { + Spicetify.Player.seek(startTime); + } + }, + }, + text + ) ); }) ), @@ -412,7 +447,7 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa react.createElement("p", { className: "lyrics-lyricsContainer-LyricsUnsyncedPadding", }), - padded.map(({ text, startTime }, i) => { + padded.map(({ text, startTime, originalText }, i) => { if (i === 0) { return react.createElement(IdlingIndicator, { isActive: activeLineIndex === 0, @@ -422,28 +457,57 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa } const isActive = i === activeLineIndex; + const showTranslatedBelow = CONFIG.visual["translate:display-mode"] === "below"; + // If we have original text and we are showing translated below, we should show the original text + // Otherwise we should show the translated text + const lineText = originalText && showTranslatedBelow ? originalText : text; return react.createElement( - "p", + "div", { - className: `lyrics-lyricsContainer-LyricsLine${i <= activeLineIndex ? " lyrics-lyricsContainer-LyricsLine-active" : ""}`, - style: { - cursor: "pointer", - }, - dir: "auto", - ref: isActive ? activeLineRef : null, - onClick: (event) => { - if (startTime) { - Spicetify.Player.seek(startTime); - } - }, - onContextMenu: (event) => { - event.preventDefault(); - Spicetify.Platform.ClipboardAPI.copy(rawLyrics) - .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) - .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - }, + className: "lyrics-lyricsContainer-LyricsLineContainer", + key: i, }, - !isKara ? text : react.createElement(KaraokeLine, { text, startTime, position, isActive }) + react.createElement( + "p", + { + className: `lyrics-lyricsContainer-LyricsLine${i <= activeLineIndex ? " lyrics-lyricsContainer-LyricsLine-active" : ""}`, + style: { + cursor: "pointer", + }, + dir: "auto", + ref: isActive ? activeLineRef : null, + onClick: (event) => { + if (startTime) { + Spicetify.Player.seek(startTime); + } + }, + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(rawLyrics) + .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); + }, + }, + !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive }) + ), + showTranslatedBelow && + originalText && + originalText !== text && + react.createElement( + "p", + { + className: `lyrics-lyricsContainer-LyricsLine${i <= activeLineIndex ? " lyrics-lyricsContainer-LyricsLine-active" : ""}`, + style: { opacity: 0.5, cursor: "pointer" }, + dir: "auto", + ref: isActive ? activeLineRef : null, + onClick: (event) => { + if (startTime) { + Spicetify.Player.seek(startTime); + } + }, + }, + text + ) ); }), react.createElement("p", { @@ -468,20 +532,44 @@ const UnsyncedLyricsPage = react.memo(({ lyrics, provider, copyright }) => { react.createElement("p", { className: "lyrics-lyricsContainer-LyricsUnsyncedPadding", }), - lyrics.map(({ text }) => { + lyrics.map(({ text, originalText }, index) => { + const showTranslatedBelow = CONFIG.visual["translate:display-mode"] === "below"; + // If we have original text and we are showing translated below, we should show the original text + // Otherwise we should show the translated text + const lineText = originalText && showTranslatedBelow ? originalText : text; + return react.createElement( - "p", + "div", { - className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", - dir: "auto", - onContextMenu: (event) => { - event.preventDefault(); - Spicetify.Platform.ClipboardAPI.copy(rawLyrics) - .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) - .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - }, + className: "lyrics-lyricsContainer-LyricsLineContainer", + key: index, }, - text + react.createElement( + "p", + { + className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", + dir: "auto", + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(rawLyrics) + .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); + }, + }, + lineText + ), + showTranslatedBelow && + originalText && + originalText !== text && + react.createElement( + "p", + { + className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", + style: { opacity: 0.5 }, + dir: "auto", + }, + text + ) ); }), react.createElement("p", { diff --git a/CustomApps/lyrics-plus/index.js b/CustomApps/lyrics-plus/index.js index f703e16a35..52ec96e6cb 100644 --- a/CustomApps/lyrics-plus/index.js +++ b/CustomApps/lyrics-plus/index.js @@ -41,6 +41,7 @@ const CONFIG = { "lines-after": localStorage.getItem("lyrics-plus:visual:lines-after") || "2", "font-size": localStorage.getItem("lyrics-plus:visual:font-size") || "32", "translate:translated-lyrics-source": localStorage.getItem("lyrics-plus:visual:translate:translated-lyrics-source") || "none", + "translate:display-mode": localStorage.getItem("lyrics-plus:visual:translate:display-mode") || "replace", "translate:detect-language-override": localStorage.getItem("lyrics-plus:visual:translate:detect-language-override") || "off", "translation-mode:japanese": localStorage.getItem("lyrics-plus:visual:translation-mode:japanese") || "furigana", "translation-mode:korean": localStorage.getItem("lyrics-plus:visual:translation-mode:korean") || "hangul", From 0ab90c27f91503df077ae0aa1b9d9b6b81a54d8a Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 8 Feb 2025 15:10:59 +0100 Subject: [PATCH 103/146] fix: re-render lyrics div when `Translation Display` is changed --- CustomApps/lyrics-plus/OptionsMenu.js | 7 ++++++- jsHelper/expFeatures.js | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CustomApps/lyrics-plus/OptionsMenu.js b/CustomApps/lyrics-plus/OptionsMenu.js index 46e2aaae49..5c5ace7740 100644 --- a/CustomApps/lyrics-plus/OptionsMenu.js +++ b/CustomApps/lyrics-plus/OptionsMenu.js @@ -165,9 +165,14 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { renderInline: true, }, { - desc: "Display", + desc: "Translation Display", key: "translate:display-mode", type: ConfigSelection, + onChange: (name, value) => { + CONFIG.visual[name] = value; + localStorage.setItem(`${APP_NAME}:visual:${name}`, value); + lyricContainerUpdate?.(); + }, options: translationDisplayOptions, defaultValue: savedTranslationDisplay, renderInline: true, diff --git a/jsHelper/expFeatures.js b/jsHelper/expFeatures.js index a7e6f78f00..2b5ea279e9 100644 --- a/jsHelper/expFeatures.js +++ b/jsHelper/expFeatures.js @@ -167,7 +167,7 @@ isFallback = false; notice.remove(); remoteConfiguration = - Spicetify?.RemoteConfigResolver.value?.remoteConfiguration ?? (await Spicetify.Platform?.RemoteConfigDebugAPI.getProperties()); + Spicetify?.RemoteConfigResolver?.value.remoteConfiguration ?? (await Spicetify.Platform?.RemoteConfigDebugAPI.getProperties()); })(); for (const key of Object.keys(overrideList)) { From 4b4f76f289bca98c404ba1983abd1bc3c3c24db5 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 8 Feb 2025 15:11:26 +0100 Subject: [PATCH 104/146] chore(lyrics-plus/OptionsMenu): change displayed text --- CustomApps/lyrics-plus/OptionsMenu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CustomApps/lyrics-plus/OptionsMenu.js b/CustomApps/lyrics-plus/OptionsMenu.js index 5c5ace7740..ba06987243 100644 --- a/CustomApps/lyrics-plus/OptionsMenu.js +++ b/CustomApps/lyrics-plus/OptionsMenu.js @@ -95,8 +95,8 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { const savedTranslationDisplay = localStorage.getItem(`${APP_NAME}:visual:translate:display-mode`) || "replace"; CONFIG.visual["translate:display-mode"] = savedTranslationDisplay; const translationDisplayOptions = { - replace: "Replace", - below: "Below", + replace: "Replace original", + below: "Below original", }; const languageOptions = { From c3485db5542b4527fff84039c0286f00e2ed6316 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 8 Feb 2025 15:52:56 +0100 Subject: [PATCH 105/146] fix(preprocess): remove checking spotify ver --- src/preprocess/preprocess.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index dd1cc2e872..2551f66040 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -118,9 +118,7 @@ func Start(version string, extractedAppsPath string, flags Flag) { content = exposeAPIs_vendor(content) } case "vendor~xpui.js": - if spotifyMajor < 1 && spotifyMinor < 2 && spotifyPatch < 57 { - content = exposeAPIs_vendor(content) - } + content = exposeAPIs_vendor(content) } } for k, v := range cssTranslationMap { From 048bc9d092676e8f66da6f74ec78b276c07c2fe1 Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Mon, 10 Feb 2025 19:55:31 +0900 Subject: [PATCH 106/146] fix(lyrics-plus): fix lyrics transition effect issue (#3305) --- CustomApps/lyrics-plus/Pages.js | 141 +++++++++++--------------------- 1 file changed, 50 insertions(+), 91 deletions(-) diff --git a/CustomApps/lyrics-plus/Pages.js b/CustomApps/lyrics-plus/Pages.js index 3bf7adcc38..8ba19515eb 100755 --- a/CustomApps/lyrics-plus/Pages.js +++ b/CustomApps/lyrics-plus/Pages.js @@ -177,56 +177,37 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara return react.createElement( "div", { - className: "lyrics-lyricsContainer-LyricsLineContainer", - key: i, - }, - react.createElement( - "p", - { - className, - style: { - cursor: "pointer", - "--position-index": animationIndex, - "--animation-index": (animationIndex < 0 ? 0 : animationIndex) + 1, - "--blur-index": Math.abs(animationIndex), - }, - key: lineNumber, - dir: "auto", - ref, - onClick: (event) => { - if (startTime) { - Spicetify.Player.seek(startTime); - } - }, - onContextMenu: (event) => { - event.preventDefault(); - Spicetify.Platform.ClipboardAPI.copy(rawLyrics) - .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) - .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - }, + className, + style: { + cursor: "pointer", + "--position-index": animationIndex, + "--animation-index": (animationIndex < 0 ? 0 : animationIndex) + 1, + "--blur-index": Math.abs(animationIndex), + }, + dir: "auto", + ref, + key: lineNumber, + onClick: (event) => { + if (startTime) { + Spicetify.Player.seek(startTime); + } + }, + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(rawLyrics) + .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); }, - !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive }) - ), + }, + react.createElement("p", {}, !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive })), showTranslatedBelow && originalText && originalText !== text && react.createElement( "p", { - className, style: { opacity: 0.5, - cursor: "pointer", - "--position-index": animationIndex, - "--animation-index": (animationIndex < 0 ? 0 : animationIndex) + 1, - "--blur-index": Math.abs(animationIndex), - }, - dir: "auto", - ref, - onClick: (event) => { - if (startTime) { - Spicetify.Player.seek(startTime); - } }, }, text @@ -464,47 +445,33 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa return react.createElement( "div", { - className: "lyrics-lyricsContainer-LyricsLineContainer", + className: `lyrics-lyricsContainer-LyricsLine${i <= activeLineIndex ? " lyrics-lyricsContainer-LyricsLine-active" : ""}`, key: i, - }, - react.createElement( - "p", - { - className: `lyrics-lyricsContainer-LyricsLine${i <= activeLineIndex ? " lyrics-lyricsContainer-LyricsLine-active" : ""}`, - style: { - cursor: "pointer", - }, - dir: "auto", - ref: isActive ? activeLineRef : null, - onClick: (event) => { - if (startTime) { - Spicetify.Player.seek(startTime); - } - }, - onContextMenu: (event) => { - event.preventDefault(); - Spicetify.Platform.ClipboardAPI.copy(rawLyrics) - .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) - .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - }, + style: { + cursor: "pointer", }, - !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive }) - ), + dir: "auto", + ref: isActive ? activeLineRef : null, + onClick: (event) => { + if (startTime) { + Spicetify.Player.seek(startTime); + } + }, + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(rawLyrics) + .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); + }, + }, + react.createElement("p", {}, !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive })), showTranslatedBelow && originalText && originalText !== text && react.createElement( "p", { - className: `lyrics-lyricsContainer-LyricsLine${i <= activeLineIndex ? " lyrics-lyricsContainer-LyricsLine-active" : ""}`, - style: { opacity: 0.5, cursor: "pointer" }, - dir: "auto", - ref: isActive ? activeLineRef : null, - onClick: (event) => { - if (startTime) { - Spicetify.Player.seek(startTime); - } - }, + style: { opacity: 0.5 }, }, text ) @@ -541,32 +508,24 @@ const UnsyncedLyricsPage = react.memo(({ lyrics, provider, copyright }) => { return react.createElement( "div", { - className: "lyrics-lyricsContainer-LyricsLineContainer", + className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", key: index, - }, - react.createElement( - "p", - { - className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", - dir: "auto", - onContextMenu: (event) => { - event.preventDefault(); - Spicetify.Platform.ClipboardAPI.copy(rawLyrics) - .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) - .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - }, + dir: "auto", + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(rawLyrics) + .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); }, - lineText - ), + }, + react.createElement("p", {}, lineText), showTranslatedBelow && originalText && originalText !== text && react.createElement( "p", { - className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", style: { opacity: 0.5 }, - dir: "auto", }, text ) From 9ef336604b4c57577e88de5ec25b67b34e7130ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Feb 2025 21:36:58 +0100 Subject: [PATCH 107/146] chore(deps): bump golang.org/x/net from 0.34.0 to 0.35.0 (#3309) Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 259ffdd687..106baaedf4 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.14 - golang.org/x/net v0.34.0 + golang.org/x/net v0.35.0 ) require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/sys v0.30.0 // indirect ) diff --git a/go.sum b/go.sum index 2e3c8436a2..cd4674eb33 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 2d9b637a8e1a849eb0d26c9f14050f0f90280222 Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Fri, 14 Feb 2025 05:57:37 +0900 Subject: [PATCH 108/146] feat(lyrics-plus): translation display individual copy implementation (#3308) --- CustomApps/lyrics-plus/OptionsMenu.js | 23 ++++-- CustomApps/lyrics-plus/Pages.js | 112 +++++++++++++++++--------- CustomApps/lyrics-plus/Settings.js | 23 ++++-- CustomApps/lyrics-plus/Utils.js | 63 +++++++++++++-- CustomApps/lyrics-plus/index.js | 17 +++- 5 files changed, 178 insertions(+), 60 deletions(-) diff --git a/CustomApps/lyrics-plus/OptionsMenu.js b/CustomApps/lyrics-plus/OptionsMenu.js index ba06987243..f5173581aa 100644 --- a/CustomApps/lyrics-plus/OptionsMenu.js +++ b/CustomApps/lyrics-plus/OptionsMenu.js @@ -92,8 +92,6 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { none: "None", }; - const savedTranslationDisplay = localStorage.getItem(`${APP_NAME}:visual:translate:display-mode`) || "replace"; - CONFIG.visual["translate:display-mode"] = savedTranslationDisplay; const translationDisplayOptions = { replace: "Replace original", below: "Below original", @@ -168,13 +166,7 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { desc: "Translation Display", key: "translate:display-mode", type: ConfigSelection, - onChange: (name, value) => { - CONFIG.visual[name] = value; - localStorage.setItem(`${APP_NAME}:visual:${name}`, value); - lyricContainerUpdate?.(); - }, options: translationDisplayOptions, - defaultValue: savedTranslationDisplay, renderInline: true, }, { @@ -183,6 +175,8 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { type: ConfigSelection, options: languageOptions, renderInline: true, + // for songs in languages that support translation but not Convert (e.g., English), the option is disabled. + when: () => friendlyLanguage, }, { desc: "Display Mode", @@ -190,6 +184,8 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { type: ConfigSelection, options: modeOptions, renderInline: true, + // for songs in languages that support translation but not Convert (e.g., English), the option is disabled. + when: () => friendlyLanguage, }, { desc: "Convert", @@ -198,6 +194,8 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { trigger: "click", action: "toggle", renderInline: true, + // for songs in languages that support translation but not Convert (e.g., English), the option is disabled. + when: () => friendlyLanguage, }, ]; }, [friendlyLanguage]); @@ -235,6 +233,15 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { type: "translation-menu", items, onChange: (name, value) => { + if (name === "translate:translated-lyrics-source" && friendlyLanguage) { + CONFIG.visual.translate = false; + localStorage.setItem(`${APP_NAME}:visual:translate`, false); + } + if (name === "translate") { + CONFIG.visual["translate:translated-lyrics-source"] = "none"; + localStorage.setItem(`${APP_NAME}:visual:translate:translated-lyrics-source`, "none"); + } + CONFIG.visual[name] = value; localStorage.setItem(`${APP_NAME}:visual:${name}`, value); lyricContainerUpdate?.(); diff --git a/CustomApps/lyrics-plus/Pages.js b/CustomApps/lyrics-plus/Pages.js index 8ba19515eb..f57d231c4c 100755 --- a/CustomApps/lyrics-plus/Pages.js +++ b/CustomApps/lyrics-plus/Pages.js @@ -123,8 +123,6 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara offset += -(activeLineEle.current.offsetTop + activeLineEle.current.clientHeight / 2); } - const rawLyrics = Utils.convertParsedToLRC(lyrics); - return react.createElement( "div", { @@ -174,6 +172,12 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara // Otherwise we should show the translated text const lineText = originalText && showTranslatedBelow ? originalText : text; + // Convert lyrics to text for comparison + const belowOrigin = typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText; + const belowTxt = typeof text === "object" ? text?.props?.children?.[0] : text; + + const belowMode = showTranslatedBelow && originalText && belowOrigin !== belowTxt; + return react.createElement( "div", { @@ -192,23 +196,32 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara Spicetify.Player.seek(startTime); } }, - onContextMenu: (event) => { - event.preventDefault(); - Spicetify.Platform.ClipboardAPI.copy(rawLyrics) - .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) - .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - }, }, - react.createElement("p", {}, !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive })), - showTranslatedBelow && - originalText && - originalText !== text && + react.createElement( + "p", + { + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToLRC(lyrics, belowMode).original) + .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); + }, + }, + !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive }) + ), + belowMode && react.createElement( "p", { style: { opacity: 0.5, }, + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToLRC(lyrics, belowMode).conver) + .then(() => Spicetify.showNotification("Translated lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy translated lyrics to clipboard")); + }, }, text ) @@ -405,8 +418,6 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa } } - const rawLyrics = Utils.convertParsedToLRC(lyrics); - useEffect(() => { if (activeLineRef.current && (!intialScroll[0] || isInViewport(activeLineRef.current))) { activeLineRef.current.scrollIntoView({ @@ -442,6 +453,13 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa // If we have original text and we are showing translated below, we should show the original text // Otherwise we should show the translated text const lineText = originalText && showTranslatedBelow ? originalText : text; + + // Convert lyrics to text for comparison + const belowOrigin = typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText; + const belowTxt = typeof text === "object" ? text?.props?.children?.[0] : text; + + const belowMode = showTranslatedBelow && originalText && belowOrigin !== belowTxt; + return react.createElement( "div", { @@ -457,21 +475,30 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa Spicetify.Player.seek(startTime); } }, - onContextMenu: (event) => { - event.preventDefault(); - Spicetify.Platform.ClipboardAPI.copy(rawLyrics) - .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) - .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - }, }, - react.createElement("p", {}, !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive })), - showTranslatedBelow && - originalText && - originalText !== text && + react.createElement( + "p", + { + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToLRC(lyrics, belowMode).original) + .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); + }, + }, + !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive }) + ), + belowMode && react.createElement( "p", { style: { opacity: 0.5 }, + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToLRC(lyrics, belowMode).conver) + .then(() => Spicetify.showNotification("Translated lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy translated lyrics to clipboard")); + }, }, text ) @@ -489,8 +516,6 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa }); const UnsyncedLyricsPage = react.memo(({ lyrics, provider, copyright }) => { - const rawLyrics = lyrics.map((lyrics) => (typeof lyrics.text !== "object" ? lyrics.text : lyrics.text?.props?.children?.[0])).join("\n"); - return react.createElement( "div", { @@ -505,27 +530,42 @@ const UnsyncedLyricsPage = react.memo(({ lyrics, provider, copyright }) => { // Otherwise we should show the translated text const lineText = originalText && showTranslatedBelow ? originalText : text; + // Convert lyrics to text for comparison + const belowOrigin = typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText; + const belowTxt = typeof text === "object" ? text?.props?.children?.[0] : text; + + const belowMode = showTranslatedBelow && originalText && belowOrigin !== belowTxt; + return react.createElement( "div", { className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active", key: index, dir: "auto", - onContextMenu: (event) => { - event.preventDefault(); - Spicetify.Platform.ClipboardAPI.copy(rawLyrics) - .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) - .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); - }, }, - react.createElement("p", {}, lineText), - showTranslatedBelow && - originalText && - originalText !== text && + react.createElement( + "p", + { + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToUnsynced(lyrics, belowMode).original) + .then(() => Spicetify.showNotification("Lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard")); + }, + }, + lineText + ), + belowMode && react.createElement( "p", { style: { opacity: 0.5 }, + onContextMenu: (event) => { + event.preventDefault(); + Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToUnsynced(lyrics, belowMode).conver) + .then(() => Spicetify.showNotification("Translated lyrics copied to clipboard")) + .catch(() => Spicetify.showNotification("Failed to copy translated lyrics to clipboard")); + }, }, text ) diff --git a/CustomApps/lyrics-plus/Settings.js b/CustomApps/lyrics-plus/Settings.js index e5e3a037ec..e51dc88817 100644 --- a/CustomApps/lyrics-plus/Settings.js +++ b/CustomApps/lyrics-plus/Settings.js @@ -109,6 +109,10 @@ const RefreshTokenButton = ({ setTokenCallback }) => { const ConfigSlider = ({ name, defaultValue, onChange = () => {} }) => { const [active, setActive] = useState(defaultValue); + useEffect(() => { + setActive(defaultValue); + }, [defaultValue]); + const toggleState = useCallback(() => { const state = !active; setActive(state); @@ -512,6 +516,7 @@ const OptionList = ({ type, items, onChange }) => { ); }); }; + const languageCodes = "none,en,af,ar,bg,bn,ca,zh,cs,da,de,el,es,et,fa,fi,fr,gu,he,hi,hr,hu,id,is,it,ja,jv,kn,ko,lt,lv,ml,mr,ms,nl,no,pl,pt,ro,ru,sk,sl,sr,su,sv,ta,te,th,tr,uk,ur,vi,zu".split( "," @@ -523,9 +528,6 @@ const languageOptions = languageCodes.reduce((acc, code) => { return acc; }, {}); -const savedLanguage = localStorage.getItem(`${APP_NAME}:visual:musixmatch-translation-language`) || "none"; -CONFIG.visual["musixmatch-translation-language"] = savedLanguage; - function openConfig() { const configContainer = react.createElement( "div", @@ -645,17 +647,22 @@ function openConfig() { }, { desc: "Musixmatch Translation Language.", - info: "Choose the language you want to translate the lyrics to. Changes will take effect after the next track.", + info: "Choose the language you want to translate the lyrics to. When the language is changed, the lyrics reloads.", key: "musixmatch-translation-language", type: ConfigSelection, options: languageOptions, - defaultValue: savedLanguage, }, ], onChange: (name, value) => { CONFIG.visual[name] = value; localStorage.setItem(`${APP_NAME}:visual:${name}`, value); - lyricContainerUpdate?.(); + + // Reload Lyrics if translation language is changed + if (name === "musixmatch-translation-language") { + reloadLyrics?.(); + } else { + lyricContainerUpdate?.(); + } const configChange = new CustomEvent("lyrics-plus", { detail: { @@ -673,15 +680,17 @@ function openConfig() { onListChange: (list) => { CONFIG.providersOrder = list; localStorage.setItem(`${APP_NAME}:services-order`, JSON.stringify(list)); + reloadLyrics?.(); }, onToggle: (name, value) => { CONFIG.providers[name].on = value; localStorage.setItem(`${APP_NAME}:provider:${name}:on`, value); - lyricContainerUpdate?.(); + reloadLyrics?.(); }, onTokenChange: (name, value) => { CONFIG.providers[name].token = value; localStorage.setItem(`${APP_NAME}:provider:${name}:token`, value); + reloadLyrics?.(); }, }), react.createElement("h2", null, "CORS Proxy Template"), diff --git a/CustomApps/lyrics-plus/Utils.js b/CustomApps/lyrics-plus/Utils.js index bd3961f587..1242138781 100644 --- a/CustomApps/lyrics-plus/Utils.js +++ b/CustomApps/lyrics-plus/Utils.js @@ -96,7 +96,7 @@ const Utils = { // Should return IETF BCP 47 language tags. // This should detect the song's main language. // Remember there is a possibility of a song referencing something in another language and the lyrics show it in that native language! - const rawLyrics = lyrics.map((line) => line.text).join(" "); + const rawLyrics = lyrics[0].originalText ? lyrics.map((line) => line.originalText).join(" ") : lyrics.map((line) => line.text).join(" "); const kanaRegex = /[\u3001-\u3003]|[\u3005\u3007]|[\u301d-\u301f]|[\u3021-\u3035]|[\u3038-\u303a]|[\u3040-\u30ff]|[\uff66-\uff9f]/gu; const hangulRegex = /(\S*[\u3131-\u314e|\u314f-\u3163|\uac00-\ud7a3]+\S*)/g; @@ -139,10 +139,12 @@ const Utils = { const lyric = { startTime: lyricsToTranslate[i].startTime || 0, text: this.rubyTextToReact(translatedLines[i]), + originalText: lyricsToTranslate[i].text, }; state[stateName].push(lyric); } }, + /** It seems that this function is not being used, but I'll keep it just in case it’s needed in the future.*/ processTranslatedOriginalLyrics(lyrics, synced) { const data = []; const dataSouce = {}; @@ -222,13 +224,58 @@ const Utils = { } return text; }, - convertParsedToLRC(lyrics) { - return lyrics - .map((line) => { - if (!line.startTime) return line.text; - return `[${this.formatTime(line.startTime)}]${this.formatTextWithTimestamps(line.text, line.startTime)}`; - }) - .join("\n"); + convertParsedToLRC(lyrics, isBelow) { + let original = ""; + let conver = ""; + + if (isBelow) { + for (const line of lyrics) { + original += `[${this.formatTime(line.startTime)}]${this.formatTextWithTimestamps(line.originalText, line.startTime)}\n`; + conver += `[${this.formatTime(line.startTime)}]${this.formatTextWithTimestamps(line.text, line.startTime)}\n`; + } + } else { + for (const line of lyrics) { + original += `[${this.formatTime(line.startTime)}]${this.formatTextWithTimestamps(line.text, line.startTime)}\n`; + } + } + + return { + original, + conver, + }; + }, + convertParsedToUnsynced(lyrics, isBelow) { + let original = ""; + let conver = ""; + + if (isBelow) { + for (const line of lyrics) { + if (typeof line.originalText === "object") { + original += `${line.originalText?.props?.children?.[0]}\n`; + } else { + original += `${line.originalText}\n`; + } + + if (typeof line.text === "object") { + conver += `${line.text?.props?.children?.[0]}\n`; + } else { + conver += `${line.text}\n`; + } + } + } else { + for (const line of lyrics) { + if (typeof line.text === "object") { + original += `${line.text?.props?.children?.[0]}\n`; + } else { + original += `${line.text}\n`; + } + } + } + + return { + original, + conver, + }; }, parseLocalLyrics(lyrics) { // Preprocess lyrics by removing [tags] and empty lines diff --git a/CustomApps/lyrics-plus/index.js b/CustomApps/lyrics-plus/index.js index 52ec96e6cb..6a4d800321 100644 --- a/CustomApps/lyrics-plus/index.js +++ b/CustomApps/lyrics-plus/index.js @@ -49,6 +49,7 @@ const CONFIG = { translate: getConfig("lyrics-plus:visual:translate", false), "ja-detect-threshold": localStorage.getItem("lyrics-plus:visual:ja-detect-threshold") || "40", "hans-detect-threshold": localStorage.getItem("lyrics-plus:visual:hans-detect-threshold") || "40", + "musixmatch-translation-language": localStorage.getItem("lyrics-plus:visual:musixmatch-translation-language") || "none", "fade-blur": getConfig("lyrics-plus:visual:fade-blur"), "fullscreen-key": localStorage.getItem("lyrics-plus:visual:fullscreen-key") || "f12", "synced-compact": getConfig("lyrics-plus:visual:synced-compact"), @@ -111,7 +112,7 @@ CONFIG.visual["font-size"] = Number.parseInt(CONFIG.visual["font-size"]); CONFIG.visual["ja-detect-threshold"] = Number.parseInt(CONFIG.visual["ja-detect-threshold"]); CONFIG.visual["hans-detect-threshold"] = Number.parseInt(CONFIG.visual["hans-detect-threshold"]); -const CACHE = {}; +let CACHE = {}; const emptyState = { karaoke: null, @@ -123,6 +124,7 @@ const emptyState = { }; let lyricContainerUpdate; +let reloadLyrics; const fontSizeLimit = { min: 16, max: 256, step: 4 }; @@ -179,6 +181,7 @@ class LyricsContainer extends react.Component { this.translationProvider = CONFIG.visual["translate:translated-lyrics-source"]; this.languageOverride = CONFIG.visual["translate:detect-language-override"]; this.translate = CONFIG.visual.translate; + this.reRenderLyricsPage = false; } infoFromTrack(track) { @@ -609,10 +612,18 @@ class LyricsContainer extends react.Component { Utils.addQueueListener(this.onQueueChange); lyricContainerUpdate = () => { + this.reRenderLyricsPage = !this.reRenderLyricsPage; this.updateVisualOnConfigChange(); this.forceUpdate(); }; + reloadLyrics = () => { + CACHE = {}; + this.updateVisualOnConfigChange(); + this.forceUpdate(); + this.fetchLyrics(Spicetify.Player.data.item, this.state.explicitMode); + }; + this.viewPort = document.querySelector(".Root__main-view .os-viewport") ?? document.querySelector(".Root__main-view .main-view-container__scroll-node"); @@ -788,6 +799,7 @@ class LyricsContainer extends react.Component { lyrics: this.state.karaoke, provider: this.state.provider, copyright: this.state.copyright, + reRenderLyricsPage: this.reRenderLyricsPage, }); } else if (mode === SYNCED && this.state.synced) { activeItem = react.createElement(CONFIG.visual["synced-compact"] ? SyncedLyricsPage : SyncedExpandedLyricsPage, { @@ -795,6 +807,7 @@ class LyricsContainer extends react.Component { lyrics: CONFIG.visual.translate && translatedLyrics ? translatedLyrics : this.state.currentLyrics, provider: this.state.provider, copyright: this.state.copyright, + reRenderLyricsPage: this.reRenderLyricsPage, }); } else if (mode === UNSYNCED && this.state.unsynced) { activeItem = react.createElement(UnsyncedLyricsPage, { @@ -802,6 +815,7 @@ class LyricsContainer extends react.Component { lyrics: CONFIG.visual.translate && translatedLyrics ? translatedLyrics : this.state.currentLyrics, provider: this.state.provider, copyright: this.state.copyright, + reRenderLyricsPage: this.reRenderLyricsPage, }); } else if (mode === GENIUS && this.state.genius) { activeItem = react.createElement(GeniusPage, { @@ -816,6 +830,7 @@ class LyricsContainer extends react.Component { lyrics2: this.state.genius2, versionIndex2: this.state.versionIndex2, onVersionChange2: this.onVersionChange2.bind(this), + reRenderLyricsPage: this.reRenderLyricsPage, }); } } From 2e4f3add32c3aefb092850c76230d3209e5030c5 Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Fri, 14 Feb 2025 22:13:04 +0900 Subject: [PATCH 109/146] feat(lyrics-plus): optimize convert performance & improve code quality (#3311) --- CustomApps/lyrics-plus/OptionsMenu.js | 1 - CustomApps/lyrics-plus/Pages.js | 12 +- CustomApps/lyrics-plus/Providers.js | 5 +- CustomApps/lyrics-plus/Settings.js | 42 ++- CustomApps/lyrics-plus/Translator.js | 15 + CustomApps/lyrics-plus/Utils.js | 17 +- CustomApps/lyrics-plus/index.js | 429 ++++++++++++++------------ 7 files changed, 305 insertions(+), 216 deletions(-) diff --git a/CustomApps/lyrics-plus/OptionsMenu.js b/CustomApps/lyrics-plus/OptionsMenu.js index f5173581aa..a97b7ecd79 100644 --- a/CustomApps/lyrics-plus/OptionsMenu.js +++ b/CustomApps/lyrics-plus/OptionsMenu.js @@ -139,7 +139,6 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { } case "korean": { modeOptions = { - hangul: "Hangul", romaja: "Romaja", }; break; diff --git a/CustomApps/lyrics-plus/Pages.js b/CustomApps/lyrics-plus/Pages.js index f57d231c4c..1b960236f6 100755 --- a/CustomApps/lyrics-plus/Pages.js +++ b/CustomApps/lyrics-plus/Pages.js @@ -173,8 +173,8 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara const lineText = originalText && showTranslatedBelow ? originalText : text; // Convert lyrics to text for comparison - const belowOrigin = typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText; - const belowTxt = typeof text === "object" ? text?.props?.children?.[0] : text; + const belowOrigin = (typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText)?.replace(/\s+/g, ""); + const belowTxt = (typeof text === "object" ? text?.props?.children?.[0] : text)?.replace(/\s+/g, ""); const belowMode = showTranslatedBelow && originalText && belowOrigin !== belowTxt; @@ -455,8 +455,8 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa const lineText = originalText && showTranslatedBelow ? originalText : text; // Convert lyrics to text for comparison - const belowOrigin = typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText; - const belowTxt = typeof text === "object" ? text?.props?.children?.[0] : text; + const belowOrigin = (typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText)?.replace(/\s+/g, ""); + const belowTxt = (typeof text === "object" ? text?.props?.children?.[0] : text)?.replace(/\s+/g, ""); const belowMode = showTranslatedBelow && originalText && belowOrigin !== belowTxt; @@ -531,8 +531,8 @@ const UnsyncedLyricsPage = react.memo(({ lyrics, provider, copyright }) => { const lineText = originalText && showTranslatedBelow ? originalText : text; // Convert lyrics to text for comparison - const belowOrigin = typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText; - const belowTxt = typeof text === "object" ? text?.props?.children?.[0] : text; + const belowOrigin = (typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText)?.replace(/\s+/g, ""); + const belowTxt = (typeof text === "object" ? text?.props?.children?.[0] : text)?.replace(/\s+/g, ""); const belowMode = showTranslatedBelow && originalText && belowOrigin !== belowTxt; diff --git a/CustomApps/lyrics-plus/Providers.js b/CustomApps/lyrics-plus/Providers.js index 22224983a8..a6558e95d4 100644 --- a/CustomApps/lyrics-plus/Providers.js +++ b/CustomApps/lyrics-plus/Providers.js @@ -36,7 +36,10 @@ const Providers = { })); } - result.provider = lyrics.provider; + /** + * to distinguish it from the existing Musixmatch, the provider will remain as Spotify. + * if Spotify official lyrics support multiple providers besides Musixmatch in the future, please uncomment the under section. */ + // result.provider = lyrics.provider; return result; }, diff --git a/CustomApps/lyrics-plus/Settings.js b/CustomApps/lyrics-plus/Settings.js index e51dc88817..1974562e3d 100644 --- a/CustomApps/lyrics-plus/Settings.js +++ b/CustomApps/lyrics-plus/Settings.js @@ -51,7 +51,7 @@ const CacheButton = () => { } const [count, setCount] = useState(Object.keys(lyrics).length); - const text = count ? "Clear cached lyrics" : "No cached lyrics"; + const text = count ? "Clear all cached lyrics" : "No cached lyrics"; return react.createElement( "button", @@ -106,6 +106,36 @@ const RefreshTokenButton = ({ setTokenCallback }) => { ); }; +const ConfigButton = ({ name, text, onChange = () => {} }) => { + return react.createElement( + "div", + { + className: "setting-row", + }, + react.createElement( + "label", + { + className: "col description", + }, + name + ), + react.createElement( + "div", + { + className: "col action", + }, + react.createElement( + "button", + { + className: "btn", + onClick: onChange, + }, + text + ) + ) + ); +}; + const ConfigSlider = ({ name, defaultValue, onChange = () => {} }) => { const [active, setActive] = useState(defaultValue); @@ -652,6 +682,16 @@ function openConfig() { type: ConfigSelection, options: languageOptions, }, + { + desc: "Clear Memory Cache", + info: "Loaded lyrics are cached in memory for faster reloading. Press this button to clear the cached lyrics from memory without restarting Spotify.", + key: "clear-memore-cache", + text: "Clear memory cache", + type: ConfigButton, + onChange: () => { + reloadLyrics?.(); + }, + }, ], onChange: (name, value) => { CONFIG.visual[name] = value; diff --git a/CustomApps/lyrics-plus/Translator.js b/CustomApps/lyrics-plus/Translator.js index b012379d06..74f35a5d4e 100644 --- a/CustomApps/lyrics-plus/Translator.js +++ b/CustomApps/lyrics-plus/Translator.js @@ -42,6 +42,21 @@ class Translator { } } + async awaitFinished(language) { + return new Promise((resolve) => { + const interval = setInterval(() => { + this.injectExternals(language); + this.createTranslator(language); + + const lan = language.slice(0, 2); + if (this.finished[lan]) { + clearInterval(interval); + resolve(); + } + }, 100); + }); + } + /** * Fix an issue with kuromoji when loading dict from external urls * Adapted from: https://github.com/mobilusoss/textlint-browser-runner/pull/7 diff --git a/CustomApps/lyrics-plus/Utils.js b/CustomApps/lyrics-plus/Utils.js index 1242138781..440ececb11 100644 --- a/CustomApps/lyrics-plus/Utils.js +++ b/CustomApps/lyrics-plus/Utils.js @@ -132,17 +132,12 @@ const Utils = { return ((simpPercentage - tradPercentage + 1) / 2) * 100 >= CONFIG.visual["hans-detect-threshold"] ? "zh-hans" : "zh-hant"; }, - processTranslatedLyrics(result, lyricsToTranslate, { state, stateName }) { - const translatedLines = result.split("\n"); - state[stateName] = []; - for (let i = 0; i < lyricsToTranslate.length; i++) { - const lyric = { - startTime: lyricsToTranslate[i].startTime || 0, - text: this.rubyTextToReact(translatedLines[i]), - originalText: lyricsToTranslate[i].text, - }; - state[stateName].push(lyric); - } + processTranslatedLyrics(translated, original) { + return original.map((lyric, index) => ({ + startTime: lyric.startTime || 0, + text: this.rubyTextToReact(translated[index]), + originalText: lyric.text, + })); }, /** It seems that this function is not being used, but I'll keep it just in case it’s needed in the future.*/ processTranslatedOriginalLyrics(lyrics, synced) { diff --git a/CustomApps/lyrics-plus/index.js b/CustomApps/lyrics-plus/index.js index 6a4d800321..f6168d73ba 100644 --- a/CustomApps/lyrics-plus/index.js +++ b/CustomApps/lyrics-plus/index.js @@ -44,7 +44,7 @@ const CONFIG = { "translate:display-mode": localStorage.getItem("lyrics-plus:visual:translate:display-mode") || "replace", "translate:detect-language-override": localStorage.getItem("lyrics-plus:visual:translate:detect-language-override") || "off", "translation-mode:japanese": localStorage.getItem("lyrics-plus:visual:translation-mode:japanese") || "furigana", - "translation-mode:korean": localStorage.getItem("lyrics-plus:visual:translation-mode:korean") || "hangul", + "translation-mode:korean": localStorage.getItem("lyrics-plus:visual:translation-mode:korean") || "romaja", "translation-mode:chinese": localStorage.getItem("lyrics-plus:visual:translation-mode:chinese") || "cn", translate: getConfig("lyrics-plus:visual:translate", false), "ja-detect-threshold": localStorage.getItem("lyrics-plus:visual:ja-detect-threshold") || "40", @@ -167,6 +167,7 @@ class LyricsContainer extends react.Component { isFullscreen: false, isFADMode: false, isCached: false, + language: null, }; this.currentTrackUri = ""; this.nextTrackUri = ""; @@ -176,12 +177,12 @@ class LyricsContainer extends react.Component { this.fullscreenContainer.id = "lyrics-fullscreen-container"; this.mousetrap = new Spicetify.Mousetrap(); this.containerRef = react.createRef(null); - this.translator = new Translator(CONFIG.visual["translate:detect-language-override"]); + this.translator = null; // Cache last state - this.translationProvider = CONFIG.visual["translate:translated-lyrics-source"]; this.languageOverride = CONFIG.visual["translate:detect-language-override"]; this.translate = CONFIG.visual.translate; this.reRenderLyricsPage = false; + this.displayMode = null; } infoFromTrack(track) { @@ -262,7 +263,6 @@ class LyricsContainer extends react.Component { if (data.error || (!data.karaoke && !data.synced && !data.unsynced && !data.genius)) continue; if (mode === -1) { finalData = data; - CACHE[data.uri] = finalData; return finalData; } @@ -294,27 +294,13 @@ class LyricsContainer extends react.Component { })); } - CACHE[data.uri] = finalData; return finalData; } - CACHE[trackInfo.uri] = finalData; return finalData; } - async fetchLyrics(track, mode = -1) { - this.state.furigana = - this.state.romaji = - this.state.hiragana = - this.state.katakana = - this.state.hangul = - this.state.romaja = - this.state.cn = - this.state.hk = - this.state.tw = - this.state.musixmatchTranslation = - this.state.neteaseTranslation = - null; + async fetchLyrics(track, mode = -1, refresh = false) { const info = this.infoFromTrack(track); if (!info) { this.setState({ error: "No track info" }); @@ -328,161 +314,229 @@ class LyricsContainer extends react.Component { } this.fetchTempo(info.uri); - - if (mode !== -1) { - if (CACHE[info.uri]?.[CONFIG.modes[mode]]) { - this.resetDelay(); - this.setState({ ...CACHE[info.uri], isCached }); - { - let mode = -1; - if (this.state.explicitMode !== -1) { - mode = this.state.explicitMode; - } else if (this.state.lockMode !== -1) { - mode = this.state.lockMode; - } else { - // Auto switch - if (this.state.karaoke) { - mode = KARAOKE; - } else if (this.state.synced) { - mode = SYNCED; - } else if (this.state.unsynced) { - mode = UNSYNCED; - } else if (this.state.genius) { - mode = GENIUS; - } - } - const lyricsState = CACHE[info.uri][CONFIG.modes[mode]]; - if (lyricsState) { - this.state.currentLyrics = this.state[CONFIG.visual["translate:translated-lyrics-source"]] ?? lyricsState; - } - } - this.translateLyrics(); - return; + this.resetDelay(); + + let tempState; + // if lyrics are cached + if ((mode === -1 && CACHE[info.uri]) || CACHE[info.uri]?.[CONFIG.modes?.[mode]]) { + tempState = { ...CACHE[info.uri], isCached }; + if (CACHE[info.uri]?.mode) { + this.state.explicitMode = CACHE[info.uri]?.mode; + tempState = { ...tempState, mode: CACHE[info.uri]?.mode }; } } else { - if (CACHE[info.uri]) { - this.resetDelay(); - this.setState({ ...CACHE[info.uri], isCached }); - { - let mode = -1; - if (this.state.explicitMode !== -1) { - mode = this.state.explicitMode; - } else if (this.state.lockMode !== -1) { - mode = this.state.lockMode; - } else { - // Auto switch - if (this.state.karaoke) { - mode = KARAOKE; - } else if (this.state.synced) { - mode = SYNCED; - } else if (this.state.unsynced) { - mode = UNSYNCED; - } else if (this.state.genius) { - mode = GENIUS; - } - } - const lyricsState = CACHE[info.uri][CONFIG.modes[mode]]; - if (lyricsState) { - this.state.currentLyrics = this.state[CONFIG.visual["translate:translated-lyrics-source"]] ?? lyricsState; - } - } - this.translateLyrics(); + this.setState({ ...emptyState, isLoading: true, isCached: false }); + + const resp = await this.tryServices(info, mode); + if (resp.provider) { + // Cache lyrics + CACHE[resp.uri] = resp; + } + + // This True when the user presses the Cache Lyrics button and saves it to localStorage. + isCached = this.lyricsSaved(resp.uri); + + // In case user skips tracks too fast and multiple callbacks + // set wrong lyrics to current track. + if (resp.uri === this.currentTrackUri) { + tempState = { ...resp, isLoading: false, isCached }; + } else { return; } } - this.setState({ ...emptyState, isLoading: true, isCached: false }); - const resp = await this.tryServices(info, mode); + let finalMode = mode; + if (mode === -1) { + if (this.state.explicitMode !== -1) { + finalMode = this.state.explicitMode; + } else if (this.state.lockMode !== -1) { + finalMode = this.state.lockMode; + } else { + // Auto switch + if (tempState.karaoke) { + finalMode = KARAOKE; + } else if (tempState.synced) { + finalMode = SYNCED; + } else if (tempState.unsynced) { + finalMode = UNSYNCED; + } else if (tempState.genius) { + finalMode = GENIUS; + } + } + } - isCached = this.lyricsSaved(resp.uri); + this.lyricsSource(tempState, finalMode); + + // if song changed one time + if (tempState.uri !== this.state.uri || refresh) { + // when a song starts for the first time and language-override is selected, the lyrics are converted to the specified language. + // however, when switching it off again, the detected language needs to be known, so defaultLanguage has been introduced. + const defaultLanguage = Utils.detectLanguage(this.state.currentLyrics); + const language = + CONFIG.visual["translate:detect-language-override"] !== "off" ? CONFIG.visual["translate:detect-language-override"] : defaultLanguage; + const friendlyLanguage = language && new Intl.DisplayNames(["en"], { type: "language" }).of(language.split("-")[0])?.toLowerCase(); + const targetConvert = CONFIG.visual[`translation-mode:${friendlyLanguage}`]; + + const isMemorey = CACHE[tempState.uri]?.[targetConvert]; + if (CONFIG.visual.translate && defaultLanguage && !isMemorey) { + this.translateLyrics(language, this.state.currentLyrics, targetConvert).then((translated) => { + const res = { [targetConvert]: translated }; + // Cache translated lyrics + CACHE[tempState.uri] = { ...CACHE[tempState.uri], ...res }; + this.setState({ ...res }); + }); + } - // In case user skips tracks too fast and multiple callbacks - // set wrong lyrics to current track. - if (resp.uri === this.currentTrackUri) { - this.resetDelay(); - this.setState({ ...resp, isLoading: false, isCached }); + // reset and apply + this.setState({ + furigana: null, + romaji: null, + hiragana: null, + katakana: null, + hangul: null, + romaja: null, + cn: null, + hk: null, + tw: null, + musixmatchTranslation: null, + neteaseTranslation: null, + ...tempState, + language: defaultLanguage, + }); + return; } - this.translateLyrics(); + this.setState({ ...tempState }); } - lyricsSource(mode) { - const lyricsState = this.state[CONFIG.modes[mode]]; + lyricsSource(lyricsState, mode) { if (!lyricsState) return; - this.state.currentLyrics = this.state[CONFIG.visual["translate:translated-lyrics-source"]] ?? lyricsState; + + const lang = this.provideLanguageCode(this.state.currentLyrics); + const friendlyLanguage = lang && new Intl.DisplayNames(["en"], { type: "language" }).of(lang.split("-")[0])?.toLowerCase(); + + if (!this.displayMode) { + this.displayMode = CONFIG.visual[`translation-mode:${friendlyLanguage}`]; + } + + // get original Lyrics + const lyrics = lyricsState[CONFIG.modes[mode]]; + + if (CONFIG.visual.translate) { + this.state.currentLyrics = lyricsState[CONFIG.visual[`translation-mode:${friendlyLanguage}`]] ?? lyrics; + } else { + this.state.currentLyrics = lyricsState[CONFIG.visual["translate:translated-lyrics-source"]] ?? lyrics; + } + + // Convert Mode re-fresh + if ( + this.translate !== CONFIG.visual.translate || + this.languageOverride !== CONFIG.visual["translate:detect-language-override"] || + this.displayMode !== CONFIG.visual[`translation-mode:${friendlyLanguage}`] + ) { + this.translate = CONFIG.visual.translate; + this.languageOverride = CONFIG.visual["translate:detect-language-override"]; + this.displayMode = CONFIG.visual[`translation-mode:${friendlyLanguage}`]; + + if (CONFIG.visual.translate) { + const targetConvert = CONFIG.visual[`translation-mode:${friendlyLanguage}`]; + const isCached = CACHE[lyricsState.uri]?.[targetConvert]; + + if (!isCached) { + this.translateLyrics(lang, lyrics, targetConvert).then((translated) => { + const res = { [targetConvert]: translated }; + // Cache translated lyrics + CACHE[lyricsState.uri] = { ...CACHE[lyricsState.uri], ...res }; + this.setState({ ...this.state, ...res }); + }); + } + } else { + const resetCache = { furigana: null, romaji: null, hiragana: null, katakana: null, hangul: null, romaja: null, cn: null, hk: null, tw: null }; + CACHE[lyricsState.uri] = { ...CACHE[lyricsState.uri], ...resetCache }; + } + } } provideLanguageCode(lyrics) { if (!lyrics) return; - if (CONFIG.visual["translate:detect-language-override"] !== "off") return CONFIG.visual["translate:detect-language-override"]; - + if (CONFIG.visual["translate:detect-language-override"] !== "off") { + return CONFIG.visual["translate:detect-language-override"]; + } + if (this.state.language) { + return this.state.language; + } return Utils.detectLanguage(lyrics); } - async translateLyrics(silent = true) { - function showNotification(timeout) { - if (silent) return; - Spicetify.showNotification("Translating...", false, timeout); + async translateLyrics(language, lyrics, targetConvert) { + if (!language) return; + + Spicetify.showNotification("Converting...", false, 1000); + if (!this.translator) { + this.translator = new Translator(language); } + await this.translator.awaitFinished(language); - const lyrics = this.state.currentLyrics; - const language = this.provideLanguageCode(lyrics); + let result; + try { + if (language === "ja") { + // Japanese + const map = { + romaji: { target: "romaji", mode: "spaced" }, + furigana: { target: "hiragana", mode: "furigana" }, + hiragana: { target: "hiragana", mode: "normal" }, + katakana: { target: "katakana", mode: "normal" }, + }; - if (!CONFIG.visual.translate || !language || typeof lyrics?.[0].text !== "string") return; + result = await Promise.all( + lyrics.map(async (lyric) => await this.translator.romajifyText(lyric.text, map[targetConvert].target, map[targetConvert].mode)) + ); + } else if (language === "ko") { + // Korean + result = await Promise.all(lyrics.map(async (lyric) => await this.translator.convertToRomaja(lyric.text, "romaji"))); + } else if (language === "zh-hans") { + // Chinese (Simplified) + const map = { + cn: { from: "cn", target: "cn" }, + tw: { from: "cn", target: "tw" }, + hk: { from: "cn", target: "hk" }, + }; - if (!this.translator?.finished[language.slice(0, 2)]) { - this.translator.injectExternals(language); - this.translator.createTranslator(language); - showNotification(500); - setTimeout(this.translateLyrics.bind(this), 100, false); - return; - } + // prevent conversion between the same language. + if (targetConvert === "cn") { + Spicetify.showNotification("No conversion is needed", false, 1000); + return lyrics; + } - // Seemingly long delay so it can be cleared later for accurate timing - showNotification(10000); - for (const params of [ - ["romaji", "spaced", "romaji"], - ["hiragana", "furigana", "furigana"], - ["hiragana", "normal", "hiragana"], - ["katakana", "normal", "katakana"], - ]) { - if (language !== "ja") continue; - Promise.all(lyrics.map((lyric) => this.translator.romajifyText(lyric.text, params[0], params[1]))).then((results) => { - const result = results.join("\n"); - Utils.processTranslatedLyrics(result, lyrics, { state: this.state, stateName: params[2] }); - showNotification(200); - lyricContainerUpdate?.(); - }); - } + result = await Promise.all( + lyrics.map(async (lyric) => await this.translator.convertChinese(lyric.text, map[targetConvert].from, map[targetConvert].target)) + ); + } else if (language === "zh-hant") { + // Chinese (Traditional) + const map = { + cn: { from: "t", target: "cn" }, + hk: { from: "t", target: "hk" }, + tw: { from: "t", target: "tw" }, + }; - for (const params of [ - ["hangul", "hangul"], - ["romaja", "romaja"], - ]) { - if (language !== "ko") continue; - Promise.all(lyrics.map((lyric) => this.translator.convertToRomaja(lyric.text, params[1]))).then((results) => { - const result = results.join("\n"); - Utils.processTranslatedLyrics(result, lyrics, { state: this.state, stateName: params[1] }); - showNotification(200); - lyricContainerUpdate?.(); - }); - } + // prevent conversion between the same language. + if (targetConvert === "tw") { + Spicetify.showNotification("No conversion is needed", false, 1000); + return lyrics; + } - for (const params of [ - ["cn", "hk"], - ["cn", "tw"], - ["t", "cn"], - ["t", "hk"], - ["t", "tw"], - ]) { - if (!language.includes("zh") || (language === "zh-hans" && params[0] === "t") || (language === "zh-hant" && params[0] === "cn")) continue; - Promise.all(lyrics.map((lyric) => this.translator.convertChinese(lyric.text, params[0], params[1]))).then((results) => { - const result = results.join("\n"); - Utils.processTranslatedLyrics(result, lyrics, { state: this.state, stateName: params[1] }); - showNotification(200); - lyricContainerUpdate?.(); - }); + result = await Promise.all( + lyrics.map(async (lyric) => await this.translator.convertChinese(lyric.text, map[targetConvert].from, map[targetConvert].target)) + ); + } + + const res = Utils.processTranslatedLyrics(result, lyrics); + Spicetify.showNotification("Converting...", false, 0); + return res; + } catch (error) { + Spicetify.showNotification("Convert Error!", true); + console.error(error); } } @@ -538,6 +592,14 @@ class LyricsContainer extends react.Component { this.setState({ isCached: true }); } + deleteLocalLyrics(uri) { + const localLyrics = JSON.parse(localStorage.getItem(`${APP_NAME}:local-lyrics`)) || {}; + delete localLyrics[uri]; + localStorage.setItem(`${APP_NAME}:local-lyrics`, JSON.stringify(localLyrics)); + console.log(localLyrics); + this.setState({ isCached: false }); + } + lyricsSaved(uri) { const localLyrics = JSON.parse(localStorage.getItem(`${APP_NAME}:local-lyrics`)) || {}; return !!localLyrics[uri]; @@ -599,7 +661,12 @@ class LyricsContainer extends react.Component { // Debounce next track fetch if (!nextInfo || nextInfo.uri === this.nextTrackUri) return; this.nextTrackUri = nextInfo.uri; - this.tryServices(nextInfo, this.state.explicitMode); + this.tryServices(nextInfo, this.state.explicitMode).then((resp) => { + if (resp.provider) { + // Cache lyrics + CACHE[resp.uri] = resp; + } + }); }; if (Spicetify.Player?.data?.item) { @@ -621,7 +688,7 @@ class LyricsContainer extends react.Component { CACHE = {}; this.updateVisualOnConfigChange(); this.forceUpdate(); - this.fetchLyrics(Spicetify.Player.data.item, this.state.explicitMode); + this.fetchLyrics(Spicetify.Player.data.item, this.state.explicitMode, true); }; this.viewPort = @@ -698,45 +765,6 @@ class LyricsContainer extends react.Component { this.mousetrap.bind(CONFIG.visual["fullscreen-key"], this.toggleFullscreen); } - componentDidUpdate() { - // Apparently if any of these values are changed, the cached translation will not be updated, hence the need to retranslate - if ( - this.translationProvider !== CONFIG.visual["translate:translated-lyrics-source"] || - this.languageOverride !== CONFIG.visual["translate:detect-language-override"] || - this.translate !== CONFIG.visual.translate - ) { - this.translationProvider = CONFIG.visual["translate:translated-lyrics-source"]; - this.languageOverride = CONFIG.visual["translate:detect-language-override"]; - this.translate = CONFIG.visual.translate; - - this.translateLyrics(false); - - return; - } - - const language = this.provideLanguageCode(this.state.currentLyrics); - - let isTranslated = false; - - switch (language) { - case "zh-hans": - case "zh-hant": { - isTranslated = !!(this.state.cn || this.state.hk || this.state.tw); - break; - } - case "ja": { - isTranslated = !!(this.state.romaji || this.state.furigana || this.state.hiragana || this.state.katakana); - break; - } - case "ko": { - isTranslated = !!(this.state.hangul || this.state.romaja); - break; - } - } - - !isTranslated && this.translateLyrics(); - } - render() { const fadLyricsContainer = document.getElementById("fad-lyrics-plus-container"); this.state.isFADMode = !!fadLyricsContainer; @@ -781,16 +809,14 @@ class LyricsContainer extends react.Component { let activeItem; let showTranslationButton; - let friendlyLanguage; + this.lyricsSource(this.state, mode); + const lang = this.provideLanguageCode(this.state.currentLyrics); + const friendlyLanguage = lang && new Intl.DisplayNames(["en"], { type: "language" }).of(lang.split("-")[0])?.toLowerCase(); const hasTranslation = this.state.neteaseTranslation !== null || this.state.musixmatchTranslation !== null; if (mode !== -1) { - this.lyricsSource(mode); - const language = this.provideLanguageCode(this.state.currentLyrics); - friendlyLanguage = language && new Intl.DisplayNames(["en"], { type: "language" }).of(language.split("-")[0])?.toLowerCase(); showTranslationButton = (friendlyLanguage || hasTranslation) && (mode === SYNCED || mode === UNSYNCED); - const translatedLyrics = this.state[CONFIG.visual[`translation-mode:${friendlyLanguage}`]]; if (mode === KARAOKE && this.state.karaoke) { activeItem = react.createElement(CONFIG.visual["synced-compact"] ? SyncedLyricsPage : SyncedExpandedLyricsPage, { @@ -804,7 +830,7 @@ class LyricsContainer extends react.Component { } else if (mode === SYNCED && this.state.synced) { activeItem = react.createElement(CONFIG.visual["synced-compact"] ? SyncedLyricsPage : SyncedExpandedLyricsPage, { trackUri: this.state.uri, - lyrics: CONFIG.visual.translate && translatedLyrics ? translatedLyrics : this.state.currentLyrics, + lyrics: this.state.currentLyrics, provider: this.state.provider, copyright: this.state.copyright, reRenderLyricsPage: this.reRenderLyricsPage, @@ -812,7 +838,7 @@ class LyricsContainer extends react.Component { } else if (mode === UNSYNCED && this.state.unsynced) { activeItem = react.createElement(UnsyncedLyricsPage, { trackUri: this.state.uri, - lyrics: CONFIG.visual.translate && translatedLyrics ? translatedLyrics : this.state.currentLyrics, + lyrics: this.state.currentLyrics, provider: this.state.provider, copyright: this.state.copyright, reRenderLyricsPage: this.reRenderLyricsPage, @@ -898,8 +924,13 @@ class LyricsContainer extends react.Component { return; } - this.saveLocalLyrics(this.currentTrackUri, { synced, unsynced, karaoke, genius }); - Spicetify.showNotification("Lyrics cached"); + if (this.state.isCached) { + this.deleteLocalLyrics(this.currentTrackUri); + Spicetify.showNotification("Delete lyrics cache"); + } else { + this.saveLocalLyrics(this.currentTrackUri, { synced, unsynced, karaoke, genius }); + Spicetify.showNotification("Lyrics cached"); + } }, }, react.createElement("svg", { @@ -956,6 +987,12 @@ class LyricsContainer extends react.Component { switchCallback: (label) => { const mode = CONFIG.modes.findIndex((a) => a === label); if (mode !== this.state.mode) { + // If explicitMode is not set, moving the topBar will apply the default mode value for the selected song. + const info = this.infoFromTrack(Spicetify.Player.data.item); + if (info?.uri && CACHE[info?.uri]) { + CACHE[info.uri].mode = mode; + } + this.setState({ explicitMode: mode }); this.state.provider !== "local" && this.fetchLyrics(Spicetify.Player.data.item, mode); } From 0414d766239b51f1d6cff6802c9fb361e4a64fe4 Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Sat, 15 Feb 2025 02:20:05 +0900 Subject: [PATCH 110/146] fix(lyrics-plus): properly check for `none` value in trans lang (#3312) --- CustomApps/lyrics-plus/OptionsMenu.js | 1 + CustomApps/lyrics-plus/Settings.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CustomApps/lyrics-plus/OptionsMenu.js b/CustomApps/lyrics-plus/OptionsMenu.js index a97b7ecd79..1dfaf49b8a 100644 --- a/CustomApps/lyrics-plus/OptionsMenu.js +++ b/CustomApps/lyrics-plus/OptionsMenu.js @@ -111,6 +111,7 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => { if (hasTranslation.musixmatch) { const selectedLanguage = CONFIG.visual["musixmatch-translation-language"]; + if (selectedLanguage === "none") return; const languageName = new Intl.DisplayNames([selectedLanguage], { type: "language", }).of(selectedLanguage); diff --git a/CustomApps/lyrics-plus/Settings.js b/CustomApps/lyrics-plus/Settings.js index 1974562e3d..84ad23278b 100644 --- a/CustomApps/lyrics-plus/Settings.js +++ b/CustomApps/lyrics-plus/Settings.js @@ -699,6 +699,10 @@ function openConfig() { // Reload Lyrics if translation language is changed if (name === "musixmatch-translation-language") { + if (value === "none") { + CONFIG.visual["translate:translated-lyrics-source"] = "none"; + localStorage.setItem(`${APP_NAME}:visual:translate:translated-lyrics-source`, "none"); + } reloadLyrics?.(); } else { lyricContainerUpdate?.(); From 37fa2106f8b0a6c0715b0ecc469ccfa28d3ed01d Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Sat, 22 Feb 2025 00:15:20 +0900 Subject: [PATCH 111/146] fix(lyrics-plus): properly set height on main view (#3322) --- CustomApps/lyrics-plus/style.css | 3 +++ css-map.json | 1 + 2 files changed, 4 insertions(+) diff --git a/CustomApps/lyrics-plus/style.css b/CustomApps/lyrics-plus/style.css index c07fc38a4b..79325c80f8 100644 --- a/CustomApps/lyrics-plus/style.css +++ b/CustomApps/lyrics-plus/style.css @@ -704,6 +704,9 @@ div.lyrics-tabBar-headerItemLink { .split .lyrics-versionSelector select { width: 100%; } +.main-content-view { + height: 100%; +} @media (min-width: 1024px) { .split .lyrics-lyricsContainer-LyricsLine { diff --git a/css-map.json b/css-map.json index ffc58ba9a6..8bb95209f5 100644 --- a/css-map.json +++ b/css-map.json @@ -271,6 +271,7 @@ "X05XDhpQJ7THPHfgbUk1": "main-confirmDialog-buttonContainer", "RVgHI2ejYct8LjT1AO7m": "main-confirmDialog-container", "m0yIuS1Q6XRA5R4PNEhl": "main-confirmDialog-overlay", + "KL8t9WB65UfUEPuTFAhO": "main-content-view", "gQoa8JTSpjSmYyABcag2": "main-connectBar-connectBar", "T3hkVxXuSbCYOD2GIeQd": "main-connectBar-connected", "GcHojieewpdN1c8vbtwk": "main-connectBar-connecting", From 1ec567bddbb1c54068b50aca12468893c3fd259d Mon Sep 17 00:00:00 2001 From: ririxi Date: Thu, 27 Feb 2025 23:42:19 +0100 Subject: [PATCH 112/146] feat: disallow running spicetify as admin/root --- CustomApps/lyrics-plus/index.js | 22 +++++++----- spicetify.go | 59 ++++++++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 16 deletions(-) diff --git a/CustomApps/lyrics-plus/index.js b/CustomApps/lyrics-plus/index.js index f6168d73ba..72952a6901 100644 --- a/CustomApps/lyrics-plus/index.js +++ b/CustomApps/lyrics-plus/index.js @@ -58,9 +58,14 @@ const CONFIG = { delay: 0, }, providers: { + lrclib: { + on: getConfig("lyrics-plus:provider:lrclib:on"), + desc: "Lyrics sourced from lrclib.net. Supports both synced and unsynced lyrics. LRCLIB is a free and open-source lyrics provider.", + modes: [SYNCED, UNSYNCED], + }, musixmatch: { on: getConfig("lyrics-plus:provider:musixmatch:on"), - desc: "Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. If you have problems with retrieving lyrics, try refreshing the token by clicking Refresh Token button.", + desc: "Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. If you have problems with retrieving lyrics, try refreshing the token by clicking Refresh Token button. You may need to be forced to use your own CORS Proxy to use this provider.", token: localStorage.getItem("lyrics-plus:provider:musixmatch:token") || "21051986b9886beabe1ce01c3ce94c96319411f8f2c122676365e3", modes: [KARAOKE, SYNCED, UNSYNCED], }, @@ -70,15 +75,10 @@ const CONFIG = { modes: [SYNCED, UNSYNCED], }, netease: { - on: getConfig("lyrics-plus:provider:netease:on"), + on: getConfig("lyrics-plus:provider:netease:on", false), desc: "Crowdsourced lyrics provider ran by Chinese developers and users.", modes: [KARAOKE, SYNCED, UNSYNCED], }, - lrclib: { - on: getConfig("lyrics-plus:provider:lrclib:on"), - desc: "Lyrics sourced from lrclib.net. Supports both synced and unsynced lyrics. LRCLIB is a free and open-source lyrics provider.", - modes: [SYNCED, UNSYNCED], - }, genius: { on: spotifyVersion >= "1.2.31" ? false : getConfig("lyrics-plus:provider:genius:on"), desc: "Provide unsynced lyrics with insights from artists themselves. Genius is disabled and cannot be used as a provider on 1.2.31 and higher.", @@ -175,9 +175,10 @@ class LyricsContainer extends react.Component { this.styleVariables = {}; this.fullscreenContainer = document.createElement("div"); this.fullscreenContainer.id = "lyrics-fullscreen-container"; - this.mousetrap = new Spicetify.Mousetrap(); + this.mousetrap = null; this.containerRef = react.createRef(null); this.translator = null; + this.initMoustrap(); // Cache last state this.languageOverride = CONFIG.visual["translate:detect-language-override"]; this.translate = CONFIG.visual.translate; @@ -647,6 +648,11 @@ class LyricsContainer extends react.Component { reader.readAsText(file[0]); event.target.value = ""; } + initMoustrap() { + if (!this.mousetrap && Spicetify.Mousetrap) { + this.mousetrap = new Spicetify.Mousetrap(); + } + } componentDidMount() { this.onQueueChange = async ({ data: queue }) => { diff --git a/spicetify.go b/spicetify.go index 1a6ee47b4f..62edb59922 100644 --- a/spicetify.go +++ b/spicetify.go @@ -17,6 +17,7 @@ import ( "github.com/spicetify/cli/src/cmd" spotifystatus "github.com/spicetify/cli/src/status/spotify" "github.com/spicetify/cli/src/utils" + "golang.org/x/sys/windows" ) var ( @@ -24,16 +25,47 @@ var ( ) var ( - flags = []string{} - commands = []string{} - quiet = false - extensionFocus = false - appFocus = false - styleFocus = false - noRestart = false - liveRefresh = false + flags = []string{} + commands = []string{} + quiet = false + extensionFocus = false + appFocus = false + styleFocus = false + noRestart = false + liveRefresh = false + bypassAdminCheck = false ) +func isAdmin(bypassAdminCheck bool) bool { + if bypassAdminCheck { + return false + } + + switch runtime.GOOS { + case "windows": + var sid *windows.SID + err := windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, + 2, + windows.SECURITY_BUILTIN_DOMAIN_RID, + windows.DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid) + if err != nil { + return false + } + defer windows.FreeSid(sid) + + token := windows.Token(0) + member, err := token.IsMember(sid) + return err == nil && member + + case "linux", "darwin": + return os.Geteuid() == 0 + } + return false +} + func init() { if runtime.GOOS != "windows" && runtime.GOOS != "darwin" && @@ -66,6 +98,8 @@ func init() { for _, v := range flags { switch v { + case "--bypass-admin": + bypassAdminCheck = true case "-c", "--config": fmt.Println(cmd.GetConfigPath()) os.Exit(0) @@ -110,6 +144,15 @@ func init() { os.Stdout = nil } + if isAdmin(bypassAdminCheck) { + utils.PrintError("Spicetify should not be run with administrator/root privileges") + utils.PrintError("Running as admin can cause Spotify to show a black/blank window after applying spicetify") + utils.PrintError("This happens because Spotify (running as a normal user) can't access files modified with admin privileges") + utils.PrintInfo("If you understand the risks and need to continue anyway, you can use the '--bypass-admin' flag.") + utils.PrintInfo("Spicetify is now exiting...") + os.Exit(1) + } + utils.MigrateConfigFolder() utils.MigrateFolders() cmd.InitConfig(quiet) From 54f4cadd8d3f5ad2466a62a1b069b3dc25d8a0f9 Mon Sep 17 00:00:00 2001 From: ririxi Date: Thu, 27 Feb 2025 23:49:28 +0100 Subject: [PATCH 113/146] fix(preprocess): add css for height on apps pages (1.2.57+) --- src/preprocess/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 2551f66040..ac0f084f11 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -142,7 +142,7 @@ func Start(version string, extractedAppsPath string, flags Flag) { } if fileName == "xpui.css" { content = content + ` - .main-gridContainer-fixedWidth{grid-template-columns: repeat(auto-fill, var(--column-width));width: calc((var(--column-count) - 1) * var(--grid-gap)) + var(--column-count) * var(--column-width));}.main-cardImage-imageWrapper{background-color: var(--card-color, #333);border-radius: 6px;-webkit-box-shadow: 0 8px 24px rgba(0, 0, 0, .5);box-shadow: 0 8px 24px rgba(0, 0, 0, .5);padding-bottom: 100%;position: relative;width:100%;}.main-cardImage-image,.main-card-imagePlaceholder{height: 100%;left: 0;position: absolute;top: 0;width: 100%} + .main-gridContainer-fixedWidth{grid-template-columns: repeat(auto-fill, var(--column-width));width: calc((var(--column-count) - 1) * var(--grid-gap)) + var(--column-count) * var(--column-width));}.main-cardImage-imageWrapper{background-color: var(--card-color, #333);border-radius: 6px;-webkit-box-shadow: 0 8px 24px rgba(0, 0, 0, .5);box-shadow: 0 8px 24px rgba(0, 0, 0, .5);padding-bottom: 100%;position: relative;width:100%;}.main-cardImage-image,.main-card-imagePlaceholder{height: 100%;left: 0;position: absolute;top: 0;width: 100%};.main-content-view{height:100%;} ` } return content From ebe60cd9bc5adaea015071ffa5da1ee9137b245a Mon Sep 17 00:00:00 2001 From: ririxi Date: Fri, 28 Feb 2025 00:01:52 +0100 Subject: [PATCH 114/146] fix: split `isAdmin` to platform files --- spicetify.go | 34 ++-------------------------------- src/utils/isAdmin/unix.go | 13 +++++++++++++ src/utils/isAdmin/windows.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 src/utils/isAdmin/unix.go create mode 100644 src/utils/isAdmin/windows.go diff --git a/spicetify.go b/spicetify.go index 62edb59922..3ba3f2ca9b 100644 --- a/spicetify.go +++ b/spicetify.go @@ -17,7 +17,7 @@ import ( "github.com/spicetify/cli/src/cmd" spotifystatus "github.com/spicetify/cli/src/status/spotify" "github.com/spicetify/cli/src/utils" - "golang.org/x/sys/windows" + "github.com/spicetify/cli/src/utils/isAdmin" ) var ( @@ -36,36 +36,6 @@ var ( bypassAdminCheck = false ) -func isAdmin(bypassAdminCheck bool) bool { - if bypassAdminCheck { - return false - } - - switch runtime.GOOS { - case "windows": - var sid *windows.SID - err := windows.AllocateAndInitializeSid( - &windows.SECURITY_NT_AUTHORITY, - 2, - windows.SECURITY_BUILTIN_DOMAIN_RID, - windows.DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, - &sid) - if err != nil { - return false - } - defer windows.FreeSid(sid) - - token := windows.Token(0) - member, err := token.IsMember(sid) - return err == nil && member - - case "linux", "darwin": - return os.Geteuid() == 0 - } - return false -} - func init() { if runtime.GOOS != "windows" && runtime.GOOS != "darwin" && @@ -144,7 +114,7 @@ func init() { os.Stdout = nil } - if isAdmin(bypassAdminCheck) { + if isAdmin.Check(bypassAdminCheck) { utils.PrintError("Spicetify should not be run with administrator/root privileges") utils.PrintError("Running as admin can cause Spotify to show a black/blank window after applying spicetify") utils.PrintError("This happens because Spotify (running as a normal user) can't access files modified with admin privileges") diff --git a/src/utils/isAdmin/unix.go b/src/utils/isAdmin/unix.go new file mode 100644 index 0000000000..d1621c3743 --- /dev/null +++ b/src/utils/isAdmin/unix.go @@ -0,0 +1,13 @@ +//go:build !windows +// +build !windows + +package isAdmin + +import "os" + +func Check(bypassAdminCheck bool) bool { + if bypassAdminCheck { + return false + } + return os.Geteuid() == 0 +} \ No newline at end of file diff --git a/src/utils/isAdmin/windows.go b/src/utils/isAdmin/windows.go new file mode 100644 index 0000000000..eadea6e280 --- /dev/null +++ b/src/utils/isAdmin/windows.go @@ -0,0 +1,31 @@ +//go:build windows +// +build windows + +package isAdmin + +import ( + "golang.org/x/sys/windows" +) + +func Check(bypassAdminCheck bool) bool { + if bypassAdminCheck { + return false + } + + var sid *windows.SID + err := windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, + 2, + windows.SECURITY_BUILTIN_DOMAIN_RID, + windows.DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid) + if err != nil { + return false + } + defer windows.FreeSid(sid) + + token := windows.Token(0) + member, err := token.IsMember(sid) + return err == nil && member +} \ No newline at end of file From fecdb4d439bd16d44a81f0030d50d0bc4b67bc3a Mon Sep 17 00:00:00 2001 From: ririxi Date: Fri, 28 Feb 2025 00:02:55 +0100 Subject: [PATCH 115/146] style: run `fmt` on `isAdmin` pkg --- src/utils/isAdmin/unix.go | 2 +- src/utils/isAdmin/windows.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/isAdmin/unix.go b/src/utils/isAdmin/unix.go index d1621c3743..a28ef1a1f4 100644 --- a/src/utils/isAdmin/unix.go +++ b/src/utils/isAdmin/unix.go @@ -10,4 +10,4 @@ func Check(bypassAdminCheck bool) bool { return false } return os.Geteuid() == 0 -} \ No newline at end of file +} diff --git a/src/utils/isAdmin/windows.go b/src/utils/isAdmin/windows.go index eadea6e280..842d27b3ec 100644 --- a/src/utils/isAdmin/windows.go +++ b/src/utils/isAdmin/windows.go @@ -28,4 +28,4 @@ func Check(bypassAdminCheck bool) bool { token := windows.Token(0) member, err := token.IsMember(sid) return err == nil && member -} \ No newline at end of file +} From b97abcc486c5da04bc43dc4a90b7da874d347120 Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Fri, 28 Feb 2025 08:08:22 +0900 Subject: [PATCH 116/146] feat(popupLyrics): implement lyrics caching & Musixmatch token refresh (#3328) --- Extensions/popupLyrics.js | 244 ++++++++++++++++++++++++++++++++------ 1 file changed, 206 insertions(+), 38 deletions(-) diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index abde9ec914..74f42b552a 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -25,6 +25,8 @@ if (!navigator.serviceWorker) { PopupLyrics(); } +let CACHE = {}; + function PopupLyrics() { const { Player, CosmosAsync, LocalStorage, ContextMenu } = Spicetify; @@ -324,7 +326,7 @@ function PopupLyrics() { musixmatch: { on: boolLocalStorage("popup-lyrics:services:musixmatch:on"), call: LyricProviders.fetchMusixmatch, - desc: `Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. Follow instructions on Spicetify Docs.`, + desc: "Fully compatible with Spotify. Requires a token that can be retrieved from the official Musixmatch app. If you have problems with retrieving lyrics, try refreshing the token by clicking Refresh Token button.", token: LocalStorage.get("popup-lyrics:services:musixmatch:token") || "2005218b74f939209bda92cb633c7380612e14cb7fe92dcd6a780f", }, spotify: { @@ -421,9 +423,11 @@ function PopupLyrics() { let sharedData = {}; - Player.addEventListener("songchange", updateTrack); + Player.addEventListener("songchange", () => { + updateTrack(); + }); - async function updateTrack() { + async function updateTrack(refresh = false) { if (!lyricVideoIsOpen) { return; } @@ -443,20 +447,26 @@ function PopupLyrics() { uri: Player.data.item.uri, }; - for (const name of userConfigs.servicesOrder) { - const service = userConfigs.services[name]; - if (!service.on) continue; - sharedData = { lyrics: [] }; + if (CACHE?.[info.uri]?.lyrics?.length && !refresh) { + sharedData = CACHE[info.uri]; + } else { + for (const name of userConfigs.servicesOrder) { + const service = userConfigs.services[name]; + if (!service.on) continue; + sharedData = { lyrics: [] }; + + try { + const data = await service.call(info); + console.log(data); + sharedData = data; + CACHE[info.uri] = sharedData; - try { - const data = await service.call(info); - console.log(data); - sharedData = data; - if (!sharedData.error) { - return; + if (!sharedData.error) { + return; + } + } catch (err) { + sharedData = { error: "No lyrics" }; } - } catch (err) { - sharedData = { error: "No lyrics" }; } } } @@ -815,11 +825,20 @@ function PopupLyrics() { function openConfig(event) { event.preventDefault(); - if (!configContainer) { + + // Reset on reopen + if (configContainer) { + resetTokenButton(configContainer); + } else { configContainer = document.createElement("div"); configContainer.id = "popup-config-container"; const style = document.createElement("style"); style.innerHTML = ` +.setting-row { + display: flex; + justify-content: space-between; + align-items: center; +} .setting-row::after { content: ""; display: table; @@ -831,13 +850,16 @@ function PopupLyrics() { align-items: center; } .setting-row .col.description { - float: left; padding-right: 15px; cursor: default; + width: 50%; } .setting-row .col.action { - float: right; - text-align: right; + justify-content: flex-end; + width: 50%; +} +.popup-config-col-margin { + margin-top: 10px; } button.switch { align-items: center; @@ -859,6 +881,27 @@ button.switch.small { height: 22px; padding: 6px; } +button.btn { + font-weight: 700; + display: block; + background-color: rgba(var(--spice-rgb-shadow), .7); + border-radius: 500px; + transition-duration: 33ms; + transition-property: background-color, border-color, color, box-shadow, filter, transform; + padding-inline: 15px; + border: 1px solid #727272; + color: var(--spice-text); + min-block-size: 32px; + cursor: pointer; +} +button.btn:hover { + transform: scale(1.04); + border-color: var(--spice-text); +} +button.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} #popup-config-container select { color: var(--spice-text); background: rgba(var(--spice-rgb-shadow), .7); @@ -945,6 +988,13 @@ button.switch.small { userConfigs.delay = Number(state); LocalStorage.set("popup-lyrics:delay", state); }); + const clearCache = descriptiveElement( + createButton("Clear Memory Cache", "Clear Memory Cache", () => { + CACHE = {}; + updateTrack(); + }), + "Loaded lyrics are cached in memory for faster reloading. Press this button to clear the cached lyrics from memory without restarting Spotify." + ); const serviceHeader = document.createElement("h2"); serviceHeader.innerText = "Services"; @@ -975,7 +1025,7 @@ button.switch.small { const id = el.dataset.id; userConfigs.services[id].on = state; LocalStorage.set(`popup-lyrics:services:${id}:on`, state); - updateTrack(); + updateTrack(true); } function posCallback(el, dir) { @@ -990,23 +1040,28 @@ button.switch.small { LocalStorage.set("popup-lyrics:services-order", JSON.stringify(userConfigs.servicesOrder)); stackServiceElements(); - updateTrack(); - } - - function tokenChangeCallback(el, inputEl) { - const newVal = inputEl.value; - const id = el.dataset.id; - userConfigs.services[id].token = newVal; - LocalStorage.set(`popup-lyrics:services:${id}:token`, newVal); - updateTrack(); + updateTrack(true); } for (const name of userConfigs.servicesOrder) { - userConfigs.services[name].element = createServiceOption(name, userConfigs.services[name], switchCallback, posCallback, tokenChangeCallback); + userConfigs.services[name].element = createServiceOption(name, userConfigs.services[name], switchCallback, posCallback); } stackServiceElements(); - configContainer.append(style, optionHeader, smooth, center, cover, blurSize, fontSize, ratio, delay, serviceHeader, serviceContainer); + configContainer.append( + style, + optionHeader, + smooth, + center, + cover, + blurSize, + fontSize, + ratio, + delay, + clearCache, + serviceHeader, + serviceContainer + ); } Spicetify.PopupModal.display({ title: "Popup Lyrics", @@ -1084,8 +1139,125 @@ button.switch.small { return container; } + // if name is null, the element can be used without a description. + function createButton(name, defaultValue, callback) { + let container; + + if (name) { + container = document.createElement("div"); + container.innerHTML = ` +

+ +
+ +
+
`; + + const button = container.querySelector("#popup-lyrics-clickbutton"); + button.onclick = () => { + callback(); + }; + } else { + container = document.createElement("button"); + container.innerHTML = defaultValue; + container.className = "btn "; + + container.onclick = () => { + callback(); + }; + } + + return container; + } + // if name is null, the element can be used without a description. + function createTextfield(name, defaultValue, placeholder, callback) { + let container; + + if (name) { + container = document.createElement("div"); + container.className = "setting-column"; + container.innerHTML = ` + + `; + + const textfield = container.querySelector("#popup-lyrics-textfield"); + textfield.onchange = () => { + callback(); + }; + } else { + container = document.createElement("input"); + container.placeholder = placeholder; + container.value = defaultValue; + + container.onchange = (e) => { + callback(e.target.value); + }; + } + + return container; + } + function descriptiveElement(element, description) { + const desc = document.createElement("span"); + desc.innerHTML = description; + element.append(desc); + return element; + } + + function resetTokenButton(container) { + const button = container.querySelector("#popup-lyrics-refresh-token"); + if (button) { + button.innerHTML = "Refresh token"; + button.disabled = false; + } + } + + function musixmatchTokenElements(defaultVal, id) { + const button = createButton(null, "Refresh token", clickRefresh); + button.className += "popup-config-col-margin"; + button.id = "popup-lyrics-refresh-token"; + const textfield = createTextfield(null, defaultVal.token, `Place your ${id} token here`, changeTokenfield); + textfield.className += "popup-config-col-margin"; + + function clickRefresh() { + button.innerHTML = "Refreshing token..."; + button.disabled = true; + + Spicetify.CosmosAsync.get("https://apic-desktop.musixmatch.com/ws/1.1/token.get?app_id=web-desktop-app-v1.0", null, { + authority: "apic-desktop.musixmatch.com", + }) + .then(({ message: response }) => { + if (response.header.status_code === 200 && response.body.user_token) { + button.innerHTML = "Token refreshed"; + textfield.value = response.body.user_token; + textfield.dispatchEvent(new Event("change")); + } else if (response.header.status_code === 401) { + button.innerHTML = "Too many attempts"; + } else { + button.innerHTML = "Failed to refresh token"; + console.error("Failed to refresh token", response); + } + }) + .catch((error) => { + button.innerHTML = "Failed to refresh token"; + console.error("Failed to refresh token", error); + }); + } + + function changeTokenfield(value) { + userConfigs.services.musixmatch.token = value; + LocalStorage.set("popup-lyrics:services:musixmatch:token", value); + updateTrack(true); + } + + const container = document.createElement("div"); + container.append(button); + container.append(textfield); + return container; + } - function createServiceOption(id, defaultVal, switchCallback, posCallback, tokenCallback) { + function createServiceOption(id, defaultVal, switchCallback, posCallback) { const name = id.replace(/^./, (c) => c.toUpperCase()); const container = document.createElement("div"); @@ -1113,12 +1285,8 @@ button.switch.small { ${defaultVal.desc}`; - if (defaultVal.token !== undefined) { - const input = document.createElement("input"); - input.placeholder = `Place your ${id} token here`; - input.value = defaultVal.token; - input.onchange = () => tokenCallback(container, input); - container.append(input); + if (id === "musixmatch") { + container.append(musixmatchTokenElements(defaultVal)); } const [up, down, slider] = container.querySelectorAll("button"); From a678cfdd5402d652d92e0c7944e09edfb69120d5 Mon Sep 17 00:00:00 2001 From: obvRedwolf <71992659+obvRedwolf@users.noreply.github.com> Date: Thu, 27 Feb 2025 18:09:05 -0500 Subject: [PATCH 117/146] feat(types): add `dark_vibrant` to `colorExtractor` (#3321) --- globals.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/globals.d.ts b/globals.d.ts index a7e6f6beab..b32889e6a0 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -540,6 +540,7 @@ declare namespace Spicetify { * @param uri Any type of URI that has artwork (playlist, track, album, artist, show, ...) */ function colorExtractor(uri: string): Promise<{ + DARK_VIBRANT: string; DESATURATED: string; LIGHT_VIBRANT: string; PROMINENT: string; From 5944d061367e938da2a9a64f737835f252799931 Mon Sep 17 00:00:00 2001 From: Jeong Hyeon Date: Fri, 28 Feb 2025 21:33:21 +0900 Subject: [PATCH 118/146] fix(popupLyrics): update view in system tray mode (#3330) --- Extensions/popupLyrics.js | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Extensions/popupLyrics.js b/Extensions/popupLyrics.js index 74f42b552a..bba91de09f 100644 --- a/Extensions/popupLyrics.js +++ b/Extensions/popupLyrics.js @@ -18,6 +18,7 @@ if (!navigator.serviceWorker) { num = setInterval(() => postMessage("popup-lyric-update-ui"), 16.66); } else if (event.data === "popup-lyric-stop-update") { clearInterval(num); + postMessage("popup-lyric-update-ui"); num = null; } }; @@ -42,6 +43,21 @@ function PopupLyrics() { } }; + let workerIsRunning = null; + document.addEventListener("visibilitychange", (e) => { + if (e.target.hidden) { + if (!workerIsRunning) { + worker.postMessage("popup-lyric-request-update"); + workerIsRunning = true; + } + } else { + if (workerIsRunning) { + worker.postMessage("popup-lyric-stop-update"); + workerIsRunning = false; + } + } + }); + const LyricUtils = { normalize(s, emptySymbol = true) { const result = s @@ -457,7 +473,6 @@ function PopupLyrics() { try { const data = await service.call(info); - console.log(data); sharedData = data; CACHE[info.uri] = sharedData; @@ -765,7 +780,6 @@ function PopupLyrics() { ctx.restore(); } - let workerIsRunning = null; let timeout = null; async function tick(options) { @@ -801,17 +815,7 @@ function PopupLyrics() { return; } - if (document.hidden) { - if (!workerIsRunning) { - worker.postMessage("popup-lyric-request-update"); - workerIsRunning = true; - } - } else { - if (workerIsRunning) { - worker.postMessage("popup-lyric-stop-update"); - workerIsRunning = false; - } - + if (!document.hidden) { requestAnimationFrame(() => tick(options)); } } @@ -910,7 +914,6 @@ button.btn:disabled { } #popup-config-container input { width: 100%; - margin-top: 10px; padding: 0 5px; height: 32px; border: 0; From c99cdf6673e1a81a64a426117538b19c042e4f3e Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 1 Mar 2025 00:22:26 +0100 Subject: [PATCH 119/146] fix: remove bypass flag after checking for admin --- spicetify.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spicetify.go b/spicetify.go index 3ba3f2ca9b..a93fc0ea05 100644 --- a/spicetify.go +++ b/spicetify.go @@ -123,6 +123,13 @@ func init() { os.Exit(1) } + for i, flag := range flags { + if flag == "--bypass-admin" { + flags = append(flags[:i], flags[i+1:]...) + break + } + } + utils.MigrateConfigFolder() utils.MigrateFolders() cmd.InitConfig(quiet) From e692acab3d1d2da7ce91352b478c1b91f62baf3e Mon Sep 17 00:00:00 2001 From: Marco Vincenzi <95416525+MarcoZVincenzi@users.noreply.github.com> Date: Mon, 17 Mar 2025 16:58:19 +0100 Subject: [PATCH 120/146] docs(lyrics-plus): update readme to warn Genius is disabled (#3344) --- CustomApps/lyrics-plus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CustomApps/lyrics-plus/README.md b/CustomApps/lyrics-plus/README.md index e2b9401298..f5590d87bc 100644 --- a/CustomApps/lyrics-plus/README.md +++ b/CustomApps/lyrics-plus/README.md @@ -7,7 +7,7 @@ Show current track lyrics. Current lyrics providers: - Internal Spotify lyrics service. - Netease: From Chinese developers and users. Provides karaoke and synced lyrics. - Musixmatch: A company from Italy. Provided synced lyrics. -- Genius: Provide unsynced lyrics but with description/insight from artists themselve. +- Genius: Provides unsynced lyrics but with description/insight from artists themselves (Disabled and cannot be used as a provider on `1.2.31` and higher). ![kara](./kara.png) From 99645b6607f126029f83261d11daf25da6bb15bc Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 17 Mar 2025 19:58:59 +0100 Subject: [PATCH 121/146] fix(wrapper): left topbar icons & run scroll fix on `1.2.56` and lower --- jsHelper/spicetifyWrapper.js | 89 ++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index aa68a5fec1..4e5c17edd5 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -306,8 +306,55 @@ window.Spicetify = { Platform: {}, }; +(function waitForPlatform() { + if (!Spicetify._platform) { + setTimeout(waitForPlatform, 50); + return; + } + const { _platform } = Spicetify; + for (const key of Object.keys(_platform)) { + if (key.startsWith("get") && typeof _platform[key] === "function") { + Spicetify.Platform[key.slice(3)] = _platform[key](); + } else { + Spicetify.Platform[key] = _platform[key]; + } + } +})(); + +(function addMissingPlatformAPIs() { + if (!Spicetify.Platform?.version && !Spicetify.Platform?.Registry) { + setTimeout(addMissingPlatformAPIs, 50); + return; + } + const version = Spicetify.Platform.version.split(".").map((i) => Number.parseInt(i)); + if (version[0] === 1 && version[1] === 2 && version[2] < 38) return; + + for (const [key, _] of Spicetify.Platform.Registry._map.entries()) { + if (typeof key?.description !== "string" || !key?.description.endsWith("API")) continue; + const symbolName = key.description; + if (Object.hasOwn(Spicetify.Platform, symbolName)) continue; + const resolvedAPI = Spicetify.Platform.Registry.resolve(key); + if (!resolvedAPI) { + console.warn(`[spicetifyWrapper] Failed to resolve PlatformAPI from Registry: ${symbolName}`); + continue; + } + + Spicetify.Platform[symbolName] = resolvedAPI; + console.debug(`[spicetifyWrapper] Resolved PlatformAPI from Registry: ${symbolName}`); + } +})(); + // Based on https://blog.aziz.tn/2025/01/spotify-fix-lagging-issue-on-scrolling.html function applyScrollingFix() { + if (!Spicetify.Platform?.version) { + setTimeout(applyScrollingFix, 50); + return; + } + + // Run only for 1.2.56 and lower + const version = Spicetify.Platform.version.split(".").map((i) => Number.parseInt(i)); + if (version[1] >= 2 && version[2] >= 57) return; + const scrollableElements = Array.from(document.querySelectorAll("*")).filter((el) => { if ( el.id === "context-menu" || @@ -351,44 +398,6 @@ window.addEventListener("popstate", () => { applyScrollingFix(); -(function waitForPlatform() { - if (!Spicetify._platform) { - setTimeout(waitForPlatform, 50); - return; - } - const { _platform } = Spicetify; - for (const key of Object.keys(_platform)) { - if (key.startsWith("get") && typeof _platform[key] === "function") { - Spicetify.Platform[key.slice(3)] = _platform[key](); - } else { - Spicetify.Platform[key] = _platform[key]; - } - } -})(); - -(function addMissingPlatformAPIs() { - if (!Spicetify.Platform?.version && !Spicetify.Platform?.Registry) { - setTimeout(addMissingPlatformAPIs, 50); - return; - } - const version = Spicetify.Platform.version.split(".").map((i) => Number.parseInt(i)); - if (version[0] === 1 && version[1] === 2 && version[2] < 38) return; - - for (const [key, _] of Spicetify.Platform.Registry._map.entries()) { - if (typeof key?.description !== "string" || !key?.description.endsWith("API")) continue; - const symbolName = key.description; - if (Object.hasOwn(Spicetify.Platform, symbolName)) continue; - const resolvedAPI = Spicetify.Platform.Registry.resolve(key); - if (!resolvedAPI) { - console.warn(`[spicetifyWrapper] Failed to resolve PlatformAPI from Registry: ${symbolName}`); - continue; - } - - Spicetify.Platform[symbolName] = resolvedAPI; - console.debug(`[spicetifyWrapper] Resolved PlatformAPI from Registry: ${symbolName}`); - } -})(); - (async function addProxyCosmos() { if (!Spicetify.Player.origin?._cosmos && !Spicetify.Platform?.Registry) { setTimeout(addProxyCosmos, 50); @@ -413,7 +422,7 @@ applyScrollingFix(); if (typeof internalFetch !== "function" || !allowedMethodsSet.has(prop)) return internalFetch; const version = Spicetify.Platform.version.split(".").map((i) => Number.parseInt(i)); - if (version[0] === 1 && version[1] === 2 && version[2] < 31) return internalFetch; + if (version[1] >= 2 && version[2] < 31) return internalFetch; return async function (url, body) { const urlObj = new URL(url); @@ -2247,7 +2256,7 @@ Spicetify.Topbar = (() => { function waitForTopbarMounted() { const globalHistoryButtons = document.querySelector(".main-globalNav-historyButtons"); leftGeneratedClassName = document.querySelector( - ".main-topBar-historyButtons .main-topBar-button, .main-globalNav-historyButtons .main-globalNav-icon" + ".main-topBar-historyButtons .main-topBar-button, .main-globalNav-historyButtons .main-globalNav-icon, .main-globalNav-historyButtons .e-9640-button-tertiary--condensed" )?.className; rightGeneratedClassName = document.querySelector( ".main-topBar-container .main-topBar-buddyFeed, .main-actionButtons .main-topBar-buddyFeed, .main-actionButtons .main-globalNav-buddyFeed" From 9cd0589bc5a34b0aa6aad639d82b6e0200d1476a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:19:26 +0100 Subject: [PATCH 122/146] chore(deps): bump golang.org/x/net from 0.35.0 to 0.37.0 (#3342) --- go.mod | 6 +++--- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 106baaedf4..16122546b2 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module github.com/spicetify/cli -go 1.21 +go 1.24.1 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.14 - golang.org/x/net v0.35.0 + golang.org/x/net v0.37.0 + golang.org/x/sys v0.31.0 ) require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/sys v0.30.0 // indirect ) diff --git a/go.sum b/go.sum index cd4674eb33..c7ef2f6cd0 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 4da1910704a8018ddcc16b25a22d6dd5bd8e9af5 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 29 Mar 2025 01:20:27 +0100 Subject: [PATCH 123/146] chore(cmd/apply): correct grammar fixes #3360 --- src/cmd/apply.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/apply.go b/src/cmd/apply.go index 89d463b844..ac1627405b 100644 --- a/src/cmd/apply.go +++ b/src/cmd/apply.go @@ -202,7 +202,7 @@ func CheckStates() { utils.PrintWarning("Spotify version and backup version are mismatched.") if spotStat.IsMixed() { - utils.PrintInfo(`Spotify client possibly just had an new update.`) + utils.PrintInfo(`Spotify client possibly just had a new update.`) utils.PrintInfo(`Please run "spicetify backup apply".`) } else if spotStat.IsStock() { utils.PrintInfo(`Spotify client is in stock state.`) From 01647436eac30b7599e33f575d313ff3666cb85a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 01:40:34 +0200 Subject: [PATCH 124/146] chore(deps): bump golang.org/x/net from 0.37.0 to 0.38.0 (#3359) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 +-- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 16122546b2..55834bef0c 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,10 @@ module github.com/spicetify/cli go 1.24.1 - require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.14 - golang.org/x/net v0.37.0 + golang.org/x/net v0.38.0 golang.org/x/sys v0.31.0 ) diff --git a/go.sum b/go.sum index c7ef2f6cd0..d736f385c2 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From 465f6e36d02d3b142a871856b3ab7236fa31c263 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 5 Apr 2025 17:42:45 +0200 Subject: [PATCH 125/146] fix: update `resizeHost` selector & `Topbar` button styling --- CustomApps/lyrics-plus/TabBar.js | 5 +++-- CustomApps/reddit/TabBar.js | 5 +++-- jsHelper/spicetifyWrapper.js | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CustomApps/lyrics-plus/TabBar.js b/CustomApps/lyrics-plus/TabBar.js index b807edeb27..0f4746e5a6 100644 --- a/CustomApps/lyrics-plus/TabBar.js +++ b/CustomApps/lyrics-plus/TabBar.js @@ -63,8 +63,9 @@ const TabBarMore = react.memo(({ items, switchTo, lockIn }) => { }); const TopBarContent = ({ links, activeLink, lockLink, switchCallback, lockCallback }) => { - const resizeHost = - document.querySelector(".Root__main-view .os-resize-observer-host") ?? document.querySelector(".Root__main-view .os-size-observer"); + const resizeHost = document.querySelector( + ".Root__main-view .os-resize-observer-host, .Root__main-view .os-size-observer, .Root__main-view .main-view-container__scroll-node" + ); const [windowSize, setWindowSize] = useState(resizeHost.clientWidth); const resizeHandler = () => setWindowSize(resizeHost.clientWidth); diff --git a/CustomApps/reddit/TabBar.js b/CustomApps/reddit/TabBar.js index bb146873be..7eaf636327 100644 --- a/CustomApps/reddit/TabBar.js +++ b/CustomApps/reddit/TabBar.js @@ -48,8 +48,9 @@ const TabBarMore = react.memo(({ items, switchTo }) => { }); const TopBarContent = ({ links, activeLink, switchCallback }) => { - const resizeHost = - document.querySelector(".Root__main-view .os-resize-observer-host") ?? document.querySelector(".Root__main-view .os-size-observer"); + const resizeHost = document.querySelector( + ".Root__main-view .os-resize-observer-host, .Root__main-view .os-size-observer, .Root__main-view .main-view-container__scroll-node" + ); const [windowSize, setWindowSize] = useState(resizeHost.clientWidth); const resizeHandler = () => setWindowSize(resizeHost.clientWidth); diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 4e5c17edd5..3623462eeb 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -2256,7 +2256,7 @@ Spicetify.Topbar = (() => { function waitForTopbarMounted() { const globalHistoryButtons = document.querySelector(".main-globalNav-historyButtons"); leftGeneratedClassName = document.querySelector( - ".main-topBar-historyButtons .main-topBar-button, .main-globalNav-historyButtons .main-globalNav-icon, .main-globalNav-historyButtons .e-9640-button-tertiary--condensed" + ".main-topBar-historyButtons .main-topBar-button, .main-globalNav-historyButtons .main-globalNav-icon, .main-globalNav-historyButtons [data-encore-id='buttonTertiary']" )?.className; rightGeneratedClassName = document.querySelector( ".main-topBar-container .main-topBar-buddyFeed, .main-actionButtons .main-topBar-buddyFeed, .main-actionButtons .main-globalNav-buddyFeed" From bbfd86528f5072606c6ea548ef213ef471d3b736 Mon Sep 17 00:00:00 2001 From: MaciejPel <67285453+MaciejPel@users.noreply.github.com> Date: Sat, 12 Apr 2025 14:28:27 +0200 Subject: [PATCH 126/146] fix: add `$lineClamp` key to styled components processing (#3370) --- jsHelper/spicetifyWrapper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index 3623462eeb..e6e13fc714 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -1205,6 +1205,7 @@ Spicetify._getStyledClassName = (args, component) => { "$buttonSize", "$position", "$iconSize", + "$lineClamp", ]; const customKeys = ["blocksize"]; const customExactKeys = ["$padding", "$paddingBottom", "paddingBottom", "padding"]; From d1a5485033d2c455a3d48c0e5847d73b91233e8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:59:04 +0200 Subject: [PATCH 127/146] chore(deps): bump golang.org/x/net from 0.38.0 to 0.39.0 (#3368) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 +++-- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 55834bef0c..f364993e76 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,12 @@ module github.com/spicetify/cli go 1.24.1 + require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.14 - golang.org/x/net v0.38.0 - golang.org/x/sys v0.31.0 + golang.org/x/net v0.39.0 + golang.org/x/sys v0.32.0 ) require ( diff --git a/go.sum b/go.sum index d736f385c2..5ecf1486eb 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 0a9a7ff37379d572ef1082d4d94eb221c4ba7ba2 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sun, 20 Apr 2025 03:58:44 +0200 Subject: [PATCH 128/146] fix: add support for Spotify `1.2.62` --- jsHelper/spicetifyWrapper.js | 3 ++- src/cmd/cmd.go | 3 +++ src/preprocess/preprocess.go | 10 +++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/jsHelper/spicetifyWrapper.js b/jsHelper/spicetifyWrapper.js index e6e13fc714..85fc9fa8a5 100644 --- a/jsHelper/spicetifyWrapper.js +++ b/jsHelper/spicetifyWrapper.js @@ -684,7 +684,6 @@ applyScrollingFix(); Routes: functionModules.find((m) => m.toString().match(/\([\w$]+\)\{let\{children:[\w$]+,location:[\w$]+\}=[\w$]+/)), Route: functionModules.find((m) => m.toString().match(/^function [\w$]+\([\w$]+\)\{\(0,[\w$]+\.[\w$]+\)\(\!1\)\}$/)), StoreProvider: functionModules.find((m) => m.toString().includes("notifyNestedSubs") && m.toString().includes("serverState")), - Navigation: exportedMemoFRefs.find((m) => m.type.render.toString().includes("navigationalRoot")), ScrollableContainer: functionModules.find((m) => m.toString().includes("scrollLeft") && m.toString().includes("showButtons")), IconComponent: reactComponentsUI.Icon, ...Object.fromEntries(menus), @@ -726,6 +725,8 @@ applyScrollingFix(); }); if (!Spicetify.ContextMenuV2._context) Spicetify.ContextMenuV2._context = Spicetify.React.createContext({}); + if (!Spicetify.ReactComponent.Navigation) + Spicetify.ReactComponent.Navigation = exportedMemoFRefs.find((m) => m.type.render.toString().includes("navigationalRoot")); (function waitForChunks() { const listOfComponents = [ diff --git a/src/cmd/cmd.go b/src/cmd/cmd.go index 697ba1f772..f9bc8326b2 100644 --- a/src/cmd/cmd.go +++ b/src/cmd/cmd.go @@ -70,6 +70,9 @@ func InitPaths() { os.Exit(1) } utils.PrintError(`Cannot detect Spotify location. Please manually set "spotify_path" in config-xpui.ini`) + if runtime.GOOS == "windows" { + utils.PrintInfo("Please make sure Spotify is not installed via Microsoft Store. If it is, please uninstall it and install Spotify with their web installer.") + } os.Exit(1) } diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index ac0f084f11..3b40fc682f 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -504,7 +504,7 @@ func exposeAPIs_main(input string) string { // Spicetify._platform utils.Replace( &input, - `(setTitlebarHeight[\w(){}<>:.,&$!=;""?!#% ]+)(\{version:[a-zA-Z_\$][\w\$]*,)`, + `((?:setTitlebarHeight|registerFactory)[\w(){}<>:.,&$!=;""?!#% ]+)(\{version:[a-zA-Z_\$][\w\$]*,)`, func(submatches ...string) string { return fmt.Sprintf("%sSpicetify._platform=%s", submatches[1], submatches[2]) }) @@ -572,6 +572,14 @@ func exposeAPIs_main(input string) string { return fmt.Sprintf("Spicetify.Snackbar.enqueueImageSnackbar=%s", submatches[0]) }) + utils.ReplaceOnce( + &input, + `(;const [\w\d]+=)((?:\(0,[\w\d]+\.memo\))[\(\d,\w\.\){:}=]+\=[\d\w]+\.[\d\w]+\.getLocaleForURLPath\(\))`, + func(submatches ...string) string { + fmt.Println(submatches) + return fmt.Sprintf("%sSpicetify.ReactComponent.Navigation=%s", submatches[1], submatches[2]) + }) + // Menu hook 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]) From b523a429d622c699866a7dcce1648b7f636b461e Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 21 Apr 2025 23:40:17 +0200 Subject: [PATCH 129/146] feat: add support for embedded xpui in v8 snapshot --- go.mod | 1 + go.sum | 2 + src/apply/apply.go | 20 +++++++-- src/cmd/backup.go | 26 ++++++----- src/preprocess/preprocess.go | 69 ++++++++++++++++++++++++++--- src/utils/file-utils.go | 86 ++++++++++++++++++++++++++++++++++++ src/utils/utils.go | 9 ++++ 7 files changed, 194 insertions(+), 19 deletions(-) create mode 100644 src/utils/file-utils.go diff --git a/go.mod b/go.mod index f364993e76..8e5bf88682 100644 --- a/go.mod +++ b/go.mod @@ -12,4 +12,5 @@ require ( require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.7.1 // indirect + golang.org/x/text v0.24.0 // indirect ) diff --git a/go.sum b/go.sum index 5ecf1486eb..10541dd41b 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/apply/apply.go b/src/apply/apply.go index 2d910f62e0..8d9dcd49f6 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -27,15 +27,19 @@ type Flag struct { // AdditionalOptions . func AdditionalOptions(appsFolderPath string, flags Flag) { + jsModifiers := []func(path string, flags Flag){ + insertExpFeatures, + insertSidebarConfig, + insertHomeConfig, + } filesToModified := map[string][]func(path string, flags Flag){ filepath.Join(appsFolderPath, "xpui", "index.html"): { htmlMod, }, - filepath.Join(appsFolderPath, "xpui", "xpui.js"): { + filepath.Join(appsFolderPath, "xpui", "xpui.js"): jsModifiers, + filepath.Join(appsFolderPath, "xpui", "xpui-modules.js"): jsModifiers, + filepath.Join(appsFolderPath, "xpui", "xpui-snapshot.js"): { insertCustomApp, - insertExpFeatures, - insertSidebarConfig, - insertHomeConfig, }, filepath.Join(appsFolderPath, "xpui", "home-v2.js"): { insertHomeConfig, @@ -57,8 +61,10 @@ func AdditionalOptions(appsFolderPath string, flags Flag) { spotifyPatch, _ = strconv.Atoi(verParts[2]) } + filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")] = append(filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")], insertCustomApp) if spotifyMajor >= 1 && spotifyMinor >= 2 && spotifyPatch >= 57 { filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")] = append(filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")], insertExpFeatures) + filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui-modules.js")] = append(filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui-modukes.js")], insertExpFeatures) } else { filesToModified[filepath.Join(appsFolderPath, "xpui", "vendor~xpui.js")] = []func(string, Flag){insertExpFeatures} } @@ -187,6 +193,12 @@ func htmlMod(htmlPath string, flags Flag) { } utils.ModifyFile(htmlPath, func(content string) string { + utils.Replace( + &content, + ``, + func(submatches ...string) string { + return `` + }) utils.Replace( &content, `<\!-- spicetify helpers -->`, diff --git a/src/cmd/backup.go b/src/cmd/backup.go index aaaed323d7..75ef55c113 100644 --- a/src/cmd/backup.go +++ b/src/cmd/backup.go @@ -65,16 +65,22 @@ Modded Spotify cannot be launched using original Shortcut/Start menu tile. To co utils.PrintBold("Preprocessing:") - preprocess.Start( - spicetifyVersion, - rawFolder, - preprocess.Flag{ - DisableSentry: preprocSection.Key("disable_sentry").MustBool(false), - DisableLogging: preprocSection.Key("disable_ui_logging").MustBool(false), - RemoveRTL: preprocSection.Key("remove_rtl_rule").MustBool(false), - ExposeAPIs: preprocSection.Key("expose_apis").MustBool(false), - SpotifyVer: utils.GetSpotifyVersion(prefsPath)}, - ) + spotifyBasePath := spotifyPath + if spotifyBasePath == "" { + utils.PrintError("Spotify installation path not found. Cannot preprocess V8 snapshots.") + } else { + preprocess.Start( + spicetifyVersion, + spotifyBasePath, + rawFolder, + preprocess.Flag{ + DisableSentry: preprocSection.Key("disable_sentry").MustBool(false), + DisableLogging: preprocSection.Key("disable_ui_logging").MustBool(false), + RemoveRTL: preprocSection.Key("remove_rtl_rule").MustBool(false), + ExposeAPIs: preprocSection.Key("expose_apis").MustBool(false), + SpotifyVer: utils.GetSpotifyVersion(prefsPath)}, + ) + } utils.PrintGreen("OK") err = utils.Copy(rawFolder, themedFolder, true, []string{".html", ".js", ".css"}) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 3b40fc682f..cd8e896664 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -10,6 +10,7 @@ import ( "path" "path/filepath" "regexp" + "runtime" "strconv" "strings" @@ -60,8 +61,7 @@ func readLocalCssMap(cssTranslationMap *map[string]string) error { return nil } -// Start preprocessing apps assets in extractedAppPath -func Start(version string, extractedAppsPath string, flags Flag) { +func Start(version string, spotifyBasePath string, extractedAppsPath string, flags Flag) { appPath := filepath.Join(extractedAppsPath, "xpui") var cssTranslationMap = make(map[string]string) // readSourceMapAndGenerateCSSMap(appPath) @@ -95,14 +95,66 @@ func Start(version string, extractedAppsPath string, flags Flag) { spotifyPatch, _ = strconv.Atoi(verParts[2]) } + frameworkResourcesPath := "" + switch runtime.GOOS { + case "darwin": + frameworkResourcesPath = filepath.Join(spotifyBasePath, "Contents", "Frameworks", "Chromium Embedded Framework.framework", "Resources") + case "windows": + frameworkResourcesPath = spotifyBasePath + case "linux": + frameworkResourcesPath = spotifyBasePath + default: + utils.PrintWarning("Unsupported OS for V8 snapshot finding: " + runtime.GOOS) + } + + if frameworkResourcesPath != "" { + files, err := os.ReadDir(frameworkResourcesPath) + if err != nil { + utils.PrintWarning(fmt.Sprintf("Could not read directory %s for V8 snapshots: %v", frameworkResourcesPath, err)) + } else { + for _, file := range files { + if !file.IsDir() && strings.HasPrefix(file.Name(), "v8_context_snapshot") && strings.HasSuffix(file.Name(), ".bin") { + binFilePath := filepath.Join(frameworkResourcesPath, file.Name()) + utils.PrintInfo("Processing V8 snapshot file: " + binFilePath) + + startMarker := []byte("var __webpack_modules__={") + endMarker := []byte("xpui-modules.js.map") + + embeddedString, _, _, err := utils.ReadStringFromUTF16Binary(binFilePath, startMarker, endMarker) + if err != nil { + utils.PrintWarning(fmt.Sprintf("Could not process %s: %v", binFilePath, err)) + utils.PrintInfo("You can ignore this warning if you're on a Spotify version that didn't yet add xpui modules to the V8 snapshot") + continue + } + + err = utils.CreateFile(filepath.Join(appPath, "xpui-modules.js"), embeddedString) + if err != nil { + utils.PrintWarning(fmt.Sprintf("Could not create xpui-modules.js: %v", err)) + continue + } else { + utils.PrintInfo("Extracted V8 snapshot blob (remaining xpui) to xpui-modules.js") + } + } + } + } + } + filepath.Walk(appPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + fmt.Printf("Error accessing path %q: %v\n", path, err) + return err + } + if info.IsDir() { + return nil + } + fileName := info.Name() extension := filepath.Ext(fileName) switch extension { case ".js": utils.ModifyFile(path, func(content string) string { - if flags.DisableSentry && fileName == "xpui.js" { + if flags.DisableSentry && (fileName == "xpui.js" || fileName == "xpui-snapshot.js") { content = disableSentry(content) } @@ -112,6 +164,9 @@ func Start(version string, extractedAppsPath string, flags Flag) { if flags.ExposeAPIs { switch fileName { + case "xpui-modules.js", "xpui-snapshot.js": + content = exposeAPIs_main(content) + content = exposeAPIs_vendor(content) case "xpui.js": content = exposeAPIs_main(content) if spotifyMajor >= 1 && spotifyMinor >= 2 && spotifyPatch >= 57 { @@ -576,7 +631,6 @@ func exposeAPIs_main(input string) string { &input, `(;const [\w\d]+=)((?:\(0,[\w\d]+\.memo\))[\(\d,\w\.\){:}=]+\=[\d\w]+\.[\d\w]+\.getLocaleForURLPath\(\))`, func(submatches ...string) string { - fmt.Println(submatches) return fmt.Sprintf("%sSpicetify.ReactComponent.Navigation=%s", submatches[1], submatches[2]) }) @@ -585,7 +639,12 @@ func exposeAPIs_main(input string) string { return fmt.Sprintf("%s[Spicetify.ContextMenuV2.renderItems(),%s].flat()", submatches[1], submatches[2]) }) - croppedInput := utils.FindFirstMatch(input, `.*value:"contextmenu"`)[0] + inputContextMenu := utils.FindFirstMatch(input, `.*value:"contextmenu"`) + if len(inputContextMenu) == 0 { + return input + } + + croppedInput := inputContextMenu[0] react := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)\.useRef`)[1] candicates := utils.FindLastMatch(croppedInput, `\(\{[^}]*menu:([a-zA-Z_\$][\w\$]*),[^}]*trigger:([a-zA-Z_\$][\w\$]*),[^}]*triggerRef:([a-zA-Z_\$][\w\$]*)`) oldCandicates := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)=[\w_$]+\.menu[^}]*,([a-zA-Z_\$][\w\$]*)=[\w_$]+\.trigger[^}]*,([a-zA-Z_\$][\w\$]*)=[\w_$]+\.triggerRef`) diff --git a/src/utils/file-utils.go b/src/utils/file-utils.go new file mode 100644 index 0000000000..d0086f7d4b --- /dev/null +++ b/src/utils/file-utils.go @@ -0,0 +1,86 @@ +package utils + +import ( + "bytes" + "encoding/binary" + "fmt" + "os" + "unicode/utf16" +) + +func ReadStringFromUTF16Binary(inputFile string, startMarker []byte, endMarker []byte) (string, int, int, error) { + fileContent, err := os.ReadFile(inputFile) + if err != nil { + return "", -1, -1, fmt.Errorf("error reading file %s: %w", inputFile, err) + } + + isUTF16LE := false + if len(fileContent) >= 2 && fileContent[0] == 0xFF && fileContent[1] == 0xFE { + isUTF16LE = true + } + + if !isUTF16LE && len(fileContent) > 100 && fileContent[1] == 0x00 { + isUTF16LE = true + } + + var startIdx, endIdx int + var contentToSearch []byte + var searchStartMarker, searchEndMarker []byte + + if !isUTF16LE { + return "", -1, -1, fmt.Errorf("file is not in UTF-16LE format: %s", inputFile) + } + + contentToSearch = fileContent[2:] + searchStartMarker = encodeUTF16LE(startMarker) + searchEndMarker = encodeUTF16LE(endMarker) + + startIdx = bytes.Index(contentToSearch, searchStartMarker) + if startIdx == -1 { + return "", -1, -1, fmt.Errorf("start marker not found: %s", string(startMarker)) + } + + searchSpace := contentToSearch[startIdx+len(searchStartMarker):] + endIdx = bytes.Index(searchSpace, searchEndMarker) + if endIdx == -1 { + return "", -1, -1, fmt.Errorf("end marker not found after start index %d: %s", startIdx+len(searchStartMarker), string(endMarker)) + } + + stringContentBytes := contentToSearch[startIdx : startIdx+len(searchStartMarker)+endIdx+len(searchEndMarker)] + + decodedStringBytes, err := decodeUTF16LE(stringContentBytes) + if err != nil { + return "", -1, -1, fmt.Errorf("error decoding UTF-16LE content: %w", err) + } + + // Adjust indices to be byte offsets in the original file + originalStartIdx := 2 + startIdx + originalEndIdx := 2 + endIdx + len(stringContentBytes) + return string(decodedStringBytes), originalStartIdx, originalEndIdx, nil +} + +// Helper function to encode a byte slice (assumed UTF-8) to UTF-16LE +func encodeUTF16LE(data []byte) []byte { + utf16Bytes := utf16.Encode([]rune(string(data))) + byteSlice := make([]byte, len(utf16Bytes)*2) + for i, r := range utf16Bytes { + binary.LittleEndian.PutUint16(byteSlice[i*2:], r) + } + + return byteSlice +} + +// Helper function to decode a byte slice (UTF-16LE) to UTF-8 +func decodeUTF16LE(data []byte) ([]byte, error) { + if len(data)%2 != 0 { + return nil, fmt.Errorf("invalid UTF-16LE data length") + } + + uint16s := make([]uint16, len(data)/2) + for i := 0; i < len(data)/2; i++ { + uint16s[i] = binary.LittleEndian.Uint16(data[i*2:]) + } + + runes := utf16.Decode(uint16s) + return []byte(string(runes)), nil +} diff --git a/src/utils/utils.go b/src/utils/utils.go index c2658e2cf1..e4f32d18df 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -223,6 +223,15 @@ func ModifyFile(path string, repl func(string) string) { os.WriteFile(path, []byte(content), 0700) } +// CreateFile creates a file with given path and content. +func CreateFile(path string, content string) error { + err := os.WriteFile(path, []byte(content), 0600) + if err != nil { + return err + } + return nil +} + // GetSpotifyVersion . func GetSpotifyVersion(prefsPath string) string { pref, err := ini.Load(prefsPath) From 52e085916fdae953b87e8d62201c69b99f34e386 Mon Sep 17 00:00:00 2001 From: ririxi Date: Mon, 21 Apr 2025 23:50:30 +0200 Subject: [PATCH 130/146] chore(preprocess): remove unnecessary checking for dir --- src/preprocess/preprocess.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index cd8e896664..1bdf405105 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -144,9 +144,6 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla fmt.Printf("Error accessing path %q: %v\n", path, err) return err } - if info.IsDir() { - return nil - } fileName := info.Name() extension := filepath.Ext(fileName) From 8a9dd40a16aa88de8c85602748342592ecfea334 Mon Sep 17 00:00:00 2001 From: ririxi Date: Tue, 22 Apr 2025 22:22:04 +0200 Subject: [PATCH 131/146] fix: remove unused appending --- src/apply/apply.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/apply/apply.go b/src/apply/apply.go index 8d9dcd49f6..19b87864e7 100644 --- a/src/apply/apply.go +++ b/src/apply/apply.go @@ -64,7 +64,6 @@ func AdditionalOptions(appsFolderPath string, flags Flag) { filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")] = append(filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")], insertCustomApp) if spotifyMajor >= 1 && spotifyMinor >= 2 && spotifyPatch >= 57 { filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")] = append(filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui.js")], insertExpFeatures) - filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui-modules.js")] = append(filesToModified[filepath.Join(appsFolderPath, "xpui", "xpui-modukes.js")], insertExpFeatures) } else { filesToModified[filepath.Join(appsFolderPath, "xpui", "vendor~xpui.js")] = []func(string, Flag){insertExpFeatures} } From aaadb571150952d40623bb5f1e941e9ac1c772a1 Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 23 Apr 2025 00:13:58 +0200 Subject: [PATCH 132/146] feat: more logging shown to users --- go.mod | 13 +- go.sum | 112 +++- src/cmd/apply.go | 22 +- src/cmd/backup.go | 24 +- src/cmd/watch.go | 14 +- src/preprocess/preprocess.go | 1163 +++++++++++++++++++++------------- src/utils/config.go | 10 +- 7 files changed, 873 insertions(+), 485 deletions(-) diff --git a/go.mod b/go.mod index 8e5bf88682..0488083045 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,23 @@ go 1.24.1 require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.14 + github.com/pterm/pterm v0.12.80 golang.org/x/net v0.39.0 golang.org/x/sys v0.32.0 ) require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.1.0 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/gookit/color v1.5.4 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/stretchr/testify v1.7.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/term v0.31.0 // indirect golang.org/x/text v0.24.0 // indirect ) diff --git a/go.sum b/go.sum index 10541dd41b..e9a14dda3f 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,127 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg= +github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/cmd/apply.go b/src/cmd/apply.go index ac1627405b..e2a3e0f415 100644 --- a/src/cmd/apply.go +++ b/src/cmd/apply.go @@ -177,9 +177,9 @@ func RefreshExtensions(list ...string) { if len(list) > 0 { pushExtensions("", list...) - utils.PrintSuccess(utils.PrependTime("All extensions are updated.")) + utils.PrintSuccess("All extensions are updated") } else { - utils.PrintError("No extension to update.") + utils.PrintError("No extension to update") } } @@ -192,9 +192,9 @@ func CheckStates() { if backStat.IsEmpty() { if spotStat.IsBackupable() { - utils.PrintError(`You haven't backed up. Run "spicetify backup apply".`) + utils.PrintError(`You haven't backed up. Run "spicetify backup apply"`) } else { - utils.PrintError(`You haven't backed up and Spotify cannot be backed up at this state. Please re-install Spotify then run "spicetify backup apply".`) + utils.PrintError(`You haven't backed up and Spotify cannot be backed up at this state. Please re-install Spotify then run "spicetify backup apply"`) } os.Exit(1) @@ -202,13 +202,13 @@ func CheckStates() { utils.PrintWarning("Spotify version and backup version are mismatched.") if spotStat.IsMixed() { - utils.PrintInfo(`Spotify client possibly just had a new update.`) - utils.PrintInfo(`Please run "spicetify backup apply".`) + utils.PrintInfo(`Spotify client possibly just had a new update`) + utils.PrintInfo(`Please run "spicetify backup apply"`) } else if spotStat.IsStock() { - utils.PrintInfo(`Spotify client is in stock state.`) - utils.PrintInfo(`Please run "spicetify backup apply".`) + utils.PrintInfo(`Spotify client is in stock state`) + utils.PrintInfo(`Please run "spicetify backup apply"`) } else { - utils.PrintInfo(`Spotify cannot be backed up at this state. Please re-install Spotify then run "spicetify backup apply".`) + utils.PrintInfo(`Spotify cannot be backed up at this state. Please re-install Spotify then run "spicetify backup apply"`) } os.Exit(1) @@ -243,7 +243,7 @@ func pushExtensions(destExt string, list ...string) { } extPath, err = utils.GetExtensionPath(extName) if err != nil { - utils.PrintError(`Extension "` + extName + `" not found.`) + utils.PrintError(`Extension "` + extName + `" not found`) continue } } @@ -281,7 +281,7 @@ func RefreshApps(list ...string) { customAppPath, err := utils.GetCustomAppPath(app) if err != nil { - utils.PrintError(`Custom app "` + app + `" not found.`) + utils.PrintError(`Custom app "` + app + `" not found`) continue } diff --git a/src/cmd/backup.go b/src/cmd/backup.go index 75ef55c113..37abc2a037 100644 --- a/src/cmd/backup.go +++ b/src/cmd/backup.go @@ -16,9 +16,9 @@ import ( // extracted apps' assets func Backup(spicetifyVersion string) { if isAppX { - utils.PrintInfo(`You are using Spotify Windows Store version, which is only partly supported. -Stop using Spicetify with Windows Store version unless you absolutely CANNOT install normal Spotify from installer. -Modded Spotify cannot be launched using original Shortcut/Start menu tile. To correctly launch Spotify with modification, please make a desktop shortcut that execute "spicetify auto". After that, you can change its icon, pin to start menu or put in startup folder.`) + utils.PrintInfo(`You are using Spotify Windows Store version, which is only partly supported +Stop using Spicetify with Windows Store version unless you absolutely CANNOT install normal Spotify from installer +Modded Spotify cannot be launched using original Shortcut/Start menu tile. To correctly launch Spotify with modification, please make a desktop shortcut that execute "spicetify auto". After that, you can change its icon, pin to start menu or put in startup folder`) if !ReadAnswer("Continue backing up anyway? [y/N]: ", false, true) { os.Exit(1) } @@ -26,7 +26,7 @@ Modded Spotify cannot be launched using original Shortcut/Start menu tile. To co backupVersion := backupSection.Key("version").MustString("") backStat := backupstatus.Get(prefsPath, backupFolder, backupVersion) if !backStat.IsEmpty() { - utils.PrintInfo("There is available backup.") + utils.PrintInfo("There is available backup") utils.PrintInfo("Clear current backup:") spotStat := spotifystatus.Get(appPath) @@ -34,8 +34,8 @@ Modded Spotify cannot be launched using original Shortcut/Start menu tile. To co clearBackup() } else { - utils.PrintWarning(`After clearing backup, Spotify cannot be backed up again.`) - utils.PrintInfo(`Please restore first then backup, run "spicetify restore backup" or re-install Spotify then run "spicetify backup".`) + utils.PrintWarning(`After clearing backup, Spotify cannot be backed up again`) + utils.PrintInfo(`Please restore first then backup, run "spicetify restore backup" or re-install Spotify then run "spicetify backup"`) os.Exit(1) } } @@ -55,7 +55,7 @@ Modded Spotify cannot be launched using original Shortcut/Start menu tile. To co if totalApp > 0 { utils.PrintGreen("OK") } else { - utils.PrintError("Cannot backup app files. Reinstall Spotify and try again.") + utils.PrintError("Cannot backup app files. Reinstall Spotify and try again") os.Exit(1) } @@ -67,7 +67,7 @@ Modded Spotify cannot be launched using original Shortcut/Start menu tile. To co spotifyBasePath := spotifyPath if spotifyBasePath == "" { - utils.PrintError("Spotify installation path not found. Cannot preprocess V8 snapshots.") + utils.PrintError("Spotify installation path not found. Cannot preprocess V8 snapshots") } else { preprocess.Start( spicetifyVersion, @@ -81,7 +81,7 @@ Modded Spotify cannot be launched using original Shortcut/Start menu tile. To co SpotifyVer: utils.GetSpotifyVersion(prefsPath)}, ) } - utils.PrintGreen("OK") + utils.PrintSuccess("Preprocessing completed") err = utils.Copy(rawFolder, themedFolder, true, []string{".html", ".js", ".css"}) if err != nil { @@ -89,7 +89,7 @@ Modded Spotify cannot be launched using original Shortcut/Start menu tile. To co } preprocess.StartCSS(themedFolder) - utils.PrintGreen("OK") + utils.PrintSuccess("CSS replacing completed") backupSection.Key("version").SetValue(utils.GetSpotifyVersion(prefsPath)) backupSection.Key("with").SetValue(spicetifyVersion) @@ -103,7 +103,7 @@ func Clear() { spotStat := spotifystatus.Get(appPath) if !spotStat.IsBackupable() { - utils.PrintWarning("Before clearing backup, please restore or re-install Spotify to stock state.") + utils.PrintWarning("Before clearing backup, please restore or re-install Spotify to stock state") os.Exit(1) } @@ -144,5 +144,5 @@ func Restore() { utils.Fatal(err) } - utils.PrintSuccess("Spotify is restored.") + utils.PrintSuccess("Spotify is restored") } diff --git a/src/cmd/watch.go b/src/cmd/watch.go index be1a06ebf4..d5fc151a3e 100644 --- a/src/cmd/watch.go +++ b/src/cmd/watch.go @@ -28,7 +28,7 @@ func Watch(liveUpdate bool) { } if len(themeFolder) == 0 { - utils.PrintError(`Config "current_theme" is blank. No theme asset to watch.`) + utils.PrintError(`Config "current_theme" is blank. No theme asset to watch`) os.Exit(1) } @@ -115,7 +115,7 @@ func WatchExtensions(extName []string, liveUpdate bool) { } if len(extPathList) == 0 { - utils.PrintError("No extension to watch.") + utils.PrintError("No extension to watch") os.Exit(1) } @@ -127,7 +127,7 @@ func WatchExtensions(extName []string, liveUpdate bool) { pushExtensions("", filePath) - utils.PrintSuccess(utils.PrependTime(`Extension "` + filePath + `" is updated.`)) + utils.PrintSuccess(utils.PrependTime(`Extension "` + filePath + `" is updated`)) }, autoReloadFunc) } @@ -152,7 +152,7 @@ func WatchCustomApp(appName []string, liveUpdate bool) { for _, v := range appNameList { appPath, err := utils.GetCustomAppPath(v) if err != nil { - utils.PrintError(`Custom app "` + v + `" not found.`) + utils.PrintError(`Custom app "` + v + `" not found`) continue } @@ -195,7 +195,7 @@ func WatchCustomApp(appName []string, liveUpdate bool) { RefreshApps(appName) - utils.PrintSuccess(utils.PrependTime(`Custom app "` + appName + `" is updated.`)) + utils.PrintSuccess(utils.PrependTime(`Custom app "` + appName + `" is updated`)) }, autoReloadFunc) } @@ -210,7 +210,7 @@ func isValidForWatching() bool { status := spotifystatus.Get(appDestPath) if !status.IsModdable() { - utils.PrintError(`You haven't applied. Run "spicetify apply" once before entering watch mode.`) + utils.PrintError(`You haven't applied. Run "spicetify apply" once before entering watch mode`) return false } @@ -229,7 +229,7 @@ func startDebugger() { autoReloadFunc = func() { if utils.SendReload(&debuggerURL) != nil { utils.PrintError("Could not Reload Spotify") - utils.PrintInfo(`Close Spotify and run watch command again.`) + utils.PrintInfo(`Close Spotify and run watch command again`) } else { utils.PrintSuccess("Spotify reloaded") } diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 1bdf405105..38696c07e1 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" + "github.com/pterm/pterm" "github.com/spicetify/cli/src/utils" ) @@ -25,11 +26,34 @@ type Flag struct { DisableLogging bool // RemoveRTL removes all Right-To-Left CSS rules to simplify CSS files. RemoveRTL bool - // ExposeAPIs leaks some Spotify's API, functions, objects to Spicetify global object. + // ExposeAPIs leaks Spotify's API, functions, objects to Spicetify global object. ExposeAPIs bool SpotifyVer string } +type Patch struct { + Name string + Regex string + Replacement func(submatches ...string) string + Once bool +} + +type patchReporter func(string) + +func applyPatches(input string, patches []Patch, report ...patchReporter) string { + for _, patch := range patches { + if len(report) > 0 && report[0] != nil { + report[0](fmt.Sprintf("%s patch", patch.Name)) + } + if patch.Once { + utils.ReplaceOnce(&input, patch.Regex, patch.Replacement) + } else { + utils.Replace(&input, patch.Regex, patch.Replacement) + } + } + return input +} + func readRemoteCssMap(tag string, cssTranslationMap *map[string]string) error { var cssMapURL string = "https://raw.githubusercontent.com/spicetify/cli/" + tag + "/css-map.json" cssMapResp, err := http.Get(cssMapURL) @@ -104,7 +128,7 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla case "linux": frameworkResourcesPath = spotifyBasePath default: - utils.PrintWarning("Unsupported OS for V8 snapshot finding: " + runtime.GOOS) + utils.PrintError("Unsupported OS for V8 snapshot finding: " + runtime.GOOS) } if frameworkResourcesPath != "" { @@ -130,28 +154,51 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla err = utils.CreateFile(filepath.Join(appPath, "xpui-modules.js"), embeddedString) if err != nil { utils.PrintWarning(fmt.Sprintf("Could not create xpui-modules.js: %v", err)) - continue + break } else { - utils.PrintInfo("Extracted V8 snapshot blob (remaining xpui) to xpui-modules.js") + utils.PrintSuccess("Extracted V8 snapshot blob (remaining xpui modules) to xpui-modules.js") + break } } } } } + var filesToPatch []string filepath.Walk(appPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - fmt.Printf("Error accessing path %q: %v\n", path, err) - return err + if err != nil || info.IsDir() { + return nil + } + ext := filepath.Ext(info.Name()) + if ext == ".js" || ext == ".css" || ext == ".html" { + filesToPatch = append(filesToPatch, path) } + return nil + }) + totalFiles := len(filesToPatch) + currentFile := 0 + + style := pterm.NewStyle(pterm.FgWhite, pterm.BgBlack) + bar, _ := pterm.DefaultProgressbar.WithTotal(totalFiles).WithTitle("Patching files...").WithTitleStyle(style).WithShowCount(true).Start() + printPatch := func(msg string) { + bar.UpdateTitle(msg) + } + for _, path := range filesToPatch { + info, err := os.Stat(path) + if err != nil { + continue + } fileName := info.Name() extension := filepath.Ext(fileName) + currentFile++ + switch extension { case ".js": utils.ModifyFile(path, func(content string) string { if flags.DisableSentry && (fileName == "xpui.js" || fileName == "xpui-snapshot.js") { + printPatch("Disable Sentry") content = disableSentry(content) } @@ -162,17 +209,18 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla if flags.ExposeAPIs { switch fileName { case "xpui-modules.js", "xpui-snapshot.js": - content = exposeAPIs_main(content) - content = exposeAPIs_vendor(content) + content = exposeAPIs_main(content, printPatch) + content = exposeAPIs_vendor(content, printPatch) case "xpui.js": - content = exposeAPIs_main(content) + content = exposeAPIs_main(content, printPatch) if spotifyMajor >= 1 && spotifyMinor >= 2 && spotifyPatch >= 57 { - content = exposeAPIs_vendor(content) + content = exposeAPIs_vendor(content, printPatch) } case "vendor~xpui.js": - content = exposeAPIs_vendor(content) + content = exposeAPIs_vendor(content, printPatch) } } + printPatch("CSS (JS): Patching our mappings into file") for k, v := range cssTranslationMap { utils.Replace(&content, k, func(submatches ...string) string { return v @@ -184,15 +232,18 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla }) case ".css": utils.ModifyFile(path, func(content string) string { + printPatch("CSS: Patching our mappings into file") for k, v := range cssTranslationMap { utils.Replace(&content, k, func(submatches ...string) string { return v }) } if flags.RemoveRTL { + printPatch("Remove RTL") content = removeRTL(content) } if fileName == "xpui.css" { + printPatch("Extra CSS Patch") content = content + ` .main-gridContainer-fixedWidth{grid-template-columns: repeat(auto-fill, var(--column-width));width: calc((var(--column-count) - 1) * var(--grid-gap)) + var(--column-count) * var(--column-width));}.main-cardImage-imageWrapper{background-color: var(--card-color, #333);border-radius: 6px;-webkit-box-shadow: 0 8px 24px rgba(0, 0, 0, .5);box-shadow: 0 8px 24px rgba(0, 0, 0, .5);padding-bottom: 100%;position: relative;width:100%;}.main-cardImage-image,.main-card-imagePlaceholder{height: 100%;left: 0;position: absolute;top: 0;width: 100%};.main-content-view{height:100%;} ` @@ -202,6 +253,7 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla case ".html": utils.ModifyFile(path, func(content string) string { + printPatch("Inject wrapper/CSS") var tags string tags += "\n" tags += "\n" @@ -218,8 +270,9 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla return content }) } - return nil - }) + + bar.Increment() + } } // StartCSS modifies all CSS files in extractedAppsPath to change @@ -233,144 +286,201 @@ func StartCSS(extractedAppsPath string) { } if filepath.Ext(info.Name()) == ".css" { - utils.ModifyFile(path, colorVariableReplace) + utils.ModifyFile(path, func(content string) string { + return colorVariableReplace(content) + }) } return nil }) } func colorVariableReplace(content string) string { - 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", func(submatches ...string) string { - return "var(--spice-card)" - }) - - utils.Replace(&content, "#121212", func(submatches ...string) string { - return "var(--spice-main)" - }) - utils.Replace(&content, `#(242424|1f1f1f)`, func(submatches ...string) string { - return "var(--spice-main-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", func(submatches ...string) string { - return "var(--spice-sidebar)" - }) - utils.Replace(&content, "#000000", func(submatches ...string) string { - return "var(--spice-sidebar)" - }) - - 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", func(submatches ...string) string { - return "var(--spice-subtext)" - }) - utils.Replace(&content, "#a7a7a7", func(submatches ...string) string { - return "var(--spice-subtext)" - }) - - 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", 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", func(submatches ...string) string { - return "var(--spice-button-disabled)" - }) - - 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", func(submatches ...string) string { - return "var(--spice-misc)" - }) - - 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", 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\.]+)\)`, 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]) - }) + patches := []Patch{ + { + Name: "CSS: --spice-player", + Regex: "#(181818|212121)", + Replacement: func(submatches ...string) string { + return "var(--spice-player)" + }, + }, + { + Name: "CSS: --spice-card", + Regex: "#282828", + Replacement: func(submatches ...string) string { + return "var(--spice-card)" + }, + }, + { + Name: "CSS: --spice-main", + Regex: "#121212", + Replacement: func(submatches ...string) string { + return "var(--spice-main)" + }, + }, + { + Name: "CSS: --spice-card-elevated", + Regex: "#(242424|1f1f1f)", + Replacement: func(submatches ...string) string { + return "var(--spice-card-elevated)" + }, + }, + { + Name: "CSS: --spice-highlight", + Regex: "#1a1a1a", + Replacement: func(submatches ...string) string { + return "var(--spice-highlight)" + }, + }, + { + Name: "CSS: --spice-highlight-elevated", + Regex: "#2a2a2a", + Replacement: func(submatches ...string) string { + return "var(--spice-highlight-elevated)" + }, + }, + { + Name: "CSS: --spice-sidebar", + Regex: "#(000|000000)", + Replacement: func(submatches ...string) string { + return "var(--spice-sidebar)" + }, + }, + { + Name: "CSS: --spice-text", + Regex: "white;|#fff|#ffffff|#f8f8f8", + Replacement: func(submatches ...string) string { + return "var(--spice-text)" + }, + }, + { + Name: "CSS: --spice-subtext", + Regex: `#(b3b3b3|a7a7a7)`, + Replacement: func(submatches ...string) string { + return "var(--spice-subtext)" + }, + }, + { + Name: "CSS: --spice-button", + Regex: "#(1db954|1877f2)", + Replacement: func(submatches ...string) string { + return "var(--spice-button)" + }, + }, + { + Name: "CSS: --spice-button-active", + Regex: "#(1ed760|1fdf64|169c46)", + Replacement: func(submatches ...string) string { + return "var(--spice-button-active)" + }, + }, + { + Name: "CSS: --spice-button-disabled", + Regex: "#535353", + Replacement: func(submatches ...string) string { + return "var(--spice-button-disabled)" + }, + }, + { + Name: "CSS: --spice-tab-active", + Regex: "#(333|333333)", + Replacement: func(submatches ...string) string { + return "var(--spice-tab-active)" + }, + }, + { + Name: "CSS: --spice-misc", + Regex: "#7f7f7f", + Replacement: func(submatches ...string) string { + return "var(--spice-misc)" + }, + }, + { + Name: "CSS: --spice-notification", + Regex: "#(4687d6|2e77d0)", + Replacement: func(submatches ...string) string { + return "var(--spice-notification)" + }, + }, + { + Name: "CSS: --spice-notification-error", + Regex: "#(e22134|cd1a2b)", + Replacement: func(submatches ...string) string { + return "var(--spice-notification-error)" + }, + }, + { + Name: "CSS (rgba): --spice-main", + Regex: `rgba\(18,18,18,([\d\.]+)\)`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("rgba(var(--spice-main),%s)", submatches[1]) + }, + }, + { + Name: "CSS (rgba): --spice-card", + Regex: `rgba\(40,40,40,([\d\.]+)\)`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("rgba(var(--spice-card),%s)", submatches[1]) + }, + }, + { + Name: "CSS (rgba): --spice-rgb-shadow", + Regex: `rgba\(0,0,0,([\d\.]+)\)`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("rgba(var(--spice-rgb-shadow),%s)", submatches[1]) + }, + }, + { + Name: "CSS (hsla): --spice-rgb-text", + Regex: `hsla\(0,0%,100%,\.9\)`, + Replacement: func(submatches ...string) string { + return "rgba(var(--spice-rgb-text),.9)" + }, + }, + { + Name: "CSS (hsla): --spice-rgb-selected-row", + Regex: `hsla\(0,0%,100%,([\d\.]+)\)`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("rgba(var(--spice-rgb-selected-row),%s)", submatches[1]) + }, + }, + } - return content + return applyPatches(content, patches) } func colorVariableReplaceForJS(content string) string { - 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 + patches := []Patch{ + { + Name: "CSS (JS): --spice-button", + Regex: `"#1db954"`, + Replacement: func(submatches ...string) string { + return ` getComputedStyle(document.body).getPropertyValue("--spice-button").trim()` + }, + }, + { + Name: "CSS (JS): --spice-subtext", + Regex: `"#b3b3b3"`, + Replacement: func(submatches ...string) string { + return ` getComputedStyle(document.body).getPropertyValue("--spice-subtext").trim()` + }, + }, + { + Name: "CSS (JS): --spice-text", + Regex: `"#ffffff"`, + Replacement: func(submatches ...string) string { + return ` getComputedStyle(document.body).getPropertyValue("--spice-text").trim()` + }, + }, + { + Name: "CSS (JS): --spice-text white", + Regex: `color:"white"`, + Replacement: func(submatches ...string) string { + return `color:"var(--spice-text)"` + }, + }, + } + + return applyPatches(content, patches) } func disableSentry(input string) string { @@ -385,289 +495,445 @@ func disableSentry(input string) string { } func disableLogging(input string) string { - 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 Promise.resolve({fired:true});", submatches[0]) - }) - utils.Replace(&input, `key:"addItemInEventsStorage",value:function\([^)]*\)\s*\{`, func(submatches ...string) string { - return fmt.Sprintf("%sreturn;", submatches[0]) - }) - utils.Replace(&input, `key:"createLoggingParams",value:function\([^)]*\)\s*\{`, func(submatches ...string) string { - return fmt.Sprintf("%sreturn {interactionIds:null,pageInstanceIds:null};", submatches[0]) - }) - utils.Replace(&input, `key:"addEventsToESSData",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 Promise.resolve({fired:true});", submatches[0]) - }) - utils.Replace(&input, `addItemInEventsStorage\([^)]*\)\s*\{`, func(submatches ...string) string { - return fmt.Sprintf("%sreturn;", submatches[0]) - }) - utils.Replace(&input, `createLoggingParams\([^)]*\)\s*\{`, func(submatches ...string) string { - return fmt.Sprintf("%sreturn {interactionIds:null,pageInstanceIds:null};", submatches[0]) - }) - utils.Replace(&input, `addEventsToESSData\([^)]*\)\s*\{`, func(submatches ...string) string { - return fmt.Sprintf("%sreturn;", submatches[0]) - }) - - return input + patches := []Patch{ + { + Name: "Remove sp://logging/v3/*", + Regex: `sp://logging/v3/\w+`, + Replacement: func(submatches ...string) string { + return "" + }, + }, + { + Name: "Remove /v3/events endpoints", + Regex: `[^"\/]+\/[^"\/]+\/(public\/)?v3\/events`, + Replacement: func(submatches ...string) string { + return "" + }, + }, + { + Name: "Disable registerEventListeners", + Regex: `key:"registerEventListeners",value:function\(\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable logInteraction", + Regex: `key:"logInteraction",value:function\([\w,]+\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn {interactionId:null,pageInstanceId:null};", submatches[0]) + }, + }, + { + Name: "Disable logNonAuthInteraction", + Regex: `key:"logNonAuthInteraction",value:function\([\w,]+\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn {interactionId:null,pageInstanceId:null};", submatches[0]) + }, + }, + { + Name: "Disable logImpression", + Regex: `key:"logImpression",value:function\([\w,]+\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable logNonAuthImpression", + Regex: `key:"logNonAuthImpression",value:function\([\w,]+\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable logNavigation", + Regex: `key:"logNavigation",value:function\([\w,]+\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable handleBackgroundStates", + Regex: `key:"handleBackgroundStates",value:function\(\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable createLoggingParams", + Regex: `key:"createLoggingParams",value:function\([\w,]+\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable initSendingEvents", + Regex: `key:"initSendingEvents",value:function\(\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable flush", + Regex: `key:"flush",value:function\(\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable send", + Regex: `(\{key:"send",value:function\([\w,]+\))\{[\d\w\s,{}()[\]\.,!\?=>&|;:_""]+?\}(\},\{key:"hasContext")`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%s{return;}%s", submatches[1], submatches[2]) + }, + }, + { + Name: "Disable lastFlush", + Regex: `key:"lastFlush",value:function\(\)\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn Promise.resolve({fired:true});", submatches[0]) + }, + }, + { + Name: "Disable addItemInEventsStorage", + Regex: `key:"addItemInEventsStorage",value:function\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable createLoggingParams (new)", + Regex: `key:"createLoggingParams",value:function\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn {interactionIds:null,pageInstanceIds:null};", submatches[0]) + }, + }, + { + Name: "Disable addEventsToESSData", + Regex: `key:"addEventsToESSData",value:function\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable registerEventListeners (new)", + Regex: `registerEventListeners\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable logInteraction (new)", + Regex: `logInteraction\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn {interactionId:null,pageInstanceId:null};", submatches[0]) + }, + }, + { + Name: "Disable logImpression (new)", + Regex: `logImpression\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable logNavigation (new)", + Regex: `logNavigation\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable handleBackgroundStates (new)", + Regex: `handleBackgroundStates\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable initSendingEvents (new)", + Regex: `initSendingEvents\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable sendEvents", + Regex: `sendEvents\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable storeEvent", + Regex: `storeEvent\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable lastFlush (new)", + Regex: `lastFlush\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn Promise.resolve({fired:true});", submatches[0]) + }, + }, + { + Name: "Disable addItemInEventsStorage (new)", + Regex: `addItemInEventsStorage\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + { + Name: "Disable createLoggingParams (new)", + Regex: `createLoggingParams\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn {interactionIds:null,pageInstanceIds:null};", submatches[0]) + }, + }, + { + Name: "Disable addEventsToESSData (new)", + Regex: `addEventsToESSData\([^)]*\)\s*\{`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sreturn;", submatches[0]) + }, + }, + } + return applyPatches(input, patches) } func removeRTL(input string) string { - 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 "" - }) + patches := []Patch{ + { + Name: "Remove }[dir=ltr]", + Regex: `}\[dir=ltr\]\s?`, + Replacement: func(submatches ...string) string { + return "} " + }, + }, + { + Name: "Remove html[dir=ltr]", + Regex: `html\[dir=ltr\]`, + Replacement: func(submatches ...string) string { + return "html" + }, + }, + { + Name: "Remove ', [dir=rtl]' selectors", + Regex: `,\s?\[dir=rtl\].+?(\{.+?\})`, + Replacement: func(submatches ...string) string { + return submatches[1] + }, + }, + { + Name: "Remove [something][dir=rtl] blocks", + Regex: `[\w\-\.]+\[dir=rtl\].+?\{.+?\}`, + Replacement: func(submatches ...string) string { + return "" + }, + }, + { + Name: "Remove }[lang=ar] blocks", + Regex: `\}\[lang=ar\].+?\{.+?\}`, + Replacement: func(submatches ...string) string { + return "}" + }, + }, + { + Name: "Remove }[dir=rtl] blocks", + Regex: `\}\[dir=rtl\].+?\{.+?\}`, + Replacement: func(submatches ...string) string { + return "}" + }, + }, + { + Name: "Remove }html[dir=rtl] blocks", + Regex: `\}html\[dir=rtl\].+?\{.+?\}`, + Replacement: func(submatches ...string) string { + return "}" + }, + }, + { + Name: "Remove }html[lang=ar] blocks", + Regex: `\}html\[lang=ar\].+?\{.+?\}`, + Replacement: func(submatches ...string) string { + return "}" + }, + }, + { + Name: "Remove [lang=ar] blocks", + Regex: `\[lang=ar\].+?\{.+?\}`, + Replacement: func(submatches ...string) string { + return "" + }, + }, + { + Name: "Remove html[dir=rtl] blocks", + Regex: `html\[dir=rtl\].+?\{.+?\}`, + Replacement: func(submatches ...string) string { + return "" + }, + }, + { + Name: "Remove html[lang=ar] blocks", + Regex: `html\[lang=ar\].+?\{.+?\}`, + Replacement: func(submatches ...string) string { + return "" + }, + }, + { + Name: "Remove [dir=rtl] blocks", + Regex: `\[dir=rtl\].+?\{.+?\}`, + Replacement: func(submatches ...string) string { + return "" + }, + }, + } - return input + return applyPatches(input, patches) } -func exposeAPIs_main(input string) string { - // Show Notification - utils.Replace( - &input, - `(?:\w+ |,)([\w$]+)=(\([\w$]+=[\w$]+\.dispatch)`, - 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|registerFactory)[\w(){}<>:.,&$!=;""?!#% ]+)(\{version:[a-zA-Z_\$][\w\$]*,)`, - 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$]+\}))`, - 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$]+\}\)=>\{))`, - 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 <=1.2.30 - utils.Replace( - &input, - `((?:\w+ ?)?[\w$]+=)(\{kind:"Document",definitions:\[\{(?:\w+:[\w"]+,)+name:\{(?:\w+:[\w"]+,?)+value:("\w+"))`, - func(submatches ...string) string { - return fmt.Sprintf("%sSpicetify.GraphQL.Definitions[%s]=%s", submatches[1], submatches[3], submatches[2]) - }) - - // GraphQL definitons >=1.2.31 - utils.Replace( - &input, - `(=new [\w_\$][\w_\$\d]*\.[\w_\$][\w_\$\d]*\("(\w+)","(query|mutation)","[\w\d]{64}",null\))`, - func(submatches ...string) string { - return fmt.Sprintf(`=Spicetify.GraphQL.Definitions["%s"]%s`, submatches[2], submatches[1]) - }) - - // Spotify Custom Snackbar Interfaces - utils.Replace( - &input, - `\b\w\s*\(\)\s*[^;,]*enqueueCustomSnackbar:\s*(\w)\s*[^;]*;`, - func(submatches ...string) string { - return fmt.Sprintf("%sSpicetify.Snackbar.enqueueCustomSnackbar=%s;", submatches[0], submatches[1]) - }) - - // >= 1.2.38 - utils.Replace( - &input, - `(=)[^=]*\(\)\.enqueueCustomSnackbar;`, - func(submatches ...string) string { - return fmt.Sprintf("=Spicetify.Snackbar.enqueueCustomSnackbar%s;", submatches[0]) - }) - - utils.Replace( - &input, - `\(\({[^}]*,\s*imageSrc`, - func(submatches ...string) string { - return fmt.Sprintf("Spicetify.Snackbar.enqueueImageSnackbar=%s", submatches[0]) - }) +func exposeAPIs_main(input string, report patchReporter) string { + inputContextMenu := utils.FindFirstMatch(input, `.*value:"contextmenu"`) + if len(inputContextMenu) > 1 { + croppedInput := inputContextMenu[0] + react := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)\.useRef`)[1] + candicates := utils.FindLastMatch(croppedInput, `\(\{[^}]*menu:([a-zA-Z_\$][\w\$]*),[^}]*trigger:([a-zA-Z_\$][\w\$]*),[^}]*triggerRef:([a-zA-Z_\$][\w\$]*)`) + oldCandicates := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)=[\w_$]+\.menu[^}]*,([a-zA-Z_\$][\w\$]*)=[\w_$]+\.trigger[^}]*,([a-zA-Z_\$][\w\$]*)=[\w_$]+\.triggerRef`) + var menu, trigger, target string + if len(oldCandicates) != 0 { + menu = oldCandicates[1] + trigger = oldCandicates[2] + target = oldCandicates[3] + } else if len(candicates) != 0 { + menu = candicates[1] + trigger = candicates[2] + target = candicates[3] + } else { + menu = "e.menu" + trigger = "e.trigger" + target = "e.triggerRef" + } - utils.ReplaceOnce( - &input, - `(;const [\w\d]+=)((?:\(0,[\w\d]+\.memo\))[\(\d,\w\.\){:}=]+\=[\d\w]+\.[\d\w]+\.getLocaleForURLPath\(\))`, - func(submatches ...string) string { - return fmt.Sprintf("%sSpicetify.ReactComponent.Navigation=%s", submatches[1], submatches[2]) + 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]) }) - - // Menu hook - 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]) - }) - - inputContextMenu := utils.FindFirstMatch(input, `.*value:"contextmenu"`) - if len(inputContextMenu) == 0 { - return input - } - - croppedInput := inputContextMenu[0] - react := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)\.useRef`)[1] - candicates := utils.FindLastMatch(croppedInput, `\(\{[^}]*menu:([a-zA-Z_\$][\w\$]*),[^}]*trigger:([a-zA-Z_\$][\w\$]*),[^}]*triggerRef:([a-zA-Z_\$][\w\$]*)`) - oldCandicates := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)=[\w_$]+\.menu[^}]*,([a-zA-Z_\$][\w\$]*)=[\w_$]+\.trigger[^}]*,([a-zA-Z_\$][\w\$]*)=[\w_$]+\.triggerRef`) - var menu, trigger, target string - if len(oldCandicates) != 0 { - menu = oldCandicates[1] - trigger = oldCandicates[2] - target = oldCandicates[3] - } else if len(candicates) != 0 { - menu = candicates[1] - trigger = candicates[2] - target = candicates[3] - } else { - menu = "e.menu" - trigger = "e.trigger" - target = "e.triggerRef" } - 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]) - }) + patches := []Patch{ + { + Name: "showNotification patch", + Regex: `(?:\w+ |,)([\w$]+)=(\([\w$]+=[\w$]+\.dispatch)`, + Replacement: 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]) + }, + }, + { + Name: "Remove list of exclusive shows", + Regex: `\["spotify:show.+?\]`, + Replacement: func(submatches ...string) string { + return "[]" + }, + }, + { + Name: "Remove Star Wars easter eggs", + Regex: `\w+\(\)\.createElement\(\w+,\{onChange:this\.handleSaberStateChange\}\),`, + Replacement: func(submatches ...string) string { + return "" + }, + }, + { + Name: "Remove data-testid", + Regex: `"data-testid":`, + Replacement: func(submatches ...string) string { + return `"":` + }, + }, + { + Name: "Expose PlatformAPI", + Regex: `((?:setTitlebarHeight|registerFactory)[\w(){}<>:.,&$!=;""?!#% ]+)(\{version:[a-zA-Z_\$][\w\$]*,)`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sSpicetify._platform=%s", submatches[1], submatches[2]) + }, + }, + { + Name: "Redux store", + Regex: `(,[\w$]+=)(([$\w,.:=;(){}]+\(\{session:[\w$]+,features:[\w$]+,seoExperiment:[\w$]+\}))`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sSpicetify.Platform.ReduxStore=%s", submatches[1], submatches[2]) + }, + }, + { + Name: "React Component: Platform Provider", + Regex: `(,[$\w]+=)((function\([\w$]{1}\)\{var [\w$]+=[\w$]+\.platform,[\w$]+=[\w$]+\.children,)|(\(\{platform:[\w$]+,children:[\w$]+\}\)=>\{))`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sSpicetify.ReactComponent.PlatformProvider=%s", submatches[1], submatches[2]) + }, + }, + { + Name: "Prevent breaking popupLyrics", + Regex: `document.pictureInPictureElement&&\(\w+.current=[!\w]+,document\.exitPictureInPicture\(\)\),\w+\.current=null`, + Replacement: func(submatches ...string) string { + return "" + }, + }, + { + Name: "GraphQL definitions <=1.2.30", + Regex: `((?:\w+ ?)?[\w$]+=)(\{kind:"Document",definitions:\[\{(?:\w+:[\w"]+,)+name:\{(?:\w+:[\w"]+,?)+value:("\w+"))`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sSpicetify.GraphQL.Definitions[%s]=%s", submatches[1], submatches[3], submatches[2]) + }, + }, + { + Name: "GraphQL definitons >=1.2.31", + Regex: `(=new [\w_\$][\w_\$\d]*\.[\w_\$][\w_\$\d]*\("(\w+)","(query|mutation)","[\w\d]{64}",null\))`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf(`=Spicetify.GraphQL.Definitions["%s"]%s`, submatches[2], submatches[1]) + }, + }, + { + Name: "Spotify Custom Snackbar Interfaces <=1.2.37", + Regex: `\b\w\s*\(\)\s*[^;,]*enqueueCustomSnackbar:\s*(\w)\s*[^;]*;`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sSpicetify.Snackbar.enqueueCustomSnackbar=%s;", submatches[0], submatches[1]) + }, + }, + { + Name: "Spotify Custom Snackbar Interfaces >=1.2.38", + Regex: `(=)[^=]*\(\)\.enqueueCustomSnackbar;`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("=Spicetify.Snackbar.enqueueCustomSnackbar%s;", submatches[0]) + }, + }, + { + Name: "Spotify Image Snackbar Interface", + Regex: `\(\({[^}]*,\s*imageSrc`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("Spicetify.Snackbar.enqueueImageSnackbar=%s", submatches[0]) + }, + }, + { + Name: "React Component: Navigation for navLinks", + Regex: `(;const [\w\d]+=)((?:\(0,[\w\d]+\.memo\))[\(\d,\w\.\){:}=]+\=[\d\w]+\.[\d\w]+\.getLocaleForURLPath\(\))`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sSpicetify.ReactComponent.Navigation=%s", submatches[1], submatches[2]) + }, + Once: true, + }, + { + Name: "Context Menu V2", + Regex: `("Menu".+?children:)([\w$][\w$\d]*)`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%s[Spicetify.ContextMenuV2.renderItems(),%s].flat()", submatches[1], submatches[2]) + }, + }, + } - return input + return applyPatches(input, patches, report) } -func exposeAPIs_vendor(input string) string { +func exposeAPIs_vendor(input string, report patchReporter) string { // URI utils.Replace( &input, @@ -676,6 +942,45 @@ func exposeAPIs_vendor(input string) string { return fmt.Sprintf(`,(globalThis.Spicetify.URI=%s)%s`, submatches[1], submatches[0]) }) + patches := []Patch{ + { + Name: "Spicetify.URI", + Regex: `,(\w+)\.prototype\.toAppType`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf(`,(globalThis.Spicetify.URI=%s)%s`, submatches[1], submatches[0]) + }, + }, + { + Name: "Mapping styled-components classes", + Regex: `(\w+ [\w$_]+)=[\w$_]+\([\w$_]+>>>0\)`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%s=Spicetify._getStyledClassName(arguments,this)", submatches[1]) + }, + }, + { + Name: "Mapping Tippy.js", + Regex: `([\w\$_]+)\.setDefaultProps=`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("Spicetify.Tippy=%s;%s", submatches[1], submatches[0]) + }, + }, + { + Name: "Flipper components", + Regex: `([\w$]+)=((?:function|\()([\w$.,{}()= ]+(?:springConfig|overshootClamping)){2})`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%s=Spicetify.ReactFlipToolkit.spring=%s", submatches[1], submatches[2]) + }, + }, + { + // https://github.com/iamhosseindhv/notistack + Name: "Snackbar", + Regex: `\w+\s*=\s*\w\.call\(this,[^)]+\)\s*\|\|\s*this\)\.enqueueSnackbar`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("Spicetify.Snackbar=%s", submatches[0]) + }, + }, + } + // URI after 1.2.4 if !strings.Contains(input, "Spicetify.URI") { URIObj := regexp.MustCompile(`(?:class ([\w$_]+)\{constructor|([\w$_]+)=function\(\)\{function ?[\w$_]+)\([\w$.,={}]+\)\{[\w !?:=.,>&(){}[\];]*this\.hasBase62Id`).FindStringSubmatch(input) @@ -700,39 +1005,7 @@ func exposeAPIs_vendor(input string) string { } } - // Mapping styled-components classes - utils.Replace( - &input, - `(\w+ [\w$_]+)=[\w$_]+\([\w$_]+>>>0\)`, - func(submatches ...string) string { - return fmt.Sprintf("%s=Spicetify._getStyledClassName(arguments,this)", submatches[1]) - }) - - // Tippy - utils.Replace( - &input, - `([\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})`, - 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`, - func(submatches ...string) string { - return fmt.Sprintf("Spicetify.Snackbar=%s", submatches[0]) - }) - - return input + return applyPatches(input, patches, report) } type githubRelease = utils.GithubRelease diff --git a/src/utils/config.go b/src/utils/config.go index 6d669f3472..83a6a70148 100644 --- a/src/utils/config.go +++ b/src/utils/config.go @@ -70,7 +70,7 @@ func ParseConfig(configPath string) Config { content: getDefaultConfig(), } defaultConfig.Write() - PrintSuccess("Default config-xpui.ini generated.") + PrintSuccess("Default config-xpui.ini generated") return defaultConfig } @@ -90,7 +90,7 @@ func ParseConfig(configPath string) Config { } if needRewrite { - PrintSuccess("Config is updated.") + PrintSuccess("Config is updated") cfg.SaveTo(configPath) } @@ -129,13 +129,13 @@ func getDefaultConfig() *ini.File { prefsFilePath := FindPrefFilePath() if len(spotifyPath) == 0 { - PrintError("Could not detect Spotify location.") + PrintError("Could not detect Spotify location") } else { configLayout["Setting"]["spotify_path"] = spotifyPath } if len(prefsFilePath) == 0 { - PrintError("Could not detect \"prefs\" file location.") + PrintError("Could not detect \"prefs\" file location") } else { configLayout["Setting"]["prefs_path"] = prefsFilePath } @@ -193,7 +193,7 @@ func FindPrefFilePath() string { path = WinXPrefs() } if len(path) == 0 { - PrintError("No valid path options found, ensure you have Spotify installed and have ran it for at least 30 seconds.") + PrintError("No valid path options found, ensure you have Spotify installed and have ran it for at least 30 seconds") } return path From cdda47a092c0b6735d29344e77a3ca48356a4665 Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 23 Apr 2025 00:16:37 +0200 Subject: [PATCH 133/146] chore(preprocess): remove `currentFile` --- src/preprocess/preprocess.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 38696c07e1..5a21449bf8 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -177,7 +177,6 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla }) totalFiles := len(filesToPatch) - currentFile := 0 style := pterm.NewStyle(pterm.FgWhite, pterm.BgBlack) bar, _ := pterm.DefaultProgressbar.WithTotal(totalFiles).WithTitle("Patching files...").WithTitleStyle(style).WithShowCount(true).Start() @@ -192,8 +191,6 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla fileName := info.Name() extension := filepath.Ext(fileName) - currentFile++ - switch extension { case ".js": utils.ModifyFile(path, func(content string) string { From df89c6fc10a8c14382d60a354a0d87b8dbda9e67 Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 23 Apr 2025 00:18:26 +0200 Subject: [PATCH 134/146] feat(preprocess): merge `windows` & `linux` case --- src/preprocess/preprocess.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 5a21449bf8..2e5e987e8e 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -123,9 +123,7 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla switch runtime.GOOS { case "darwin": frameworkResourcesPath = filepath.Join(spotifyBasePath, "Contents", "Frameworks", "Chromium Embedded Framework.framework", "Resources") - case "windows": - frameworkResourcesPath = spotifyBasePath - case "linux": + case "windows", "linux": frameworkResourcesPath = spotifyBasePath default: utils.PrintError("Unsupported OS for V8 snapshot finding: " + runtime.GOOS) From 3f16dbd86b93ca5bdc508ff75c9ae4da903fcb17 Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 23 Apr 2025 15:17:17 +0200 Subject: [PATCH 135/146] fix(preprocess): check if `> 0` for context menu --- src/preprocess/preprocess.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 2e5e987e8e..e47ce31801 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -349,7 +349,7 @@ func colorVariableReplace(content string) string { }, { Name: "CSS: --spice-subtext", - Regex: `#(b3b3b3|a7a7a7)`, + Regex: "#(b3b3b3|a7a7a7)", Replacement: func(submatches ...string) string { return "var(--spice-subtext)" }, @@ -791,7 +791,7 @@ func removeRTL(input string) string { func exposeAPIs_main(input string, report patchReporter) string { inputContextMenu := utils.FindFirstMatch(input, `.*value:"contextmenu"`) - if len(inputContextMenu) > 1 { + if len(inputContextMenu) > 0 { croppedInput := inputContextMenu[0] react := utils.FindLastMatch(croppedInput, `([a-zA-Z_\$][\w\$]*)\.useRef`)[1] candicates := utils.FindLastMatch(croppedInput, `\(\{[^}]*menu:([a-zA-Z_\$][\w\$]*),[^}]*trigger:([a-zA-Z_\$][\w\$]*),[^}]*triggerRef:([a-zA-Z_\$][\w\$]*)`) From cdb0a4549e93606b3078e0bde0cf1e11cd1cabd4 Mon Sep 17 00:00:00 2001 From: ririxi Date: Wed, 23 Apr 2025 15:55:49 +0200 Subject: [PATCH 136/146] fix(preprocess): add missing `--spice-main-elevated` --- src/preprocess/preprocess.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index e47ce31801..5ef95900d7 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -305,6 +305,13 @@ func colorVariableReplace(content string) string { return "var(--spice-card)" }, }, + { + Name: "CSS: --spice-main-elevated", + Regex: "#(242424|1f1f1f)", + Replacement: func(submatches ...string) string { + return "var(--spice-main-elevated)" + }, + }, { Name: "CSS: --spice-main", Regex: "#121212", From c77ca8082061ee4bac058c08b7faf1855acfa003 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 26 Apr 2025 17:04:14 +0200 Subject: [PATCH 137/146] chore: change names --- src/preprocess/preprocess.go | 44 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 5ef95900d7..cd98160322 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -38,9 +38,9 @@ type Patch struct { Once bool } -type patchReporter func(string) +type logPatch func(string) -func applyPatches(input string, patches []Patch, report ...patchReporter) string { +func applyPatches(input string, patches []Patch, report ...logPatch) string { for _, patch := range patches { if len(report) > 0 && report[0] != nil { report[0](fmt.Sprintf("%s patch", patch.Name)) @@ -290,7 +290,7 @@ func StartCSS(extractedAppsPath string) { } func colorVariableReplace(content string) string { - patches := []Patch{ + colorPatches := []Patch{ { Name: "CSS: --spice-player", Regex: "#(181818|212121)", @@ -447,11 +447,11 @@ func colorVariableReplace(content string) string { }, } - return applyPatches(content, patches) + return applyPatches(content, colorPatches) } func colorVariableReplaceForJS(content string) string { - patches := []Patch{ + colorVariablePatches := []Patch{ { Name: "CSS (JS): --spice-button", Regex: `"#1db954"`, @@ -482,7 +482,7 @@ func colorVariableReplaceForJS(content string) string { }, } - return applyPatches(content, patches) + return applyPatches(content, colorVariablePatches) } func disableSentry(input string) string { @@ -497,7 +497,7 @@ func disableSentry(input string) string { } func disableLogging(input string) string { - patches := []Patch{ + loggingPatches := []Patch{ { Name: "Remove sp://logging/v3/*", Regex: `sp://logging/v3/\w+`, @@ -702,11 +702,11 @@ func disableLogging(input string) string { }, }, } - return applyPatches(input, patches) + return applyPatches(input, loggingPatches) } func removeRTL(input string) string { - patches := []Patch{ + rtlPatches := []Patch{ { Name: "Remove }[dir=ltr]", Regex: `}\[dir=ltr\]\s?`, @@ -793,10 +793,10 @@ func removeRTL(input string) string { }, } - return applyPatches(input, patches) + return applyPatches(input, rtlPatches) } -func exposeAPIs_main(input string, report patchReporter) string { +func exposeAPIs_main(input string, report logPatch) string { inputContextMenu := utils.FindFirstMatch(input, `.*value:"contextmenu"`) if len(inputContextMenu) > 0 { croppedInput := inputContextMenu[0] @@ -823,7 +823,7 @@ func exposeAPIs_main(input string, report patchReporter) string { }) } - patches := []Patch{ + xpuiPatches := []Patch{ { Name: "showNotification patch", Regex: `(?:\w+ |,)([\w$]+)=(\([\w$]+=[\w$]+\.dispatch)`, @@ -881,28 +881,28 @@ func exposeAPIs_main(input string, report patchReporter) string { }, }, { - Name: "GraphQL definitions <=1.2.30", + Name: "GraphQL definitions (<=1.2.30)", Regex: `((?:\w+ ?)?[\w$]+=)(\{kind:"Document",definitions:\[\{(?:\w+:[\w"]+,)+name:\{(?:\w+:[\w"]+,?)+value:("\w+"))`, Replacement: func(submatches ...string) string { return fmt.Sprintf("%sSpicetify.GraphQL.Definitions[%s]=%s", submatches[1], submatches[3], submatches[2]) }, }, { - Name: "GraphQL definitons >=1.2.31", + Name: "GraphQL definitons (>=1.2.31)", Regex: `(=new [\w_\$][\w_\$\d]*\.[\w_\$][\w_\$\d]*\("(\w+)","(query|mutation)","[\w\d]{64}",null\))`, Replacement: func(submatches ...string) string { return fmt.Sprintf(`=Spicetify.GraphQL.Definitions["%s"]%s`, submatches[2], submatches[1]) }, }, { - Name: "Spotify Custom Snackbar Interfaces <=1.2.37", + Name: "Spotify Custom Snackbar Interfaces (<=1.2.37)", Regex: `\b\w\s*\(\)\s*[^;,]*enqueueCustomSnackbar:\s*(\w)\s*[^;]*;`, Replacement: func(submatches ...string) string { return fmt.Sprintf("%sSpicetify.Snackbar.enqueueCustomSnackbar=%s;", submatches[0], submatches[1]) }, }, { - Name: "Spotify Custom Snackbar Interfaces >=1.2.38", + Name: "Spotify Custom Snackbar Interfaces (>=1.2.38)", Regex: `(=)[^=]*\(\)\.enqueueCustomSnackbar;`, Replacement: func(submatches ...string) string { return fmt.Sprintf("=Spicetify.Snackbar.enqueueCustomSnackbar%s;", submatches[0]) @@ -932,10 +932,10 @@ func exposeAPIs_main(input string, report patchReporter) string { }, } - return applyPatches(input, patches, report) + return applyPatches(input, xpuiPatches, report) } -func exposeAPIs_vendor(input string, report patchReporter) string { +func exposeAPIs_vendor(input string, report logPatch) string { // URI utils.Replace( &input, @@ -944,7 +944,7 @@ func exposeAPIs_vendor(input string, report patchReporter) string { return fmt.Sprintf(`,(globalThis.Spicetify.URI=%s)%s`, submatches[1], submatches[0]) }) - patches := []Patch{ + vendorPatches := []Patch{ { Name: "Spicetify.URI", Regex: `,(\w+)\.prototype\.toAppType`, @@ -953,14 +953,14 @@ func exposeAPIs_vendor(input string, report patchReporter) string { }, }, { - Name: "Mapping styled-components classes", + Name: "Map styled-components classes", Regex: `(\w+ [\w$_]+)=[\w$_]+\([\w$_]+>>>0\)`, Replacement: func(submatches ...string) string { return fmt.Sprintf("%s=Spicetify._getStyledClassName(arguments,this)", submatches[1]) }, }, { - Name: "Mapping Tippy.js", + Name: "Tippy.js", Regex: `([\w\$_]+)\.setDefaultProps=`, Replacement: func(submatches ...string) string { return fmt.Sprintf("Spicetify.Tippy=%s;%s", submatches[1], submatches[0]) @@ -1007,7 +1007,7 @@ func exposeAPIs_vendor(input string, report patchReporter) string { } } - return applyPatches(input, patches, report) + return applyPatches(input, vendorPatches, report) } type githubRelease = utils.GithubRelease From 7dc9c31615b8555a96f022222c59df4000e7ef9c Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 26 Apr 2025 17:08:40 +0200 Subject: [PATCH 138/146] fix: `frameworkResourcesPath` for macOS --- src/preprocess/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index cd98160322..7974aec329 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -122,7 +122,7 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla frameworkResourcesPath := "" switch runtime.GOOS { case "darwin": - frameworkResourcesPath = filepath.Join(spotifyBasePath, "Contents", "Frameworks", "Chromium Embedded Framework.framework", "Resources") + frameworkResourcesPath = filepath.Join(spotifyBasePath, "..", "Frameworks", "Chromium Embedded Framework.framework", "Resources") case "windows", "linux": frameworkResourcesPath = spotifyBasePath default: From 8860f1b8d8a6a62056456b1f43d1cca71fdd2813 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 26 Apr 2025 17:39:36 +0200 Subject: [PATCH 139/146] build: bump golang to `1.24.2` --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0488083045..bb92f39aaa 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/spicetify/cli -go 1.24.1 +go 1.24.2 require ( github.com/go-ini/ini v1.67.0 From 39fcc02e6630fcdc6cfd079beeb9c34ac1cf0b01 Mon Sep 17 00:00:00 2001 From: mallusrgreat Date: Tue, 29 Apr 2025 20:16:02 +0530 Subject: [PATCH 140/146] fix: correct minor typo when version is invalid (#3393) --- install.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.ps1 b/install.ps1 index cdd92c9dcc..d03c8e0e11 100644 --- a/install.ps1 +++ b/install.ps1 @@ -78,7 +78,7 @@ function Get-Spicetify { $targetVersion = $v } else { - Write-Warning -Message "You have spicefied an invalid spicetify version: $v `nThe version must be in the following format: 1.2.3" + Write-Warning -Message "You have specified an invalid spicetify version: $v `nThe version must be in the following format: 1.2.3" Pause exit } From 6c01db71b62bf71f1c4a36ac3389bd3956230c5f Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 3 May 2025 03:42:11 +0200 Subject: [PATCH 141/146] feat(preprocess): expose graphql defs from every js file --- src/preprocess/preprocess.go | 37 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 7974aec329..16a4686983 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -214,6 +214,8 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla case "vendor~xpui.js": content = exposeAPIs_vendor(content, printPatch) } + + content = exposeGraphQL(content, printPatch) } printPatch("CSS (JS): Patching our mappings into file") for k, v := range cssTranslationMap { @@ -796,6 +798,27 @@ func removeRTL(input string) string { return applyPatches(input, rtlPatches) } +func exposeGraphQL(input string, report logPatch) string { + graphQLPatches := []Patch{ + { + Name: "GraphQL definitions (<=1.2.30)", + Regex: `((?:\w+ ?)?[\w$]+=)(\{kind:"Document",definitions:\[\{(?:\w+:[\w"]+,)+name:\{(?:\w+:[\w"]+,?)+value:("\w+"))`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf("%sSpicetify.GraphQL.Definitions[%s]=%s", submatches[1], submatches[3], submatches[2]) + }, + }, + { + Name: "GraphQL definitons (>=1.2.31)", + Regex: `(=new [\w_\$][\w_\$\d]*\.[\w_\$][\w_\$\d]*\("(\w+)","(query|mutation)","[\w\d]{64}",null\))`, + Replacement: func(submatches ...string) string { + return fmt.Sprintf(`=Spicetify.GraphQL.Definitions["%s"]%s`, submatches[2], submatches[1]) + }, + }, + } + + return applyPatches(input, graphQLPatches, report) +} + func exposeAPIs_main(input string, report logPatch) string { inputContextMenu := utils.FindFirstMatch(input, `.*value:"contextmenu"`) if len(inputContextMenu) > 0 { @@ -880,20 +903,6 @@ func exposeAPIs_main(input string, report logPatch) string { return "" }, }, - { - Name: "GraphQL definitions (<=1.2.30)", - Regex: `((?:\w+ ?)?[\w$]+=)(\{kind:"Document",definitions:\[\{(?:\w+:[\w"]+,)+name:\{(?:\w+:[\w"]+,?)+value:("\w+"))`, - Replacement: func(submatches ...string) string { - return fmt.Sprintf("%sSpicetify.GraphQL.Definitions[%s]=%s", submatches[1], submatches[3], submatches[2]) - }, - }, - { - Name: "GraphQL definitons (>=1.2.31)", - Regex: `(=new [\w_\$][\w_\$\d]*\.[\w_\$][\w_\$\d]*\("(\w+)","(query|mutation)","[\w\d]{64}",null\))`, - Replacement: func(submatches ...string) string { - return fmt.Sprintf(`=Spicetify.GraphQL.Definitions["%s"]%s`, submatches[2], submatches[1]) - }, - }, { Name: "Spotify Custom Snackbar Interfaces (<=1.2.37)", Regex: `\b\w\s*\(\)\s*[^;,]*enqueueCustomSnackbar:\s*(\w)\s*[^;]*;`, From 4af0bf02ff88fcf84f40d83240ccd9ed11a043ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 14:53:14 +0200 Subject: [PATCH 142/146] chore(deps): bump golang.org/x/net from 0.39.0 to 0.40.0 (#3399) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index bb92f39aaa..e00ef2d6e8 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/go-ini/ini v1.67.0 github.com/mattn/go-colorable v0.1.14 github.com/pterm/pterm v0.12.80 - golang.org/x/net v0.39.0 - golang.org/x/sys v0.32.0 + golang.org/x/net v0.40.0 + golang.org/x/sys v0.33.0 ) require ( @@ -22,6 +22,6 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/term v0.31.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect ) diff --git a/go.sum b/go.sum index e9a14dda3f..62bc10fa1c 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -96,22 +96,22 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 15be854547447437ae8f8fe205a7dc6bc88dfa19 Mon Sep 17 00:00:00 2001 From: ririxi Date: Fri, 9 May 2025 21:09:33 +0200 Subject: [PATCH 143/146] chore: change message in preprocess --- src/preprocess/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 16a4686983..22c8fc7cf2 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -145,7 +145,7 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla embeddedString, _, _, err := utils.ReadStringFromUTF16Binary(binFilePath, startMarker, endMarker) if err != nil { utils.PrintWarning(fmt.Sprintf("Could not process %s: %v", binFilePath, err)) - utils.PrintInfo("You can ignore this warning if you're on a Spotify version that didn't yet add xpui modules to the V8 snapshot") + utils.PrintInfo("If above error says 'could not find start marker', you can safely ignore that error. It's for the future spotify release that might add xpui to the snapshot.") continue } From 2d38dc7c3cfc2c02eaa84609d6ca229c95f42cf5 Mon Sep 17 00:00:00 2001 From: ririxi Date: Fri, 9 May 2025 21:10:37 +0200 Subject: [PATCH 144/146] chore: `error` -> `warning` --- src/preprocess/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preprocess/preprocess.go b/src/preprocess/preprocess.go index 22c8fc7cf2..b2f263aaa0 100644 --- a/src/preprocess/preprocess.go +++ b/src/preprocess/preprocess.go @@ -145,7 +145,7 @@ func Start(version string, spotifyBasePath string, extractedAppsPath string, fla embeddedString, _, _, err := utils.ReadStringFromUTF16Binary(binFilePath, startMarker, endMarker) if err != nil { utils.PrintWarning(fmt.Sprintf("Could not process %s: %v", binFilePath, err)) - utils.PrintInfo("If above error says 'could not find start marker', you can safely ignore that error. It's for the future spotify release that might add xpui to the snapshot.") + utils.PrintInfo("If above warning says 'could not find start marker', you can safely ignore that error. It's for the future spotify release that might add xpui to the snapshot.") continue } From 00199ce23d6e1051911687874ad986dba87c6932 Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Sat, 10 May 2025 23:51:19 +0800 Subject: [PATCH 145/146] feat(lyrics-plus/netease): implement below-mode for netease (#3404) --- CustomApps/lyrics-plus/Providers.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CustomApps/lyrics-plus/Providers.js b/CustomApps/lyrics-plus/Providers.js index a6558e95d4..d542253cea 100644 --- a/CustomApps/lyrics-plus/Providers.js +++ b/CustomApps/lyrics-plus/Providers.js @@ -125,8 +125,13 @@ const Providers = { result.unsynced = unsynced; } const translation = ProviderNetease.getTranslation(list); - if (translation) { - result.neteaseTranslation = translation; + if ((synced || unsynced) && Array.isArray(translation)) { + const baseLyrics = synced ?? unsynced; + result.neteaseTranslation = baseLyrics.map((line) => ({ + ...line, + text: translation.find((t) => t.startTime === line.startTime)?.text ?? line.text, + originalText: line.text, + })); } return result; From 68f41d51303ed690e2fb0cf077350ffc512080d8 Mon Sep 17 00:00:00 2001 From: ririxi Date: Sat, 10 May 2025 18:05:18 +0200 Subject: [PATCH 146/146] feat(lyrics-plus/translator): inject internals when using netease --- CustomApps/lyrics-plus/Translator.js | 5 +++-- CustomApps/lyrics-plus/Utils.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CustomApps/lyrics-plus/Translator.js b/CustomApps/lyrics-plus/Translator.js index 74f35a5d4e..453c0f3d73 100644 --- a/CustomApps/lyrics-plus/Translator.js +++ b/CustomApps/lyrics-plus/Translator.js @@ -6,12 +6,13 @@ const openCCPath = "https://cdn.jsdelivr.net/npm/opencc-js@1.0.5/dist/umd/full.m const dictPath = "https:/cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict"; class Translator { - constructor(lang) { + constructor(lang, isUsingNetease = false) { this.finished = { ja: false, ko: false, zh: false, }; + this.isUsingNetease = isUsingNetease; this.applyKuromojiFix(); this.injectExternals(lang); @@ -19,7 +20,7 @@ class Translator { } includeExternal(url) { - if (CONFIG.visual.translate && !document.querySelector(`script[src="${url}"]`)) { + if ((CONFIG.visual.translate || this.isUsingNetease) && !document.querySelector(`script[src="${url}"]`)) { const script = document.createElement("script"); script.setAttribute("type", "text/javascript"); script.setAttribute("src", url); diff --git a/CustomApps/lyrics-plus/Utils.js b/CustomApps/lyrics-plus/Utils.js index 440ececb11..39c28f8e8f 100644 --- a/CustomApps/lyrics-plus/Utils.js +++ b/CustomApps/lyrics-plus/Utils.js @@ -70,7 +70,7 @@ const Utils = { */ async toSimplifiedChinese(s) { // create a singleton Translator instance - if (!this._translatorInstance) this.translator = new Translator("zh"); + if (!this._translatorInstance) this.translator = new Translator("zh", true); // translate to Simplified Chinese // as Traditional Chinese differs between HK and TW, forcing to use OpenCC standard