mirror of
https://github.com/SpotX-Official/SpotX-Bash.git
synced 2026-08-09 17:41:07 +10:00
Compare commits
18
Commits
157af3071e
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a68b29fd9d | ||
|
|
307db7769d | ||
|
|
73b74cbd65 | ||
|
|
03207b6117 | ||
|
|
b6df3cdcc5 | ||
|
|
876e4dc0cd | ||
|
|
f4b9f7d20f | ||
|
|
852acb2acb | ||
|
|
7f190c6660 | ||
|
|
8a2e356e8d | ||
|
|
140f86488d | ||
|
|
b350dbe57d | ||
|
|
38c1844f28 | ||
|
|
b65e1b7d19 | ||
|
|
d11c014f14 | ||
|
|
d975395574 | ||
|
|
72d2482876 | ||
|
|
7a2883bef5 |
@@ -0,0 +1,37 @@
|
||||
name: Deploy static content to Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: '.'
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
@@ -12,7 +12,7 @@
|
||||
<center>
|
||||
<h4 align="center">Adblock for the Spotify desktop client on Linux & macOS</h4>
|
||||
<p align="center">
|
||||
<strong>Latest supported version:</strong> 1.2.93.667.g7b5cc0ce
|
||||
<strong>Latest supported version:</strong> 1.2.95.453.g0eeebbed
|
||||
</p>
|
||||
</center>
|
||||
|
||||
@@ -44,6 +44,18 @@ bash <(curl -sSL https://raw.githubusercontent.com/SpotX-Official/SpotX-Bash/mai
|
||||
- View additional flags/options and examples in the `Options` section below
|
||||
- For more information, see the [FAQ](https://github.com/SpotX-Official/SpotX-Bash/wiki/SpotX%E2%80%90Bash-FAQ)
|
||||
|
||||
### Snap / NixOS:
|
||||
|
||||
Spotify installations using Snap require the included `spotx-snap.sh` helper. Download the repo so `spotx-snap.sh` and `spotx.sh` remain in the same directory:
|
||||
```
|
||||
git clone https://github.com/SpotX-Official/SpotX-Bash.git
|
||||
cd SpotX-Bash
|
||||
bash spotx-snap.sh
|
||||
```
|
||||
Run `bash spotx-snap.sh --help` for additional Snap options or see the [FAQ](https://github.com/SpotX-Official/SpotX-Bash/wiki/SpotX%E2%80%90Bash-FAQ#is-the-snap-version-of-spotify-supported).
|
||||
|
||||
NixOS users should use [SpotX-Nix](https://github.com/SpotX-Official/SpotX-Nix), which applies SpotX-Bash while building the Spotify package without modifying the Nix store.
|
||||
|
||||
### Options:
|
||||
<details>
|
||||
<summary>Click to expand!</summary>
|
||||
|
||||
Executable
+342
@@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
clr='\033[0m'
|
||||
green='\033[0;32m'
|
||||
red='\033[0;31m'
|
||||
yellow='\033[0;33m'
|
||||
|
||||
show_help() {
|
||||
echo -e \
|
||||
"Usage: bash spotx-snap.sh [helper options] [SpotX-Bash options]
|
||||
|
||||
Helper options:
|
||||
--allow-unverified : allow an unverifiable snap set by '--snap-file'
|
||||
--build-only : create patched snap without installing
|
||||
--channel <channel> : select stable, candidate, beta, or edge with '--download'
|
||||
--download : download Spotify snap instead of using a local source
|
||||
--help : print this help message
|
||||
--output-dir <path> : set output directory for '--build-only'
|
||||
--restore : restore official store-managed Spotify snap
|
||||
--snap-file <path> : use a specific local Spotify snap
|
||||
--uninstall : same as '--restore'
|
||||
|
||||
All other supported options are passed to SpotX-Bash.
|
||||
"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${red}Error:${clr} $*\n" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
latest_cached_snap() {
|
||||
local version="${1:-}" candidate latest=''
|
||||
for candidate in "${cacheDir}"/spotify_*.snap; do
|
||||
[[ -f "${candidate}" ]] || continue
|
||||
[[ -z "${version}" || "${candidate##*/}" == "spotify_${version}_"*.snap ]] || continue
|
||||
[[ -z "${latest}" || "${candidate}" -nt "${latest}" ]] && latest="${candidate}"
|
||||
done
|
||||
[[ -n "${latest}" ]] && printf '%s\n' "${latest}"
|
||||
}
|
||||
|
||||
has_spotx_marker() {
|
||||
local spa="${1}" entry markerFile="${workDir}/marker.js"
|
||||
for entry in xpui.js xpui-snapshot.js; do
|
||||
"${sudoCmd[@]}" unzip -p "${spa}" "${entry}" > "${markerFile}" 2>/dev/null || true
|
||||
grep -Fq "//# SpotX was here" "${markerFile}" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
verify_snap_source() {
|
||||
local snap="${1}" digest size assertion assertedDigest snapId assertedSize declaration declaredName candidate remoteAssertion
|
||||
digest=$("${sudoCmd[@]}" snap info --verbose "${snap}" 2>/dev/null | awk '$1 == "sha3-384:" { print $2; exit }')
|
||||
size=$("${sudoCmd[@]}" stat -c '%s' "${snap}")
|
||||
[[ -n "${digest}" ]] || return 1
|
||||
assertion=$(LC_ALL=C snap known snap-revision snap-sha3-384="${digest}" 2>/dev/null || true)
|
||||
[[ -n "${assertion}" ]] || {
|
||||
for candidate in "$(dirname -- "${sourceSnap}")"/*.assert; do
|
||||
[[ -f "${candidate}" ]] || continue
|
||||
"${sudoCmd[@]}" grep -Fqx "snap-sha3-384: ${digest}" "${candidate}" 2>/dev/null || continue
|
||||
"${sudoCmd[@]}" snap ack "${candidate}" >/dev/null 2>&1 || continue
|
||||
assertion=$(LC_ALL=C snap known snap-revision snap-sha3-384="${digest}" 2>/dev/null || true)
|
||||
[[ -n "${assertion}" ]] && break
|
||||
done
|
||||
}
|
||||
[[ -n "${assertion}" ]] || {
|
||||
remoteAssertion=$(LC_ALL=C timeout 20 snap known --remote snap-revision snap-sha3-384="${digest}" 2>/dev/null || true)
|
||||
[[ -n "${remoteAssertion}" ]] && {
|
||||
printf '%s\n' "${remoteAssertion}" > "${workDir}/snap-revision.assert"
|
||||
"${sudoCmd[@]}" snap ack "${workDir}/snap-revision.assert" >/dev/null 2>&1 || true
|
||||
assertion="${remoteAssertion}"
|
||||
}
|
||||
}
|
||||
[[ -n "${assertion}" ]] || return 1
|
||||
assertedDigest=$(awk '$1 == "snap-sha3-384:" { print $2; exit }' <<< "${assertion}")
|
||||
snapId=$(awk '$1 == "snap-id:" { print $2; exit }' <<< "${assertion}")
|
||||
assertedSize=$(awk '$1 == "snap-size:" { print $2; exit }' <<< "${assertion}")
|
||||
[[ "${assertedDigest}" == "${digest}" && -n "${snapId}" && "${assertedSize}" == "${size}" ]] || return 1
|
||||
declaration=$(LC_ALL=C snap known snap-declaration snap-id="${snapId}" 2>/dev/null || true)
|
||||
[[ -n "${declaration}" ]] || {
|
||||
remoteAssertion=$(LC_ALL=C timeout 20 snap known --remote snap-declaration snap-id="${snapId}" 2>/dev/null || true)
|
||||
[[ -n "${remoteAssertion}" ]] && {
|
||||
printf '%s\n' "${remoteAssertion}" > "${workDir}/snap-declaration.assert"
|
||||
"${sudoCmd[@]}" snap ack "${workDir}/snap-declaration.assert" >/dev/null 2>&1 || true
|
||||
declaration="${remoteAssertion}"
|
||||
}
|
||||
}
|
||||
declaredName=$(awk '$1 == "snap-name:" { print $2; exit }' <<< "${declaration}")
|
||||
[[ "${declaredName}" == "spotify" ]] || return 1
|
||||
sourceVerified='true'
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
[[ -n "${workDir:-}" && -d "${workDir}" && "${workDir}" == "${tempBase}/spotx-snap."* ]] && "${sudoCmd[@]}" rm -rf -- "${workDir}"
|
||||
}
|
||||
|
||||
snapChannel='stable'
|
||||
snapFile=''
|
||||
allowUnverified=''
|
||||
downloadSnap=''
|
||||
buildOnly=''
|
||||
restoreSnap=''
|
||||
channelSet=''
|
||||
outputSet=''
|
||||
outputDir="${PWD}"
|
||||
spotxArgs=()
|
||||
while (($#)); do
|
||||
case "${1}" in
|
||||
--allow-unverified) allowUnverified='true' ;;
|
||||
--build-only) buildOnly='true' ;;
|
||||
--channel)
|
||||
(($# > 1)) || error "'--channel' requires an argument."
|
||||
snapChannel="${2}"
|
||||
channelSet='true'
|
||||
shift
|
||||
;;
|
||||
--channel=*)
|
||||
snapChannel="${1#*=}"
|
||||
channelSet='true'
|
||||
;;
|
||||
--download) downloadSnap='true' ;;
|
||||
--help) show_help; exit 0 ;;
|
||||
--output-dir)
|
||||
(($# > 1)) || error "'--output-dir' requires an argument."
|
||||
outputDir="${2}"
|
||||
outputSet='true'
|
||||
shift
|
||||
;;
|
||||
--output-dir=*)
|
||||
outputDir="${1#*=}"
|
||||
outputSet='true'
|
||||
;;
|
||||
--restore|--uninstall) restoreSnap='true' ;;
|
||||
--snap-file)
|
||||
(($# > 1)) || error "'--snap-file' requires an argument."
|
||||
snapFile="${2}"
|
||||
shift
|
||||
;;
|
||||
--snap-file=*) snapFile="${1#*=}" ;;
|
||||
-P*|-F*|--installdeb|--installmac|--rollback|--stable)
|
||||
error "'${1}' cannot be used with spotx-snap.sh."
|
||||
;;
|
||||
-c|--clearcache)
|
||||
error "Snap cache clearing is not supported by spotx-snap.sh."
|
||||
;;
|
||||
-v|--version|--logo)
|
||||
error "'${1}' does not patch a snap and cannot be used with spotx-snap.sh."
|
||||
;;
|
||||
--) error "'--' is not supported by spotx-snap.sh." ;;
|
||||
*) spotxArgs+=("${1}") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[[ "$(uname -s)" == "Linux" ]] || error "spotx-snap.sh requires Linux."
|
||||
[[ "$(uname -m)" == "x86_64" || "$(uname -m)" == "amd64" ]] || error "Spotify snap requires an x86_64 Linux system."
|
||||
[[ -z "${allowUnverified}" || -n "${snapFile}" ]] || error "'--allow-unverified' requires '--snap-file'."
|
||||
[[ -z "${snapFile}" || -z "${downloadSnap}" ]] || error "'--snap-file' and '--download' cannot be used together."
|
||||
[[ -z "${channelSet}" || -n "${downloadSnap}" ]] || error "'--channel' requires '--download'."
|
||||
[[ -z "${outputSet}" || -n "${buildOnly}" ]] || error "'--output-dir' requires '--build-only'."
|
||||
[[ "${restoreSnap}" ]] && {
|
||||
[[ -z "${snapFile}${downloadSnap}${buildOnly}${channelSet}${outputSet}" && ${#spotxArgs[@]} -eq 0 ]] || error "'--restore' cannot be combined with other options."
|
||||
}
|
||||
[[ "${snapChannel}" =~ ^(stable|candidate|beta|edge)$ ]] || error "Invalid snap channel '${snapChannel}'."
|
||||
|
||||
scriptDir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
spotxScript="${scriptDir}/spotx.sh"
|
||||
[[ -f "${spotxScript}" ]] || error "spotx.sh not found beside spotx-snap.sh."
|
||||
|
||||
for requirement in snap unsquashfs unzip zip perl awk grep sha256sum mktemp getent install stat timeout; do
|
||||
command -v "${requirement}" >/dev/null || error "${requirement} command not found."
|
||||
done
|
||||
|
||||
runUser="${SUDO_USER:-$(id -un)}"
|
||||
runUid=$(id -u "${runUser}")
|
||||
runGid=$(id -g "${runUser}")
|
||||
runHome=$(getent passwd "${runUser}" 2>/dev/null | awk -F: 'NR == 1 { print $6 }')
|
||||
[[ -n "${runHome}" ]] || runHome="${HOME}"
|
||||
cacheBase="${runHome}/.cache/spotx-bash"
|
||||
cacheDir="${cacheBase}/snap"
|
||||
sudoCmd=()
|
||||
((EUID == 0)) || {
|
||||
command -v sudo >/dev/null || error "sudo command not found."
|
||||
sudo -n true >/dev/null 2>&1 || {
|
||||
echo -e "This script requires sudo permission to preserve and install snap files.\nPlease enter your sudo password..."
|
||||
sudo -v || error "Failed to obtain sudo permission."
|
||||
}
|
||||
sudoCmd=(sudo)
|
||||
}
|
||||
|
||||
snap version >/dev/null 2>&1 || error "snapd is not available."
|
||||
[[ "${restoreSnap}" ]] && {
|
||||
LC_ALL=C snap list spotify >/dev/null 2>&1 || error "Spotify snap is not installed."
|
||||
command pkill -9 '[sS]potify' 2>/dev/null || true
|
||||
echo -e "Restoring official store-managed Spotify snap...\n"
|
||||
"${sudoCmd[@]}" snap refresh --amend --channel=latest/stable spotify || error "Official Spotify snap restore failed."
|
||||
echo
|
||||
echo -e "${green}Finished${clr}\n"
|
||||
exit 0
|
||||
}
|
||||
|
||||
tempBase="${TMPDIR:-/tmp}"
|
||||
tempBase="${tempBase%/}"
|
||||
workDir=$(mktemp -d "${tempBase}/spotx-snap.XXXXXXXX")
|
||||
trap cleanup EXIT
|
||||
trap 'exit 130' HUP INT TERM
|
||||
sourceDir="${workDir}/source"
|
||||
snapRoot="${workDir}/squashfs-root"
|
||||
packedDir="${workDir}/packed"
|
||||
sourceCopy="${sourceDir}/spotify.snap"
|
||||
mkdir -p "${sourceDir}" "${packedDir}"
|
||||
|
||||
sourceSnap=''
|
||||
sourceOutput=''
|
||||
[[ -n "${snapFile}" ]] && {
|
||||
[[ -f "${snapFile}" ]] || error "Snap file not found: ${snapFile}"
|
||||
sourceSnap=$(cd -- "$(dirname -- "${snapFile}")" && pwd)/$(basename -- "${snapFile}")
|
||||
sourceOutput="${sourceSnap}"
|
||||
}
|
||||
[[ "${downloadSnap}" ]] && {
|
||||
downloadDir="${workDir}/download"
|
||||
mkdir -p "${downloadDir}"
|
||||
echo -e "Downloading Spotify snap from ${snapChannel} channel...\n"
|
||||
downloadOutput=$(cd "${downloadDir}" && snap download spotify --channel="${snapChannel}" 2>&1) || {
|
||||
echo -e "${downloadOutput}\n" >&2
|
||||
error "Spotify snap download failed."
|
||||
}
|
||||
for candidate in "${downloadDir}"/spotify_*.snap; do
|
||||
[[ -f "${candidate}" ]] || continue
|
||||
[[ -z "${sourceSnap}" ]] || error "Multiple Spotify snap downloads found."
|
||||
sourceSnap="${candidate}"
|
||||
done
|
||||
[[ -n "${sourceSnap}" ]] || error "Spotify snap download not found."
|
||||
sourceOutput="downloaded ${snapChannel} snap"
|
||||
}
|
||||
[[ -z "${sourceSnap}" ]] && {
|
||||
installedOutput=$(LC_ALL=C snap list spotify 2>/dev/null || true)
|
||||
installedVersion=$(awk 'NR == 2 { print $2 }' <<< "${installedOutput}")
|
||||
installedRev=$(awk 'NR == 2 { print $3 }' <<< "${installedOutput}")
|
||||
[[ -n "${installedRev}" && "${installedRev}" != x* ]] && {
|
||||
installedSnap="/var/lib/snapd/snaps/spotify_${installedRev}.snap"
|
||||
[[ -f "${installedSnap}" ]] && {
|
||||
sourceSnap="${installedSnap}"
|
||||
sourceOutput="installed Spotify snap revision ${installedRev}"
|
||||
}
|
||||
}
|
||||
}
|
||||
[[ -z "${sourceSnap}" ]] && {
|
||||
installedCacheVersion=''
|
||||
[[ -n "${installedVersion:-}" ]] && installedCacheVersion=$(printf '%s' "${installedVersion}" | tr -c 'A-Za-z0-9._-' '_')
|
||||
cachedSnap=$(latest_cached_snap "${installedCacheVersion}" || true)
|
||||
[[ -n "${cachedSnap}" ]] && {
|
||||
sourceSnap="${cachedSnap}"
|
||||
sourceOutput="cached original snap"
|
||||
}
|
||||
[[ -z "${cachedSnap}" && -n "${installedVersion:-}" ]] && error "No cached original found for installed Spotify ${installedVersion}.\nUse '--snap-file <path>' or '--download'."
|
||||
}
|
||||
[[ -n "${sourceSnap}" ]] || error "No unmodified Spotify snap source found.\nUse '--snap-file <path>' or '--download'."
|
||||
|
||||
echo -e "Using ${sourceOutput}\n"
|
||||
"${sudoCmd[@]}" cp -p -- "${sourceSnap}" "${sourceCopy}"
|
||||
sourceVerified=''
|
||||
verify_snap_source "${sourceCopy}" && {
|
||||
echo -e "${green}Verified official Spotify snap${clr}\n"
|
||||
} || {
|
||||
[[ "${allowUnverified}" ]] || error "Selected snap could not be authenticated as an official Spotify snap.\nConnect to the internet, place its matching assertion beside it or use '--allow-unverified'."
|
||||
echo -e "${yellow}Warning:${clr} Selected snap could not be authenticated and will not be cached.\n"
|
||||
}
|
||||
"${sudoCmd[@]}" unsquashfs -d "${snapRoot}" "${sourceCopy}" >/dev/null
|
||||
spotifyPath="${snapRoot}/usr/share/spotify"
|
||||
xpuiSpa="${spotifyPath}/Apps/xpui.spa"
|
||||
snapYaml="${snapRoot}/meta/snap.yaml"
|
||||
[[ -f "${xpuiSpa}" && -f "${spotifyPath}/spotify" ]] || error "Spotify client not found inside snap."
|
||||
[[ -f "${snapYaml}" ]] || error "Snap metadata not found."
|
||||
snapName=$("${sudoCmd[@]}" awk '$1 == "name:" { print $2; exit }' "${snapYaml}")
|
||||
snapName="${snapName#\"}"
|
||||
snapName="${snapName%\"}"
|
||||
snapName="${snapName#\'}"
|
||||
snapName="${snapName%\'}"
|
||||
[[ "${snapName}" == "spotify" ]] || error "Selected snap is not the Spotify snap."
|
||||
has_spotx_marker "${xpuiSpa}" && error "Selected snap source is already patched.\nUse an original snap file or '--download'."
|
||||
|
||||
snapVersion=$("${sudoCmd[@]}" awk '$1 == "version:" { print $2; exit }' "${snapYaml}")
|
||||
snapVersion="${snapVersion#\"}"
|
||||
snapVersion="${snapVersion%\"}"
|
||||
snapVersion="${snapVersion#\'}"
|
||||
snapVersion="${snapVersion%\'}"
|
||||
[[ -n "${snapVersion}" ]] || error "Unable to determine Spotify snap version."
|
||||
forcedVersion="${snapVersion%%.g*}"
|
||||
safeVersion=$(printf '%s' "${snapVersion}" | tr -c 'A-Za-z0-9._-' '_')
|
||||
sourceHash=$("${sudoCmd[@]}" sha256sum "${sourceCopy}" | awk '{ print $1 }')
|
||||
cacheFile="${cacheDir}/spotify_${safeVersion}_${sourceHash:0:12}.snap"
|
||||
[[ "${sourceVerified}" ]] && {
|
||||
"${sudoCmd[@]}" install -d -o "${runUid}" -g "${runGid}" -m 0755 "${cacheBase}" "${cacheDir}"
|
||||
[[ -f "${cacheFile}" ]] || {
|
||||
"${sudoCmd[@]}" install -o "${runUid}" -g "${runGid}" -m 0644 "${sourceCopy}" "${cacheFile}"
|
||||
echo -e "Cached original snap: ${cacheFile}\n"
|
||||
}
|
||||
}
|
||||
|
||||
echo -e "Patching Spotify ${forcedVersion} with SpotX-Bash...\n"
|
||||
"${sudoCmd[@]}" env SPOTX_BUILD_MODE=true bash "${spotxScript}" -P "${spotifyPath}" -F "${forcedVersion}" "${spotxArgs[@]}"
|
||||
"${sudoCmd[@]}" rm -f -- "${spotifyPath}/spotify.bak" "${spotifyPath}/Apps/xpui.bak"
|
||||
"${sudoCmd[@]}" unzip -tqq "${xpuiSpa}" || error "Patched xpui.spa validation failed."
|
||||
has_spotx_marker "${xpuiSpa}" || error "SpotX marker not found in patched xpui.spa."
|
||||
|
||||
echo -e "Packing patched Spotify snap...\n"
|
||||
"${sudoCmd[@]}" snap pack "${snapRoot}" "${packedDir}" >/dev/null
|
||||
packedSnap=''
|
||||
for candidate in "${packedDir}"/*.snap; do
|
||||
[[ -f "${candidate}" ]] || continue
|
||||
[[ -z "${packedSnap}" ]] || error "Multiple packed snap files found."
|
||||
packedSnap="${candidate}"
|
||||
done
|
||||
[[ -n "${packedSnap}" ]] || error "Patched snap was not created."
|
||||
"${sudoCmd[@]}" unsquashfs -s "${packedSnap}" >/dev/null || error "Patched snap validation failed."
|
||||
|
||||
outputName="Spotify.v${forcedVersion}.Linux.x64-SPOTX.snap"
|
||||
[[ "${buildOnly}" ]] && {
|
||||
mkdir -p "${outputDir}"
|
||||
outputDir=$(cd -- "${outputDir}" && pwd)
|
||||
outputFile="${outputDir}/${outputName}"
|
||||
[[ ! -e "${outputFile}" ]] || error "Output file already exists: ${outputFile}"
|
||||
"${sudoCmd[@]}" install -o "${runUid}" -g "${runGid}" -m 0644 "${packedSnap}" "${outputFile}"
|
||||
echo -e "${green}Created:${clr} ${outputFile}\n"
|
||||
exit 0
|
||||
}
|
||||
|
||||
command pkill -9 '[sS]potify' 2>/dev/null || true
|
||||
echo -e "${yellow}Warning:${clr} This locally installed snap will not receive automatic Spotify updates."
|
||||
echo -e "Re-run this helper to update it or use '--restore' to return to the official snap.\n"
|
||||
echo -e "Installing patched Spotify snap...\n"
|
||||
"${sudoCmd[@]}" snap install --dangerous "${packedSnap}"
|
||||
installedOutput=$(LC_ALL=C snap list spotify 2>/dev/null || true)
|
||||
installedVersion=$(awk 'NR == 2 { print $2 }' <<< "${installedOutput}")
|
||||
[[ -n "${installedVersion}" ]] || error "Spotify snap installation could not be verified."
|
||||
echo -e "\n${green}Installed:${clr} Spotify ${installedVersion} with SpotX-Bash"
|
||||
echo -e "Re-run this helper to rebuild from the cached original snap."
|
||||
echo -e "Restore the official snap with: ${yellow}bash spotx-snap.sh --restore${clr}\n"
|
||||
exit 0
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
buildVer="1.2.93.667.g7b5cc0ce"
|
||||
rollbackVer="1.2.92.148.g882cc571"
|
||||
buildVer="1.2.95.453.g0eeebbed"
|
||||
rollbackVer="1.2.94.583.g60394bd5"
|
||||
|
||||
latestB_X="2388"
|
||||
latestB_A="2385"
|
||||
rollbackB_X="26"
|
||||
rollbackB_A="26"
|
||||
latestB_X="3672"
|
||||
latestB_A="3672"
|
||||
rollbackB_X="4881"
|
||||
rollbackB_A="4872"
|
||||
|
||||
clr='\033[0m'
|
||||
green='\033[0;32m'
|
||||
@@ -47,45 +47,45 @@ show_help() {
|
||||
}
|
||||
|
||||
latestA_X=$(printf "%s" \
|
||||
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODU2NzE4OTAsIm5iZiI6" \
|
||||
"MTc4MzA3OTg5MCwicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gteDg2XzY0L3Nw" \
|
||||
"b3RpZnktYXV0b3VwZGF0ZS0xLjIuOTMuNjY3Lmc3YjVjYzBjZS0yMzg4LnRieiJ9" \
|
||||
".Ms9wZE1ISP7F3VkbBj5W4aCB4OhuG-TW9Crd99p0ruuzr9KQ38V-8JxntTZ_5YJ" \
|
||||
"-uVvhomfiR-ZiSHe2VLP1HEbrnfGqPiU7UN-SLw5IV6pHRp2uRU28VX__Bw06OBT" \
|
||||
"VUptwrVuaCKxooHkhrSFM49px0dWJPg3QwS0oNNK-lcVmchEvw4Dh9Jm9Gd8EpWq" \
|
||||
"3_lCDmZKKyoBPJE2PGkQAjCl1J2YJNcppsNaebWbYF0WDdlH627tu-v07O2rVmxJ" \
|
||||
"OyHUslFvpITv1CwDNg7plxFEbdbDeZM1XKjlGeggpkJy-3pV4DzzPfBe1E1dTGUc" \
|
||||
"y_8YZEw6uil72fLs653LqQA")
|
||||
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODg1NjYyOTMsIm5iZiI6" \
|
||||
"MTc4NTk3NDI5MywicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gteDg2XzY0L3Nw" \
|
||||
"b3RpZnktYXV0b3VwZGF0ZS0xLjIuOTUuNDUzLmcwZWVlYmJlZC0zNjcyLnRieiJ9" \
|
||||
".FQBMTRH3659zzbVTZn-IqCqpqBJwyd7h3l33pZ8gYrO-ko8cmdS0w2bxzO46h9n" \
|
||||
"7lV-ES8-aVXyzt0S57c8T3eNGkgz049amd-AgMJrtMSnhpGkmxnrzNwPUtvwWZUd" \
|
||||
"HqH4edpN2WYHqSmclz8Xt82FY2FeKkfzF-pE0RzJEBVdW3ZXqvQcPNtO_0az4LGW" \
|
||||
"qjbA7ZKd404-AGzBqrvJ7G_48XttT8xndYkVT2dTuNypZPoYzFCQ5Oh4I5XudlLB" \
|
||||
"uztQkf9kweo3LLwgfy1k2wlj012Iqiz7WUDofHLzYSTjR_1FA4JDGCAH5E_Uoaa6" \
|
||||
"Nnq-EChcnipD5WLpUon6AWQ")
|
||||
latestA_A=$(printf "%s" \
|
||||
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODU2NzE4ODksIm5iZiI6" \
|
||||
"MTc4MzA3OTg4OSwicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gtYXJtNjQvc3Bv" \
|
||||
"dGlmeS1hdXRvdXBkYXRlLTEuMi45My42NjcuZzdiNWNjMGNlLTIzODUudGJ6In0." \
|
||||
"VEKJVarKFhm6__4P0LCuu8zma7NcFGu__Gwqsb97WG3phWkE9G6Bk1EvyOGxZRu0" \
|
||||
"GAkqh0UZQm2OCTOio6kUbrmzudYYN5nd6bHht8yCh0U7u9y5YngS9zb7k8eh6yaN" \
|
||||
"Z1UjtULj4YKaekRDk90Nqzukf88U6PSoKuVvLywpymjMb10tLGKCvpC84sZyKyAm" \
|
||||
"08C0tOxB4lW2ci5nrvcRDcCV4sRMM_FXs3WSS4Byst0_4XpCbCs5Zu0QGpcTd_xv" \
|
||||
"n6T7nqOl4JCQoaDcbNLpqbAmPyKana2CNJc4l-iaqNp4QDcS_48dTOoF5rPbAcOU" \
|
||||
"0ca-VIBUueYh1NEeSLV-jw")
|
||||
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODg1NjYyOTMsIm5iZiI6" \
|
||||
"MTc4NTk3NDI5MywicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gtYXJtNjQvc3Bv" \
|
||||
"dGlmeS1hdXRvdXBkYXRlLTEuMi45NS40NTMuZzBlZWViYmVkLTM2NzIudGJ6In0." \
|
||||
"fRBIOAjThS9W-m_0EEw7oxd2tpIViU26gBsVdScwrnaT1XtJxhLp28bP26kd6pF4" \
|
||||
"DsFxz5lmb5UogRCMhjdLMVvwUrJlMYR_IDKOkWxiOzFXG-CBEBCbylkm-hT5JG85" \
|
||||
"fy7u5ZtXq-fUiyl5VEchP1vYH8JMZiHHC4i2V1SM9O1ExOeqxNccu_4m29Hh4gFK" \
|
||||
"5qPIMnlD8egTqy_wstsZa7M5sHkmdjsJ4-SXxGy87ONtnbO9UxPOEQYemNOf9Zqq" \
|
||||
"iGRs10px09Mb4Mk-ZnBiLl9jhLrTsVHSnBHr4Alet1D7kcMmxRnFxkBAeUHCaTw3" \
|
||||
"xUtqn8T49yF538bgsKK5kw")
|
||||
rollbackA_X=$(printf "%s" \
|
||||
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODU2NjQ2ODQsIm5iZiI6" \
|
||||
"MTc4MzA3MjY4NCwicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gteDg2XzY0L3Nw" \
|
||||
"b3RpZnktYXV0b3VwZGF0ZS0xLjIuOTIuMTQ4Lmc4ODJjYzU3MS0yNi50YnoifQ.X" \
|
||||
"NWUbG3S_HJnu8qGgny--Qax5UThn-FqjBR9fQ_MjTsahJcs1It7Vp34QMlhuO7ny" \
|
||||
"qOpql68OpvYdILcsJZgZeSqub0SV4VcTH1uY1d5yfDG8YDVF5kHgR0Nl3oKqCHSm" \
|
||||
"EASEuwnhTONspAwKdyr6PhrWR5IpznUWtYsQe_OMwGNb3ycS6eJVuBOOQT-q-m6x" \
|
||||
"XV5aQEcWXbB0D_eR9MCYvQnWJ0qsPRKT-fPj8pVc1arF0GsUx73dszYvqKGItsWl" \
|
||||
"PXMakY48wkhSidqji8FnhAxJIA0OPXkscsjPz5Mk3li6QeiRNxNi_ZUqloGJzhZH" \
|
||||
"RjFeEXP_PVoQqJlSg5bFQ")
|
||||
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODg0Mzg0ODksIm5iZiI6" \
|
||||
"MTc4NTg0NjQ4OSwicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gteDg2XzY0L3Nw" \
|
||||
"b3RpZnktYXV0b3VwZGF0ZS0xLjIuOTQuNTgzLmc2MDM5NGJkNS00ODgxLnRieiJ9" \
|
||||
".VjPA-sh1Tjg72unZXmb-Mn-NRL64czXwOMkQzy0IN6gsRaYrwqsIXAMPldiJHgo" \
|
||||
"SSp6HtSBkepcI9bagoM6US0rT9FEDkrjUn03kp4wl0tHxyahfDVFCbAMskc5VCVf" \
|
||||
"yb1LK_GhNX2bvzTz9lLq1tSlNXclQNQB-NH21IfnvUViNF6-eXoTkvPSMNsLNg-n" \
|
||||
"t8kOePUNm1ypOZ4SgJxxUyz4QaIftyvIH-7PEeyrbgIyKYukEB8RbtjjPlH3DfUi" \
|
||||
"lmYMaJKOuDz2dww_r-4nPMIdCABdwyPj7RzZ1Yx5wMGVpAVpbNYg5nqRrTOs2PxO" \
|
||||
"lmmjgMDdLBfx6PKz1eww6Xw")
|
||||
rollbackA_A=$(printf "%s" \
|
||||
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODU2NjQ2ODMsIm5iZiI6" \
|
||||
"MTc4MzA3MjY4MywicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gtYXJtNjQvc3Bv" \
|
||||
"dGlmeS1hdXRvdXBkYXRlLTEuMi45Mi4xNDguZzg4MmNjNTcxLTI2LnRieiJ9.tbq" \
|
||||
"9qTZDPSDOUnK19LOAM87WNg-_48dLplrgwllprPNdZSP47bG0KZ0BSZHEbrR9v5O" \
|
||||
"rV9RWJZLinVA4hYQ2N5eH0wUPXz7JnIBL7hiAzg032vfZ4SGIn0xPtKSRwzQxJXJ" \
|
||||
"Wc9aaVM63i2WRelRlet7U12asFyjNg4JAoQs_azaKQHjVIPPzfaUgNt929CiX2kn" \
|
||||
"EPvrDe0OxZT7vjfqm9MRHZaAl0hllOOZ-dD1UPR49Q6P_Wwi7gJjMmEIzfDD1ZHI" \
|
||||
"iiLod5-N-ez5MpCX2rWt1LAq8zLPR-jAnUw2ERIblcmp_ROgrpTc5po1mkKz1L_g" \
|
||||
"GQCFP7QJzhYBeiAyJUQ")
|
||||
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODg0Mzg0ODksIm5iZiI6" \
|
||||
"MTc4NTg0NjQ4OSwicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gtYXJtNjQvc3Bv" \
|
||||
"dGlmeS1hdXRvdXBkYXRlLTEuMi45NC41ODMuZzYwMzk0YmQ1LTQ4NzIudGJ6In0." \
|
||||
"Ch1itbFNAno5o7YDJ_Eh2ZjHuA12PIvkWrBIJmbHAH6AqdUXC2gbD4znygHVWtPn" \
|
||||
"wc74tChZKO_-a79N0v3d26P6_rjPUir3hbtRn09YFzd4Tz4IZXHKY_bGg8YZI4rO" \
|
||||
"0pijbtgVQ1dnLOG90aTROC8jCSpwj4dBmzEBwKgE68J_n5sEJJMxNNHCzpV5P-mC" \
|
||||
"MHQiardZdR1zShvBy6d4CSzcaIiTZFTfeCnnyc8eyV9cY2wf18f4uEDgaZs90KKd" \
|
||||
"J-MpU41Anjpa5yk2SUVsTSHJsKbR4s4vbW25jw2Z5rlfBJsegbxLuNAu00t9Y14R" \
|
||||
"8eXZeGV3NgCrQCddw1sIzA")
|
||||
|
||||
while getopts ':BcdefF:hilopP:SvV:-:' flag; do
|
||||
case "${flag}" in
|
||||
@@ -107,7 +107,7 @@ while getopts ':BcdefF:hilopP:SvV:-:' flag; do
|
||||
noexp) excludeExp='true' ;;
|
||||
oldui) oldUi='true' ;;
|
||||
premium) paidPremium='true' ;;
|
||||
rollback) [[ "${platformType}" == "macOS" ]] && rollback='true'; installMac='true' ;;
|
||||
rollback) [[ "${platformType}" == "macOS" ]] && { rollback='true'; installMac='true'; } ;;
|
||||
skipcodesign) [[ "${platformType}" == "macOS" ]] && skipCodesign='true' ;;
|
||||
stable) [[ "${platformType}" == "Linux" ]] && stableVar='true' ;;
|
||||
uninstall) uninstallSpotx='true' ;;
|
||||
@@ -142,7 +142,7 @@ sxbLiveVer=$(printf "%s" \
|
||||
"kcwwmW0V0VPRXQ6dlb1MEWyF1RYV3dxs0a4xGTjR3QaNWNDhlcRdEWvhTeKdWVtJGd" \
|
||||
"BNkY5Z1Rjd2dIlUaw42YspVMadjUpl0Z3BzY0F0UjRXQDJWeWNTW" \
|
||||
| rev | base64 --decode | base64 --decode)
|
||||
sxbLive=$(eval "${sxbLiveVer}")
|
||||
[[ "${SPOTX_BUILD_MODE}" ]] && sxbLive="${buildVer}" || sxbLive=$(eval "${sxbLiveVer}")
|
||||
sxbVer=$(echo ${buildVer} | perl -ne '/(.*)\./ && print "$1"')
|
||||
verCk=$(printf "%s" \
|
||||
"9QzRYNGayMGaKdUZwkzRjpXODplb1k3YwJ0QRdWVHJWaGdkYwZUbkhmQ5NGcCNl" \
|
||||
@@ -277,16 +277,18 @@ macos_prepare() {
|
||||
}
|
||||
|
||||
linux_client_variant() {
|
||||
[[ "${installPath}" == *"flatpak"* ]] && {
|
||||
command -v flatpak >/dev/null && flatpak list | grep spotify >/dev/null && {
|
||||
flatpakVer=$(LANG=C.UTF-8 flatpak info com.spotify.Client | grep Version: | perl -ne '/Version: (1\.[0-9]+\.[0-9]+\.[0-9]+)\.g[0-9a-f]+/ && print "$1"')
|
||||
[[ -z "${flatpakVer+x}" ]] && versionFailed='true' || { clientVer="${flatpakVer}"; flatpakClient='true'; }
|
||||
cachePath=$(timeout 10 find /var/lib/flatpak/ $HOME/.var/app -type d -path "*com.spotify.Client/cache/spotify*" -name "spotify" -print -quit 2>/dev/null)
|
||||
[[ "${clientVariant}" == "flatpak" || "${installPath}" == *"flatpak"* ]] && {
|
||||
command -v flatpak >/dev/null && flatpak info com.spotify.Client >/dev/null 2>&1 && {
|
||||
flatpakVer=$(LC_ALL=C flatpak info com.spotify.Client 2>/dev/null | perl -ne '/Version: (1\.[0-9]+\.[0-9]+\.[0-9]+)\.g[0-9a-f]+/ && print "$1"')
|
||||
[[ -z "${flatpakVer}" ]] && versionFailed='true' || { clientVer="${flatpakVer}"; flatpakClient='true'; }
|
||||
cachePath="${HOME}/.var/app/com.spotify.Client/cache/spotify"
|
||||
[[ -d "${cachePath}" ]] || unset cachePath
|
||||
}
|
||||
return 0
|
||||
}
|
||||
[[ "${installPath}" == *"opt/spotify"* || "${installPath}" == *"spotify-launcher"* || "${installPath}" == *"usr/share/spotify"* ]] && {
|
||||
cachePath=$(timeout 10 find $HOME/.cache/ -type d -path "*.cache/spotify*" -not -path "*snap/spotify*" -name "spotify" -print -quit 2>/dev/null)
|
||||
cachePath="${HOME}/.cache/spotify"
|
||||
[[ -d "${cachePath}" ]] || unset cachePath
|
||||
return 0
|
||||
}
|
||||
return 0
|
||||
@@ -312,7 +314,7 @@ linux_deb_prepare() {
|
||||
|
||||
linux_no_client() {
|
||||
command -v snap >/dev/null && snap list spotify &>/dev/null && {
|
||||
echo -e "${red}Error:${clr} Snap client not supported. See FAQ for more info.\nIf another Spotify package is installed, set directory path with '-P' flag.\n" >&2
|
||||
echo -e "${red}Error:${clr} Snap client requires spotx-snap.sh. See FAQ for more info.\nIf another Spotify package is installed, set directory path with '-P' flag.\n" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v apt >/dev/null && {
|
||||
@@ -326,16 +328,56 @@ linux_no_client() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
linux_resolve_client_path() {
|
||||
local base="${1%/}" candidate
|
||||
[[ -n "${base}" ]] || return 1
|
||||
for candidate in \
|
||||
"${base}" \
|
||||
"${base}/extra/share/spotify" \
|
||||
"${base}/share/spotify" \
|
||||
"${base}/files/extra/share/spotify" \
|
||||
"${base}/files/share/spotify"; do
|
||||
[[ -f "${candidate}/Apps/xpui.spa" ]] && {
|
||||
installPath="${candidate}"
|
||||
return 0
|
||||
}
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
linux_search_path() {
|
||||
local paths=("/opt" "/usr/share" "/var/lib/flatpak" "$HOME/.local/share" "/")
|
||||
local paths=("/opt" "/usr/share" "/usr/lib" "$HOME/.local/share" "/var/lib/flatpak/app/com.spotify.Client")
|
||||
local flatpakPath spotifyBinary xpuiFile path
|
||||
linux_resolve_client_path "/opt/spotify" && return 0
|
||||
linux_resolve_client_path "/usr/share/spotify" && return 0
|
||||
linux_resolve_client_path "$HOME/.local/share/spotify-launcher/install/usr/share/spotify" && return 0
|
||||
spotifyBinary=$(command -v spotify 2>/dev/null)
|
||||
[[ -n "${spotifyBinary}" ]] && {
|
||||
spotifyBinary=$(readlink -f "${spotifyBinary}" 2>/dev/null || printf '%s' "${spotifyBinary}")
|
||||
linux_resolve_client_path "${spotifyBinary%/*}" && return 0
|
||||
}
|
||||
command -v flatpak >/dev/null && {
|
||||
flatpakPath=$(flatpak info --show-location com.spotify.Client 2>/dev/null)
|
||||
[[ -n "${flatpakPath}" ]] && linux_resolve_client_path "${flatpakPath}" && {
|
||||
clientVariant='flatpak'
|
||||
return 0
|
||||
}
|
||||
}
|
||||
for path in "${paths[@]}"; do
|
||||
installPath=$(timeout 6 find "${path}" -type f -path "*/spotify*Apps/*" -not -path "*snapd/snap*" -not -path "*snap/spotify*" -not -path "*snap/bin*" -not -path "*flatpak/.removed*" -name "xpui.spa" -size -20M -size +3M -print -quit 2>/dev/null | rev | cut -d/ -f3- | rev)
|
||||
[[ -n "${installPath}" ]] && return 0
|
||||
[[ -d "${path}" ]] || continue
|
||||
xpuiFile=$(timeout 6 find "${path}" \
|
||||
\( -path "*/flatpak/.removed" -o -path "*/snap" -o -path "*/snapd/snap" \) -prune -o \
|
||||
-type f -path "*/Apps/xpui.spa" -print -quit 2>/dev/null)
|
||||
[[ -n "${xpuiFile}" ]] && {
|
||||
installPath="${xpuiFile%/Apps/xpui.spa}"
|
||||
return 0
|
||||
}
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
linux_set_path() {
|
||||
local requestedPath
|
||||
[[ "${installDeb}" ]] && { linux_deb_prepare; return; }
|
||||
[[ -z "${installPath+x}" ]] && {
|
||||
echo -e "Searching for client directory...\n"
|
||||
@@ -347,11 +389,13 @@ linux_set_path() {
|
||||
} || linux_no_client
|
||||
return
|
||||
}
|
||||
[[ "${installPath}" == *"snapd/snap"* || "${installPath}" == *"snap/spotify"* || "${installPath}" == *"snap/bin"* ]] && {
|
||||
echo -e "${red}Error:${clr} Snap client not supported. See FAQ for more info.\n" >&2
|
||||
requestedPath="${installPath%/}"
|
||||
[[ "${requestedPath}" == *"snapd/snap"* || "${requestedPath}" == *"snap/spotify"* || "${requestedPath}" == *"snap/bin"* ]] && {
|
||||
echo -e "${red}Error:${clr} Snap client requires spotx-snap.sh. See FAQ for more info.\n" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ -f "${installPath}/Apps/xpui.spa" ]] && {
|
||||
linux_resolve_client_path "${requestedPath}" && {
|
||||
installOutput=$(echo "${installPath}" | perl -pe 's|^$ENV{HOME}|~|')
|
||||
echo -e "Using client Directory: ${installOutput}\n"
|
||||
linux_client_variant
|
||||
} || {
|
||||
@@ -368,7 +412,6 @@ linux_prepare() {
|
||||
appBak="${appBinary}.bak"
|
||||
snapshotBinary="${appPath}/v8_context_snapshot.bin"
|
||||
xpuiPath="${appPath}/Apps"
|
||||
[[ -z "${cachePath}" ]] && cachePath=$(timeout 10 find / -type d -path "*cache/spotify*" -not -path "*snap/spotify*" -name "spotify" -print -quit 2>/dev/null)
|
||||
[[ "${debug}" ]] && echo -e "${green}Debug:${clr} $(cat /etc/*release | grep PRETTY_NAME | cut -d '"' -f2)"
|
||||
[[ "${debug}" ]] && echo -e "${green}Debug:${clr} $(uname -m) detected"
|
||||
[[ "${debug}" ]] && command -v apt >/dev/null && echo -e "${green}Debug:${clr} APT detected"
|
||||
@@ -438,14 +481,27 @@ run_prepare() {
|
||||
(($(ver "${clientVer}") > $(ver "${legacyMaxVer}"))) && macos_legacy_notice "toohigh"
|
||||
client_version_output
|
||||
ver_check
|
||||
command pkill -9 '[sS]potify' 2>/dev/null
|
||||
[[ -z "${SPOTX_BUILD_MODE}" ]] && command pkill -9 '[sS]potify' 2>/dev/null
|
||||
[[ -f "${appBinary}" ]] && cleanAB=$(perl -ne '$found1 = 1 if /\x00\x73\x6C\x6F\x74\x73\x00/; $found2 = 1 if /\x2D\x70\x72\x65\x72\x6F\x6C\x6C/; END { print "true" if $found1 && $found2 }' "${appBinary}")
|
||||
}
|
||||
|
||||
check_write_permission() {
|
||||
local target_user="${SUDO_USER:-$(id -un)}"
|
||||
local writePath
|
||||
[[ "${platformType}" == "Linux" && -z "${SPOTX_BUILD_MODE}" ]] && ((EUID == 0)) && {
|
||||
stagedInstall='true'
|
||||
protectedInstall='true'
|
||||
}
|
||||
for path_to_check in "$@"; do
|
||||
[[ ! -w "${path_to_check}" ]] && {
|
||||
[[ -d "${path_to_check}" ]] && writePath="${path_to_check}" || writePath="${path_to_check%/*}"
|
||||
[[ ! -w "${path_to_check}" ]] && stagedInstall='true'
|
||||
[[ ! -w "${writePath}" ]] && {
|
||||
stagedInstall='true'
|
||||
protectedInstall='true'
|
||||
((EUID == 0)) && continue
|
||||
command -v sudo >/dev/null || {
|
||||
echo -e "\n${red}Error:${clr} sudo command not found. Install sudo or run this script as root.\n" >&2
|
||||
exit 1
|
||||
}
|
||||
sudo -n true 2>/dev/null || {
|
||||
echo -e "${yellow}Warning:${clr} SpotX-Bash does not have write permission in client directory.\nRequesting sudo permission..." >&2
|
||||
sudo -v || {
|
||||
@@ -453,17 +509,172 @@ check_write_permission() {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
sudo chown -R "${target_user}" "${path_to_check}"
|
||||
sudo chmod -R u+rwX,go-w "${path_to_check}"
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
sudo_run() {
|
||||
if ((EUID == 0)) || [[ -z "${protectedInstall+x}" ]]; then
|
||||
command "$@"
|
||||
else
|
||||
sudo "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
protected_stage_copy() {
|
||||
local source="${1}" destination="${2}"
|
||||
sudo_run cat -- "${source}" > "${destination}" || {
|
||||
rm -f -- "${destination}"
|
||||
return 1
|
||||
}
|
||||
chmod 600 "${destination}"
|
||||
}
|
||||
|
||||
protected_stage_prepare() {
|
||||
targetAppBinary="${appBinary}"
|
||||
targetAppBak="${appBak}"
|
||||
targetXpuiPath="${xpuiPath}"
|
||||
targetXpuiSpa="${xpuiSpa}"
|
||||
targetXpuiBak="${xpuiBak}"
|
||||
linux_working_dir
|
||||
appPath="${workDir}/client"
|
||||
appBinary="${appPath}/spotify"
|
||||
appBak="${appBinary}.bak"
|
||||
xpuiPath="${appPath}/Apps"
|
||||
xpuiBak="${xpuiPath}/xpui.bak"
|
||||
xpuiDir="${xpuiPath}/xpui"
|
||||
xpuiSpa="${xpuiPath}/xpui.spa"
|
||||
dwpPanelSectionJs="${xpuiDir}/dwp-panel-section.js"
|
||||
homeHptoJs="${xpuiDir}/home-hpto.js"
|
||||
homeV2Js="${xpuiDir}/home-v2.js"
|
||||
indexHtml="${xpuiDir}/index.html"
|
||||
vendorXpuiJs="${xpuiDir}/vendor~xpui.js"
|
||||
xpuiCss="${xpuiDir}/xpui.css"
|
||||
xpuiDesktopModalsJs="${xpuiDir}/xpui-desktop-modals.js"
|
||||
xpuiJs="${xpuiDir}/xpui.js"
|
||||
xpuiSnapshotJs="${xpuiDir}/xpui-snapshot.js"
|
||||
mkdir -p "${xpuiPath}" || exit 1
|
||||
protected_stage_copy "${targetAppBinary}" "${appBinary}" || exit 1
|
||||
protected_stage_copy "${targetXpuiSpa}" "${xpuiSpa}" || exit 1
|
||||
[[ -f "${targetAppBak}" ]] && protected_stage_copy "${targetAppBak}" "${appBak}"
|
||||
[[ -f "${targetXpuiBak}" ]] && protected_stage_copy "${targetXpuiBak}" "${xpuiBak}"
|
||||
}
|
||||
|
||||
protected_prepare_file() {
|
||||
local source="${1}" destination="${2}" reference="${3}" tempFile
|
||||
tempFile=$(sudo_run mktemp "${destination}.spotx.XXXXXXXX") || return 1
|
||||
sudo_run cp -a -- "${reference}" "${tempFile}" &&
|
||||
sudo_run cp -- "${source}" "${tempFile}" &&
|
||||
sudo_run chown --reference="${reference}" "${tempFile}" &&
|
||||
sudo_run chmod --reference="${reference}" "${tempFile}" &&
|
||||
sudo_run touch -r "${reference}" "${tempFile}" || {
|
||||
sudo_run rm -f -- "${tempFile}"
|
||||
return 1
|
||||
}
|
||||
printf '%s\n' "${tempFile}"
|
||||
}
|
||||
|
||||
protected_save_destination() {
|
||||
local destination="${1}" tempFile
|
||||
[[ -e "${destination}" ]] || return 0
|
||||
tempFile=$(sudo_run mktemp "${destination}.spotx-rollback.XXXXXXXX") || return 1
|
||||
sudo_run cp -a -- "${destination}" "${tempFile}" || {
|
||||
sudo_run rm -f -- "${tempFile}"
|
||||
return 1
|
||||
}
|
||||
printf '%s\n' "${tempFile}"
|
||||
}
|
||||
|
||||
protected_restore_destination() {
|
||||
local backup="${1}" destination="${2}" existed="${3}"
|
||||
[[ "${existed}" ]] && sudo_run mv -f -- "${backup}" "${destination}" || sudo_run rm -f -- "${destination}"
|
||||
}
|
||||
|
||||
protected_remove_files() {
|
||||
local file
|
||||
for file in "$@"; do
|
||||
[[ -n "${file}" ]] && sudo_run rm -f -- "${file}"
|
||||
done
|
||||
}
|
||||
|
||||
protected_commit() {
|
||||
local uninstall="${1:-}" appFile spaFile appBakFile='' xpuiBakFile=''
|
||||
local appRollback='' spaRollback='' appBakRollback='' xpuiBakRollback=''
|
||||
local appExisted='' spaExisted='' appBakExisted='' xpuiBakExisted=''
|
||||
local appBakReference="${targetAppBinary}" xpuiBakReference="${targetXpuiSpa}"
|
||||
[[ -f "${appBinary}" && -f "${xpuiSpa}" ]] || return 1
|
||||
unzip -tqq "${xpuiSpa}" || return 1
|
||||
appFile=$(protected_prepare_file "${appBinary}" "${targetAppBinary}" "${targetAppBinary}") || return 1
|
||||
spaFile=$(protected_prepare_file "${xpuiSpa}" "${targetXpuiSpa}" "${targetXpuiSpa}") || {
|
||||
sudo_run rm -f -- "${appFile}"
|
||||
return 1
|
||||
}
|
||||
[[ -z "${uninstall}" ]] && {
|
||||
[[ -e "${targetAppBak}" ]] && appBakReference="${targetAppBak}"
|
||||
[[ -e "${targetXpuiBak}" ]] && xpuiBakReference="${targetXpuiBak}"
|
||||
appBakFile=$(protected_prepare_file "${appBak}" "${targetAppBak}" "${appBakReference}") || {
|
||||
sudo_run rm -f -- "${appFile}" "${spaFile}"
|
||||
return 1
|
||||
}
|
||||
xpuiBakFile=$(protected_prepare_file "${xpuiBak}" "${targetXpuiBak}" "${xpuiBakReference}") || {
|
||||
sudo_run rm -f -- "${appFile}" "${spaFile}" "${appBakFile}"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
[[ -e "${targetAppBinary}" ]] && appExisted='true'
|
||||
[[ -e "${targetXpuiSpa}" ]] && spaExisted='true'
|
||||
[[ -e "${targetAppBak}" ]] && appBakExisted='true'
|
||||
[[ -e "${targetXpuiBak}" ]] && xpuiBakExisted='true'
|
||||
{
|
||||
appRollback=$(protected_save_destination "${targetAppBinary}") &&
|
||||
spaRollback=$(protected_save_destination "${targetXpuiSpa}")
|
||||
[[ "${uninstall}" ]] || {
|
||||
appBakRollback=$(protected_save_destination "${targetAppBak}") &&
|
||||
xpuiBakRollback=$(protected_save_destination "${targetXpuiBak}")
|
||||
}
|
||||
} || {
|
||||
protected_remove_files "${appFile}" "${spaFile}" "${appBakFile}" "${xpuiBakFile}"
|
||||
protected_remove_files "${appRollback}" "${spaRollback}" "${appBakRollback}" "${xpuiBakRollback}"
|
||||
return 1
|
||||
}
|
||||
{
|
||||
{ [[ "${uninstall}" ]] || sudo_run mv -f -- "${appBakFile}" "${targetAppBak}"; } &&
|
||||
{ [[ "${uninstall}" ]] || sudo_run mv -f -- "${xpuiBakFile}" "${targetXpuiBak}"; } &&
|
||||
sudo_run mv -f -- "${appFile}" "${targetAppBinary}" &&
|
||||
sudo_run mv -f -- "${spaFile}" "${targetXpuiSpa}"
|
||||
} || {
|
||||
protected_restore_destination "${appRollback}" "${targetAppBinary}" "${appExisted}"
|
||||
protected_restore_destination "${spaRollback}" "${targetXpuiSpa}" "${spaExisted}"
|
||||
[[ "${uninstall}" ]] || protected_restore_destination "${appBakRollback}" "${targetAppBak}" "${appBakExisted}"
|
||||
[[ "${uninstall}" ]] || protected_restore_destination "${xpuiBakRollback}" "${targetXpuiBak}" "${xpuiBakExisted}"
|
||||
protected_remove_files "${appFile}" "${spaFile}" "${appBakFile}" "${xpuiBakFile}"
|
||||
return 1
|
||||
}
|
||||
protected_remove_files "${appRollback}" "${spaRollback}" "${appBakRollback}" "${xpuiBakRollback}"
|
||||
[[ "${uninstall}" ]] && sudo_run rm -f -- "${targetAppBak}" "${targetXpuiBak}"
|
||||
return 0
|
||||
}
|
||||
|
||||
atomic_copy() {
|
||||
local source="${1}" destination="${2}" tempFile result
|
||||
copyTempDir=$(mktemp -d "${destination}.spotx.XXXXXXXX") || return 1
|
||||
tempFile="${copyTempDir}/${destination##*/}"
|
||||
cp "${source}" "${tempFile}" && mv -f "${tempFile}" "${destination}"
|
||||
result=$?
|
||||
rm -rf "${copyTempDir}" 2>/dev/null
|
||||
[[ ! -d "${copyTempDir}" ]] && unset copyTempDir
|
||||
return "${result}"
|
||||
}
|
||||
|
||||
backup_spotx() {
|
||||
atomic_copy "${xpuiSpa}" "${xpuiBak}" && atomic_copy "${appBinary}" "${appBak}" && return 0
|
||||
rm -f "${appBak}" "${xpuiBak}" 2>/dev/null
|
||||
return 1
|
||||
}
|
||||
|
||||
uninstall_spotx() {
|
||||
rm -f "${appBinary}" 2>/dev/null
|
||||
mv -f "${appBak}" "${appBinary}"
|
||||
rm -f "${xpuiSpa}" 2>/dev/null
|
||||
mv -f "${xpuiBak}" "${xpuiSpa}"
|
||||
atomic_copy "${appBak}" "${appBinary}" && atomic_copy "${xpuiBak}" "${xpuiSpa}" || return 1
|
||||
rm -f "${appBak}" "${xpuiBak}" 2>/dev/null
|
||||
rm -rf "${xpuiDir}" 2>/dev/null
|
||||
}
|
||||
|
||||
@@ -474,12 +685,23 @@ run_uninstall_check() {
|
||||
exit 1
|
||||
}
|
||||
check_write_permission "${appPath}" "${appBinary}" "${xpuiPath}" "${xpuiSpa}"
|
||||
[[ "${platformType}" == "Linux" && "${stagedInstall}" ]] && protected_stage_prepare
|
||||
[[ "${cleanAB}" ]] && {
|
||||
echo -e "${yellow}Warning:${clr} SpotX-Bash has detected abnormal behavior.\nClient reinstallation may be required...\n" >&2
|
||||
rm -f "${appBak}" 2>/dev/null
|
||||
rm -f "${xpuiBak}" 2>/dev/null
|
||||
[[ "${platformType}" == "Linux" && "${stagedInstall}" ]] && sudo_run rm -f -- "${targetAppBak}" "${targetXpuiBak}"
|
||||
} || {
|
||||
uninstall_spotx
|
||||
uninstall_spotx || {
|
||||
echo -e "\n${red}Error:${clr} Failed to restore client. Backups were preserved.\n" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "${platformType}" == "Linux" && "${stagedInstall}" ]] && {
|
||||
protected_commit 'true' || {
|
||||
echo -e "\n${red}Error:${clr} Failed to restore client. Original files restored.\n" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
printf "\xE2\x9C\x94\x20\x46\x69\x6E\x69\x73\x68\x65\x64\x20\x75\x6E\x69\x6E\x73\x74\x61\x6C\x6C\n\n"
|
||||
exit 0
|
||||
@@ -541,7 +763,33 @@ sudo_check() {
|
||||
}
|
||||
}
|
||||
|
||||
linux_working_dir() { [[ -d "/tmp" ]] && workDir="/tmp" || workDir="$HOME"; }
|
||||
cleanup_temp_dirs() {
|
||||
[[ -n "${macInstallOld:-}" && -d "${macInstallOld}" && "${macInstallOld%/*}" == "${installPath:-}" && "${macInstallOld##*/}" == .spotx-previous.* ]] && {
|
||||
[[ "${macInstallOldMoved:-}" && -z "${macInstallCommitted:-}" && ! -e "${appPath:-}" ]] && mv -- "${macInstallOld}" "${appPath}"
|
||||
[[ -z "${macInstallOldMoved:-}" && -d "${macInstallOld}" ]] && rm -rf -- "${macInstallOld}"
|
||||
[[ "${macInstallCommitted:-}" && -d "${macInstallOld}" ]] && rm -rf -- "${macInstallOld}"
|
||||
}
|
||||
[[ -n "${macInstallTemp:-}" && -d "${macInstallTemp}" && "${macInstallTemp%/*}" == "${installPath:-}" && "${macInstallTemp##*/}" == .spotx-install.* ]] && rm -rf -- "${macInstallTemp}"
|
||||
[[ -n "${macDownloadPath:-}" && -f "${macDownloadPath}" && "${macDownloadPath%/*}" == "${HOME}/Downloads" && "${macDownloadPath##*/}" == "${fileVar:-}" ]] && rm -f -- "${macDownloadPath}"
|
||||
[[ -n "${workDir:-}" && -d "${workDir}" && "${workDir##*/}" == spotx-bash.* ]] && rm -rf -- "${workDir}"
|
||||
[[ -n "${copyTempDir:-}" && -d "${copyTempDir}" && "${copyTempDir##*/}" == *.spotx.* ]] && rm -rf -- "${copyTempDir}"
|
||||
[[ -n "${spaTempDir:-}" && -d "${spaTempDir}" && "${spaTempDir##*/}" == .spotx-spa.* ]] && rm -rf -- "${spaTempDir}"
|
||||
[[ "${xpuiTempCreated:-}" && -n "${xpuiDir:-}" && -d "${xpuiDir}" && "${xpuiDir##*/}" == "xpui" ]] && rm -rf -- "${xpuiDir}"
|
||||
}
|
||||
|
||||
linux_working_dir() {
|
||||
local tempBase="${TMPDIR:-/tmp}"
|
||||
[[ -n "${workDir:-}" && -d "${workDir}" && "${workDir##*/}" == spotx-bash.* ]] && return
|
||||
[[ "${tempBase}" == /* && -d "${tempBase}" && -w "${tempBase}" ]] || tempBase="/tmp"
|
||||
workDir=$(mktemp -d "${tempBase%/}/spotx-bash.XXXXXXXX") || {
|
||||
echo -e "${red}Error:${clr} Failed to create temporary working directory.\n" >&2
|
||||
exit 1
|
||||
}
|
||||
chmod 700 "${workDir}" || {
|
||||
echo -e "${red}Error:${clr} Failed to secure temporary working directory.\n" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
linux_deb_install() {
|
||||
sudo_check
|
||||
@@ -587,6 +835,7 @@ linux_deb_install() {
|
||||
}
|
||||
|
||||
macos_client_install() {
|
||||
local downloadPath="${HOME}/Downloads/${fileVar}" stagedApp
|
||||
[[ ! -w "${installPath}" ]] && {
|
||||
echo -e "${red}Error:${clr} SpotX-Bash does not have write permission in ${installOutput}.\nConfirm permissions or set custom install path to writable directory.\n" >&2
|
||||
exit 1
|
||||
@@ -605,22 +854,54 @@ macos_client_install() {
|
||||
"alHZyIWeChFT0F0UjRXQDJWeWNTW" \
|
||||
| rev | base64 --decode | base64 --decode)
|
||||
eval "${mc01}"; eval "${mc02}"
|
||||
tar -tf "$HOME/Downloads/${fileVar}" >/dev/null 2>&1 || {
|
||||
rm "$HOME/Downloads/${fileVar}" 2>/dev/null
|
||||
tar -tf "${downloadPath}" >/dev/null 2>&1 || {
|
||||
rm "${downloadPath}" 2>/dev/null
|
||||
echo -e "\n${red}Error:${clr} Downloaded client archive is corrupt or incomplete. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
macDownloadPath="${downloadPath}"
|
||||
printf "\xE2\x9C\x94\x20\x44\x6F\x77\x6E\x6C\x6F\x61\x64\x65\x64\x20\x61\x6E\x64\x20\x69\x6E\x73\x74\x61\x6C\x6C\x69\x6E\x67\x20\x53\x70\x6F\x74\x69\x66\x79\n"
|
||||
rm -rf "${appPath}" 2>/dev/null
|
||||
mkdir -p "${appPath}"
|
||||
tar -xpf "$HOME/Downloads/${fileVar}" -C "${appPath}" && unset notInstalled versionFailed || {
|
||||
rm "$HOME/Downloads/${fileVar}" 2>/dev/null
|
||||
macInstallTemp=$(mktemp -d "${installPath}/.spotx-install.XXXXXXXX") || {
|
||||
rm "${downloadPath}" 2>/dev/null
|
||||
echo -e "\n${red}Error:${clr} Client install failed. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
stagedApp="${macInstallTemp}/Spotify.app"
|
||||
mkdir -p "${stagedApp}" &&
|
||||
tar -xpf "${downloadPath}" -C "${stagedApp}" &&
|
||||
[[ -x "${stagedApp}/Contents/MacOS/Spotify" ]] &&
|
||||
[[ -f "${stagedApp}/Contents/Info.plist" ]] &&
|
||||
[[ -f "${stagedApp}/Contents/Resources/Apps/xpui.spa" ]] || {
|
||||
rm "${downloadPath}" 2>/dev/null
|
||||
echo -e "\n${red}Error:${clr} Client install failed. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ -e "${appPath}" ]] && {
|
||||
macInstallOld=$(mktemp -d "${installPath}/.spotx-previous.XXXXXXXX") || {
|
||||
rm "${downloadPath}" 2>/dev/null
|
||||
echo -e "\n${red}Error:${clr} Client install failed. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
rmdir "${macInstallOld}" && macInstallOldMoved='true' && mv "${appPath}" "${macInstallOld}" || {
|
||||
rm "${downloadPath}" 2>/dev/null
|
||||
echo -e "\n${red}Error:${clr} Client install failed. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
mv "${stagedApp}" "${appPath}" && macInstallCommitted='true' || {
|
||||
[[ -n "${macInstallOld:-}" && -d "${macInstallOld}" && ! -e "${appPath}" ]] && mv "${macInstallOld}" "${appPath}"
|
||||
rm "${downloadPath}" 2>/dev/null
|
||||
echo -e "\n${red}Error:${clr} Client install failed. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
rm -rf "${macInstallTemp}"
|
||||
[[ ! -d "${macInstallTemp}" ]] && unset macInstallTemp
|
||||
[[ -n "${macInstallOld:-}" ]] && rm -rf "${macInstallOld}"
|
||||
[[ -z "${macInstallOld:-}" || ! -d "${macInstallOld}" ]] && unset macInstallOld macInstallOldMoved macInstallCommitted
|
||||
printf "\xE2\x9C\x94\x20\x49\x6E\x73\x74\x61\x6C\x6C\x65\x64\x20\x69\x6E\x20'"${installOutput}"'\n"
|
||||
rm "$HOME/Downloads/${fileVar}"
|
||||
rm "${downloadPath}" && unset macDownloadPath
|
||||
clientVer=$(echo "${fileVar}" | perl -ne '/te-(.*)\..*\./ && print "$1"')
|
||||
unset notInstalled versionFailed
|
||||
}
|
||||
|
||||
run_install_check() {
|
||||
@@ -630,6 +911,20 @@ run_install_check() {
|
||||
}
|
||||
}
|
||||
|
||||
macos_codesign() {
|
||||
/usr/bin/xattr -cr "${appPath}" 2>/dev/null || {
|
||||
echo -e "\n${red}Error:${clr} Failed to clear Spotify security attributes. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "${skipCodesign}" ]] && return
|
||||
codesign -f --deep -s - "${appPath}" >/dev/null 2>&1 &&
|
||||
codesign --verify --deep --strict "${appPath}" >/dev/null 2>&1 || {
|
||||
echo -e "\n${red}Error:${clr} Failed to codesign Spotify. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
printf "\xE2\x9C\x94\x20\x43\x6F\x64\x65\x73\x69\x67\x6E\x65\x64\x20\x53\x70\x6F\x74\x69\x66\x79\n"
|
||||
}
|
||||
|
||||
run_cache_check() {
|
||||
[[ "${clearCache}" ]] && {
|
||||
[[ -n "${cachePath}" && -d "${cachePath}" ]] && {
|
||||
@@ -645,6 +940,7 @@ run_cache_check() {
|
||||
|
||||
final_setup_check() {
|
||||
[[ "${notInstalled}" ]] && { echo -e "${red}Error:${clr} Client not found\n" >&2; exit 1; }
|
||||
[[ ! -f "${appBinary}" || ! -s "${appBinary}" || ! -r "${appBinary}" || ! -x "${appBinary}" ]] && { echo -e "${red}Error:${clr} Client executable not found or invalid.\nReinstall client then try again.\n" >&2; exit 1; }
|
||||
[[ ! -f "${xpuiSpa}" ]] && { echo -e "${red}Error:${clr} Detected a modified client installation!\nReinstall client then try again.\n" >&2; exit 1; }
|
||||
[[ "${clientVer}" ]] && (($(ver "${clientVer}") < $(ver "1.1.59.710"))) && { echo -e "${red}Error:${clr} ${clientVer} not supported by SpotX-Bash\n" >&2; exit 1; }
|
||||
}
|
||||
@@ -653,42 +949,51 @@ perlVar() {
|
||||
local A=("$@")
|
||||
for cmd in "${A[@]}"; do
|
||||
IFS='&' read -r -a a <<< "${cmd}"
|
||||
local f="${a[4]}"
|
||||
local p="${!f}"
|
||||
[[ ! -f "${p}" && "${debug}" && "${devMode}" && "${t}" ]] && {
|
||||
echo -e "${red}Error:${clr} ${a[0]} invalid entry"
|
||||
continue
|
||||
}
|
||||
{ { [[ -z "${a[5]}" ]] || (( $(ver "${clientVer}") >= $(ver "${a[5]}") )); } &&
|
||||
{ [[ -z "${a[6]}" ]] || (( $(ver "${clientVer}") <= $(ver "${a[6]}") )); } &&
|
||||
{ [[ -z "${a[7]}" ]] || [[ "${a[7]}" =~ (^|\|)"${platformType}"($|\|) ]]; } &&
|
||||
{ [[ -z "${a[8]}" ]] || [[ "${a[8]}" =~ (^|\|)"${archVar}"($|\|) ]]; }
|
||||
} && perlvar "${xpuiSpa}"
|
||||
} || continue
|
||||
local f="${a[4]}"
|
||||
local p="${!f}"
|
||||
[[ ! -f "${p}" ]] && {
|
||||
[[ "${debug}" && "${devMode}" && "${t}" ]] && echo -e "${red}Error:${clr} ${a[0]} invalid entry"
|
||||
continue
|
||||
}
|
||||
perlvar "${xpuiSpa}"
|
||||
done
|
||||
}
|
||||
|
||||
xpui_detect() {
|
||||
[[ (-f "${appBak}" || -f "${xpuiBak}") && "${cleanAB}" ]] && {
|
||||
rm -f "${appBak}" 2>/dev/null; rm -f "${xpuiBak}" 2>/dev/null
|
||||
cp "${xpuiSpa}" "${xpuiBak}"; cp "${appBinary}" "${appBak}"
|
||||
backup_spotx || {
|
||||
echo -e "\n${red}Error:${clr} Failed to create client backup. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
printf "\xE2\x9C\x94\x20\x43\x72\x65\x61\x74\x65\x64\x20\x62\x61\x63\x6B\x75\x70\n"
|
||||
return
|
||||
}
|
||||
[[ (-f "${appBak}" || -f "${xpuiBak}") && "${forceSpotx}" ]] && {
|
||||
[[ -f "${appBak}" ]] && { rm -f "${appBinary}"; cp "${appBak}" "${appBinary}"; }
|
||||
[[ -f "${xpuiBak}" ]] && { rm -f "${xpuiSpa}"; cp "${xpuiBak}" "${xpuiSpa}"; }
|
||||
{ [[ ! -f "${appBak}" ]] || atomic_copy "${appBak}" "${appBinary}"; } &&
|
||||
{ [[ ! -f "${xpuiBak}" ]] || atomic_copy "${xpuiBak}" "${xpuiSpa}"; } || {
|
||||
echo -e "\n${red}Error:${clr} Failed to restore client backup. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
printf "\xE2\x9C\x94\x20\x44\x65\x74\x65\x63\x74\x65\x64\x20\x26\x20\x72\x65\x73\x74\x6F\x72\x65\x64\x20\x62\x61\x63\x6B\x75\x70\n"
|
||||
return
|
||||
}
|
||||
[[ (-f "${appBak}" || -f "${xpuiBak}") && -z "${forceSpotx+x}" ]] && {
|
||||
rm -rf "${xpuiDir}" 2>/dev/null
|
||||
xpuiSkip='true'
|
||||
printf "\xE2\x9C\x94\x20\x44\x65\x74\x65\x63\x74\x65\x64\x20\x62\x61\x63\x6B\x75\x70\n"
|
||||
echo -e "\n${yellow}Warning:${clr} SpotX-Bash has already been installed." >&2
|
||||
echo -e "Use the '-f' flag to force SpotX-Bash to run.\n" >&2
|
||||
return
|
||||
}
|
||||
cp "${xpuiSpa}" "${xpuiBak}"
|
||||
cp "${appBinary}" "${appBak}"
|
||||
backup_spotx || {
|
||||
echo -e "\n${red}Error:${clr} Failed to create client backup. Exiting...\n" >&2
|
||||
exit 1
|
||||
}
|
||||
printf "\xE2\x9C\x94\x20\x43\x72\x65\x61\x74\x65\x64\x20\x62\x61\x63\x6B\x75\x70\n"
|
||||
}
|
||||
|
||||
@@ -745,7 +1050,9 @@ snapshot_check() {
|
||||
}
|
||||
|
||||
xpui_open() {
|
||||
rm -rf "${xpuiDir}" 2>/dev/null
|
||||
mkdir -p "${xpuiDir}"
|
||||
xpuiTempCreated='true'
|
||||
unzip -qq "${xpuiSpa}" -d "${xpuiDir}" || {
|
||||
rm -rf "${xpuiDir}" 2>/dev/null
|
||||
echo -e "\n${red}Error:${clr} Failed to unpack xpui.spa. Reinstall client. Exiting...\n" >&2
|
||||
@@ -783,6 +1090,7 @@ xpui_open() {
|
||||
run_core_start() {
|
||||
final_setup_check
|
||||
check_write_permission "${appPath}" "${appBinary}" "${xpuiPath}" "${xpuiSpa}"
|
||||
[[ "${platformType}" == "Linux" && "${stagedInstall}" ]] && protected_stage_prepare
|
||||
xpui_detect
|
||||
[[ "${xpuiSkip}" ]] && { printf "\xE2\x9C\x94\x20\x46\x69\x6E\x69\x73\x68\x65\x64\n\n"; exit 0; }
|
||||
xpui_open
|
||||
@@ -839,20 +1147,40 @@ run_patches() {
|
||||
}
|
||||
|
||||
run_finish() {
|
||||
local spaTemp
|
||||
echo -e "\n//# SpotX was here" >> "${xpuiJs}"
|
||||
rm -f "${xpuiSpa}"
|
||||
(cd "${xpuiDir}" && zip -qq -r ../xpui.spa .) || {
|
||||
echo -e "\n${red}Error:${clr} Failed to repackage client." >&2
|
||||
echo -e "Spotify is now in a broken state. Please reinstall client.\n" >&2
|
||||
spaTempDir=$(mktemp -d "${xpuiPath}/.spotx-spa.XXXXXXXX") || {
|
||||
uninstall_spotx && \
|
||||
echo -e "\n${red}Error:${clr} Failed to create temporary SPA directory. Original client restored.\n" >&2 || \
|
||||
echo -e "\n${red}Error:${clr} Failed to create temporary SPA directory or restore client. Backups were preserved.\n" >&2
|
||||
exit 1
|
||||
}
|
||||
rm -rf "${xpuiDir}"
|
||||
[[ "${platformType}" == "macOS" ]] && {
|
||||
[[ "${skipCodesign}" ]] && /usr/bin/xattr -cr "${appPath}" 2>/dev/null || {
|
||||
/usr/bin/xattr -cr "${appPath}" 2>/dev/null
|
||||
codesign -f --deep -s - "${appPath}" 2>/dev/null
|
||||
printf "\xE2\x9C\x94\x20\x43\x6F\x64\x65\x73\x69\x67\x6E\x65\x64\x20\x53\x70\x6F\x74\x69\x66\x79\n"
|
||||
spaTemp="${spaTempDir}/xpui.spa"
|
||||
(cd "${xpuiDir}" && zip -qq -r "${spaTemp}" .) &&
|
||||
unzip -tqq "${spaTemp}" &&
|
||||
mv -f "${spaTemp}" "${xpuiSpa}" || {
|
||||
rm -rf "${spaTempDir}" 2>/dev/null
|
||||
uninstall_spotx && {
|
||||
echo -e "\n${red}Error:${clr} Failed to repackage client." >&2
|
||||
echo -e "Original client restored.\n" >&2
|
||||
} || {
|
||||
echo -e "\n${red}Error:${clr} Failed to repackage or restore client." >&2
|
||||
echo -e "Backups were preserved.\n" >&2
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
rm -rf "${spaTempDir}" 2>/dev/null
|
||||
[[ ! -d "${spaTempDir}" ]] && unset spaTempDir
|
||||
rm -rf "${xpuiDir}"
|
||||
unset xpuiTempCreated
|
||||
[[ "${platformType}" == "Linux" && "${stagedInstall}" ]] && protected_commit || {
|
||||
[[ "${platformType}" == "Linux" && "${stagedInstall}" ]] && {
|
||||
echo -e "\n${red}Error:${clr} Failed to install patched client. Original files restored.\n" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
[[ "${platformType}" == "macOS" ]] && {
|
||||
macos_codesign
|
||||
}
|
||||
}
|
||||
|
||||
@@ -884,10 +1212,10 @@ freeEx=(
|
||||
'hideDlQual&(\(.,..jsxs\)\(.{1,3}|(.\(\).|..)createElement\(.{1,4}),\{(filterMatchQuery|filter:.,title|(variant:"viola",semanticColor:"textSubdued"|..:"span",variant:.{3,6}mesto,color:.{3,6}),htmlFor:"desktop.settings.downloadQuality.+?).{1,6}get\("desktop.settings.downloadQuality.title.+?(children:.{1,2}\(.,.\).+?,|\(.,.\){3,4},|,.\)}},.\(.,.\)\),)&&&xpuiJs&1.1.59.710&1.2.29.605'
|
||||
'hideUpgradeButton&(return|.=.=>)"free"===(.+?)(return|.=.=>)"premium"===&$1"premium"===$2$3"free"===&g&xpuiJs&1.1.59.710&1.1.92.647'
|
||||
'hideUpgradeButton2&(?|(===")free(")|(")free("===))&$1premium$2&g&xpuiJs&1.2.55.235'
|
||||
'hptoEnabled&hptoEnabled:!\K0&1&s&xpuiJs'
|
||||
'hptoEnabled&hptoEnabled:!\K0&1&s&xpuiJs&&1.2.94.583'
|
||||
'hptoShown&isHptoShown:!\K0&1&gs&homeHptoJs&1.1.85.884&1.2.20.1218'
|
||||
'hptoShown2&(ADS_PREMIUM,isPremium:)\w(.*?ADS_HPTO_HIDDEN,isHptoHidden:)\w&$1true$2true&&xpuiJs&1.2.21.1104'
|
||||
'payloadS&\x3F\x70\x61\x79\x6C\x6F\x61\x64&\x00\x00\x00\x00\x00\x00\x00\x00&gs&appBinary&1.2.53.437'
|
||||
'payloadS&\x3F\x70\x61\x79\x6C\x6F\x61\x64&\x00\x00\x00\x00\x00\x00\x00\x00&gs&appBinary&1.2.53.437&1.2.93.667'
|
||||
'stateS1&\x69\x6E\x69\x74\x69\x61\x6C\x5F(?=\x48)&\x00\x00\x00\x00\x00\x00\x00\x00&s&appBinary&1.2.53.437&1.2.55.235&macOS'
|
||||
'stateS2&\x69\x6E\x69\x74\x69\x61\x6C\x5F(?=\x48)&\x00\x00\x00\x00\x00\x00\x00\x00&s&appBinary&1.2.53.437&1.2.84.476&Linux'
|
||||
'stateS3&[\x00\x0A\x1A]\K\x69\x6E\x69\x74\x69\x61\x6C\x5F(?=\x73\x74\x61\x74\x65\x00)&\x00\x00\x00\x00\x00\x00\x00\x00&s&appBinary&1.2.55.235&&macOS'
|
||||
@@ -907,8 +1235,8 @@ newUiEx=(
|
||||
'enableNavAltExperiment&Enable the new home structure and navigation",values:.,default:\K..DISABLED&true&&xpuiJs&1.1.94.864&1.1.96.785'
|
||||
'enableNavAltExperiment2&Enable the new home structure and navigation",values:.,default:.\K.DISABLED&.ENABLED_CENTER&&xpuiJs&1.1.97.956&1.2.2.582'
|
||||
'enablePanelSizeCoordination&Enable Panel Size Coordination between the left sidebar, the main view and the right sidebar",default:\K!.(?=})&true&s&xpuiJs&1.2.7.1264&1.2.50.335'
|
||||
'enableRightSidebar&Enable the view on the right sidebar",default:\K!1&true&s&xpuiJs&1.1.98.683&1.2.23.1125'
|
||||
'enableRightSidebarLyrics&Show lyrics in the right sidebar",default:\K!1&true&s&xpuiJs&1.2.0.1165'
|
||||
'enableRightSidebar&Enable the view on the right sidebar",default:\K!1&true&s&xpuiJs&1.1.98.683&1.2.93.667'
|
||||
'enableRightSidebarLyrics&Show lyrics in the right sidebar",default:\K!1&true&s&xpuiJs&1.2.0.1165&1.2.94.583'
|
||||
'enableYLXSidebar&Enable Your Library X view of the left sidebar",default:\K!1&true&s&xpuiJs&1.1.97.962&1.2.13.661'
|
||||
)
|
||||
podEx=(
|
||||
@@ -924,16 +1252,16 @@ lyricsBgEx=(
|
||||
)
|
||||
aoEx=(
|
||||
'aboutSpotX&((..createElement|children:\(.{1,7}\))\(.{1,7},\{source:).{1,7}get\("about.copyright",.\),paragraphClassName:("[^"]+"|.)(?=\}\))&$1"<h3>About SpotX / SpotX-Bash</h3><br><details><summary><svg xmlns='\''http://www.w3.org/2000/svg'\'' width='\''20'\'' height='\''20'\'' viewBox='\''0 0 24 24'\''><path d='\''M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z'\'' fill='\''#fff'\''/></svg> Github</summary><a href='\''https://github.com/SpotX-Official/SpotX'\''>SpotX \(Windows\)</a><br><a href='\''https://github.com/SpotX-Official/SpotX-Bash'\''>SpotX-Bash \(Linux/macOS\)</a><br><br/></details><details><summary><svg xmlns='\''http://www.w3.org/2000/svg'\'' width='\''20'\'' height='\''20'\'' viewBox='\''0 0 24 24'\''><path id='\''telegram-1'\'' d='\''M18.384,22.779c0.322,0.228 0.737,0.285 1.107,0.145c0.37,-0.141 0.642,-0.457 0.724,-0.84c0.869,-4.084 2.977,-14.421 3.768,-18.136c0.06,-0.28 -0.04,-0.571 -0.26,-0.758c-0.22,-0.187 -0.525,-0.241 -0.797,-0.14c-4.193,1.552 -17.106,6.397 -22.384,8.35c-0.335,0.124 -0.553,0.446 -0.542,0.799c0.012,0.354 0.25,0.661 0.593,0.764c2.367,0.708 5.474,1.693 5.474,1.693c0,0 1.452,4.385 2.209,6.615c0.095,0.28 0.314,0.5 0.603,0.576c0.288,0.075 0.596,-0.004 0.811,-0.207c1.216,-1.148 3.096,-2.923 3.096,-2.923c0,0 3.572,2.619 5.598,4.062Zm-11.01,-8.677l1.679,5.538l0.373,-3.507c0,0 6.487,-5.851 10.185,-9.186c0.108,-0.098 0.123,-0.262 0.033,-0.377c-0.089,-0.115 -0.253,-0.142 -0.376,-0.064c-4.286,2.737 -11.894,7.596 -11.894,7.596Z'\'' fill='\''#fff'\''/></svg> Telegram</summary><a href='\''https://t.me/spotify_windows_mod'\''>SpotX Channel</a><br><a href='\''https://t.me/SpotxCommunity'\''>SpotX Community</a><br><br/></details><details><summary><svg xmlns='\''http://www.w3.org/2000/svg'\'' width='\''20'\'' height='\''20'\'' viewBox='\''0 0 24 24'\''><path d='\''M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm1.25 17c0 .69-.559 1.25-1.25 1.25-.689 0-1.25-.56-1.25-1.25s.561-1.25 1.25-1.25c.691 0 1.25.56 1.25 1.25zm1.393-9.998c-.608-.616-1.515-.955-2.551-.955-2.18 0-3.59 1.55-3.59 3.95h2.011c0-1.486.829-2.013 1.538-2.013.634 0 1.307.421 1.364 1.226.062.847-.39 1.277-.962 1.821-1.412 1.343-1.438 1.993-1.432 3.468h2.005c-.013-.664.03-1.203.935-2.178.677-.73 1.519-1.638 1.536-3.022.011-.924-.284-1.719-.854-2.297z'\'' fill='\''#fff'\''/></svg> FAQ</summary><a href='\''https://te.legra.ph/SpotX-FAQ-09-19'\''>Windows</a><br><a href='\''https://github.com/SpotX-Official/SpotX-Bash/wiki/SpotX%E2%80%90Bash-FAQ'\''>Linux/macOS</a></details><br><h4>DISCLAIMER</h4>SpotX is a modified version of the official Spotify\x26reg; client, provided \x26quot;as is\x26quot; for the purpose of evaluation at user'\''s own risk. Source code for SpotX is available separately and free of charge under open source software license agreements. SpotX is not affiliated with Spotify\x26reg;, Spotify AB or Spotify Group.<br><br>Spotify\x26reg; is a registered trademark of Spotify Group."&&xpuiDesktopModalsJs&1.1.79.763'
|
||||
'allowSwitchingBetweenHomeAdsAndHpto&opposed to only showing the legacy HPTO format.",default:\K!.(?=})&false&s&xpuiJs&1.2.34.783'
|
||||
'betamaxFilterNegativeDuration&for duration that is negative",default:\K!.(?=})&false&s&xpuiJs'
|
||||
'allowSwitchingBetweenHomeAdsAndHpto&opposed to only showing the legacy HPTO format.",default:\K!.(?=})&false&s&xpuiJs&1.2.34.783&1.2.94.583'
|
||||
'betamaxFilterNegativeDuration&for duration that is negative",default:\K!.(?=})&false&s&xpuiJs&1.1.59.001&1.2.93.667'
|
||||
'bGabo&\x00\K\x67(?=\x61\x62\x6F\x2D\x72\x65\x63\x65\x69\x76\x65\x72\x2D\x73\x65\x72\x76\x69\x63\x65\x2F\x70)&\x00&g&appBinary&1.1.84.716'
|
||||
'bLogic&\x00\K\x61(?=\x64\x2D\x6C\x6F\x67\x69\x63\x2F\x73)&\x00&&appBinary&1.1.70.610&1.2.28.581'
|
||||
'bSlot&\x00\K\x73(?=\x6C\x6F\x74\x73\x00)&\x00&g&appBinary&1.1.70.610'
|
||||
'disablePremiumOnlyModal&Disable the Premium Only Modal",default:\K!.(?=})&true&s&xpuiJs&1.2.39.578'
|
||||
'embeddedAdImpressionDoesNotIgnoreVisilibility&If enabled, we do consider percent visibility when logging the display ad impression.{0,49}",default:\K!.(?=})&false&s&xpuiJs&1.2.78.397&1.2.84.477'
|
||||
'enable_ad_feedback_home_free&Kill switch for ad feedback on home ad format for free users",default:\K!.(?=})&false&s&xpuiJs&1.2.93.650'
|
||||
'enable_ad_feedback_home_premium&Kill switch for ad feedback on home ad format for premium users",default:\K!.(?=})&false&s&xpuiJs&1.2.93.650'
|
||||
'enable_ad_feedback_milestone_3&Enable ad feedback milestone 3 feature",default:\K!.(?=})&false&s&xpuiJs&1.2.93.650'
|
||||
'enable_ad_feedback_home_free&Kill switch for ad feedback on home ad format for free users",default:\K!.(?=})&false&s&xpuiJs&1.2.93.656'
|
||||
'enable_ad_feedback_home_premium&Kill switch for ad feedback on home ad format for premium users",default:\K!.(?=})&false&s&xpuiJs&1.2.93.656'
|
||||
'enable_ad_feedback_milestone_3&Enable ad feedback milestone 3 feature",default:\K!.(?=})&false&s&xpuiJs&1.2.93.656'
|
||||
'enableAgeAssuranceComments&Enables the age assurance gating for comments feature",default:\K!.(?=})&false&s&xpuiJs&1.2.78.397'
|
||||
'enableAgeAssuranceFriendActivity&Enables the age assurance gating for friend activity feed",default:\K!.(?=})&false&s&xpuiJs&1.2.78.397'
|
||||
'enableAgeAssuranceProfileMenu&Enables the age assurance entry point in the profile menu .{0,43}",default:\K!.(?=})&false&s&xpuiJs&1.2.78.397'
|
||||
@@ -941,12 +1269,13 @@ aoEx=(
|
||||
'enableCanvasAds&Enable Canvas for ads",default:\K!.(?=})&false&s&xpuiJs&1.2.52.442&1.2.92.148'
|
||||
'enableConnectedStateObserver&observer that logs errors related to connected state and ad info",default:\K!.(?=})&false&s&xpuiJs&1.2.53.437'
|
||||
'enableCulturalMoments&Cultural Moment pagess",default:\K!.(?=})&false&s&xpuiJs&1.2.7.1264&1.2.50.335'
|
||||
'enableDesktopMusicLeavebehinds&Enable music leavebehinds on eligible playlists for desktop",default:\K!.(?=})&false&s&xpuiJs&1.2.10.751'
|
||||
'enableDesktopMusicLeavebehinds&Enable music leavebehinds on eligible playlists for desktop",default:\K!.(?=})&false&s&xpuiJs&1.2.10.751&1.2.93.667'
|
||||
'enableDsaAds&Enable showing DSA .Digital Services Act. context menu and modal for ads",default:\K!.(?=})&false&s&xpuiJs&1.2.20.1210&1.2.52.442'
|
||||
'enableDSASetting&Enable DSA .Digital Service Act. features for desktop and web",default:\K!.(?=})&false&s&xpuiJs&1.2.20.1210'
|
||||
'enableEnhancedAdsClientDeconfliction&Enable refactored version of ads orchestrator middleware",default:\K!.(?=})&false&s&xpuiJs&1.2.57.460&1.2.61.443'
|
||||
'enableEmbeddedAdsCarousel&embedded ads carousel for the NPV",default:\K!.(?=})&false&s&xpuiJs&1.2.73.451'
|
||||
'enableEmbeddedAdsFetchingOverCanvas&embedded ads fetching when canvas track is playing. Defaults to true since this is currently existing behavior",default:\K!.(?=})&false&s&xpuiJs&1.2.72.435&1.2.77.358'
|
||||
'enableEmbeddedAdHtmlDisplay&Enable HTML display ads in the embedded NPV",default:\K!.(?=})&false&s&xpuiJs&1.2.94.0'
|
||||
'enableEmbeddedAdVisibilityLogging&When enabled, enhanced visibility logs will be sent for embedded ads",default:\K!.(?=})&false&s&xpuiJs&1.2.64.407&1.2.77.358'
|
||||
'enableEmbeddedNpvAds&Enable embedded display ads on NPV",default:\K!.(?=})&false&s&xpuiJs&1.2.57.460&1.2.77.358'
|
||||
'enableEsperantoMigration&Enable esperanto Migration for (HPTO\s)?Ad Formats?",default:\K!.(?=})&false&s&xpuiJs&1.2.6.861&1.2.50.335'
|
||||
@@ -954,11 +1283,13 @@ aoEx=(
|
||||
'enableFraudLoadSignals&Enable user fraud signals emitted on page load",default:\K!.(?=})&false&s&xpuiJs&1.2.22.975&1.2.62.580'
|
||||
'enableHomeAds&Enable Fist Impression Takeover ads on Home Page",default:\K!.(?=})&false&s&xpuiJs&1.2.31.1205&1.2.84.477'
|
||||
'enableHomeAdStaticBanner&Enables temporary home banner, static version",default:\K!.(?=})&false&s&xpuiJs&1.2.25.1009&1.2.53.440'
|
||||
'enableHpto&Hpto announcements on Home",default:\K!.(?=})&false&s&xpuiJs&1.2.65.255'
|
||||
'enableHpto&Hpto announcements on Home",default:\K!.(?=})&false&s&xpuiJs&1.2.65.255&1.2.94.583'
|
||||
'enableHptoLayoutRewrite&Enable the new HomeAdCard flexbox layout rewrite",default:\K!.(?=})&false&s&xpuiJs&1.2.92.0'
|
||||
'enableHptoLocationRefactor&Enable new permanent location for HPTO iframe to HptoHtml.js",default:\K!.(?=})&false&s&xpuiJs&1.2.1.958&1.2.20.1218'
|
||||
'enableImageOptimizationSentrySpanMeasurement&Sentry image resource span attributes for image optimization rollout measurement",default:\K!.(?=})&false&s&xpuiJs&1.2.94.0'
|
||||
'enableInAppMessaging&Enables quicksilver in-app messaging modal",default:\K!.(?=})&false&s&xpuiJs&1.1.70.610'
|
||||
'enableInteractionLogger&Enables the old interaction logger",default:\K!.(?=})&false&s&xpuiJs&1.2.41.434&1.2.64.408'
|
||||
'enableLeaderboardEmptySlotHandling&Config for clearing the current leaderboard ad and hiding the leaderboard container when the ad slot returns an empty response",default:\K!.(?=})&true&s&xpuiJs&1.2.95.200'
|
||||
'enableLeavebehindsMockData&Use the mock endpoint to fetch Leavebehinds from AP4P",default:\K!.(?=})&false&s&xpuiJs&1.2.30.1135'
|
||||
'enableNewAdsNpv&Enable showing new ads NPV",default:\K!.(?=})&false&s&xpuiJs&1.2.18.997&1.2.50.335'
|
||||
'enableNewAdsNpvCanvasAds&Enable Canvas ads for new ads NPV",default:\K!.(?=})&false&s&xpuiJs&1.2.28.581&1.2.51.345'
|
||||
@@ -971,8 +1302,10 @@ aoEx=(
|
||||
'enablePodcastSponsoredContent&Enable sponsored content information for podcasts",default:\K!.(?=})&false&s&xpuiJs&1.2.30.1135&1.2.50.335'
|
||||
'enablePromotions&Enables promotions on home",default:\K!.(?=})&false&s&xpuiJs&1.2.38.720&1.2.45.454'
|
||||
'enableSaxLeaderboardAds&Enable SAX Leaderboard Ad Format",default:\K!.(?=})&false&s&xpuiJs&1.2.62.575&1.2.82.428'
|
||||
'enableShowLeavebehindConsolidation&Enable show leavebehinds consolidated experience",default:\K!.(?=})&false&s&xpuiJs&1.2.23.1114'
|
||||
'enableSentryReactRouterV6Routing&Sentry React Router v6 route instrumentation for Web Player SPA transactions",default:\K!.(?=})&false&s&xpuiJs&1.2.94.0'
|
||||
'enableShowLeavebehindConsolidation&Enable show leavebehinds consolidated experience",default:\K!.(?=})&false&s&xpuiJs&1.2.23.1114&1.2.93.667'
|
||||
'enableSponsoredPlaylistEsperantoMigration&Enable esperanto Migration for Sponsored Playlist Ad Formats",default:\K!.(?=})&false&s&xpuiJs&1.2.32.985&1.2.50.335'
|
||||
'enableSponsoredPlaylistHorizontalVideo&horizontal video layout for sponsored playlist headers on desktop",default:\K!.(?=})&false&s&xpuiJs&1.2.95.200'
|
||||
'enableSurveyAds&Enable Spotify Brand Lift .SBL. Surveys in the NPV",default:\K!.(?=})&false&s&xpuiJs&1.2.43.420&1.2.63.394'
|
||||
'enableUnderAgeBlockingModal&Enables the underage blocking modal for accounts in blocked/pending disabled state",default:\K!.(?=})&false&s&xpuiJs&1.2.78.397'
|
||||
'enableUserFraudCanvas&Enable user fraud Canvas Fingerprinting",default:\K!.(?=})&false&s&xpuiJs&1.2.13.656&1.2.63.394'
|
||||
@@ -1017,7 +1350,7 @@ expEx=(
|
||||
'enableAttackOnTitanEasterEgg&Titan Easter egg turning progress bar red when playing official soundtrack",default:\K!.(?=})&true&s&xpuiJs&1.2.6.861&1.2.50.335'
|
||||
'enableAudiobookPrerelease&audiobook prerelease pages",default:\K!1&true&s&xpuiJs&1.2.33.1039&1.2.47.366'
|
||||
'enableAudiobooks&Audiobooks feature on ClientX",default:\K!1&true&s&xpuiJs&1.1.74.631&1.2.46.462'
|
||||
'enableAutoSeekToVideoBufferedStartPosition&avoid initial seek if the initial position is not buffered",default:\K!1&true&s&xpuiJs&1.2.31.1205'
|
||||
'enableAutoSeekToVideoBufferedStartPosition&avoid initial seek if the initial position is not buffered",default:\K!1&true&s&xpuiJs&1.2.31.1205&1.2.93.667'
|
||||
'enableBackendSearchHistory&Enable backend search history",default:\K!1&true&s&xpuiJs&1.2.60.564&1.2.85.519'
|
||||
'enableBanArtistAction&context menu action to ban/unban artists",default:\K!1&true&s&xpuiJs&1.2.28.581&1.2.42.290'
|
||||
'enableBetamaxSdkSubtitlesDesktopX&rendering subtitles on the betamax SDK on DesktopX",default:\K!.(?=})&true&s&xpuiJs&1.1.70.610'
|
||||
@@ -1049,6 +1382,7 @@ expEx=(
|
||||
'enableEightShortcuts&Increase max number of shortcuts on home to 8",default:\K!1&true&s&xpuiJs&1.2.26.1180&1.2.45.454'
|
||||
'enableEncoreCards&all cards throughout app to be Encore Cards",default:\K!1&true&s&xpuiJs&1.2.21.1104&1.2.33.1042'
|
||||
'enableEncorePlaybackButtons&Use Encore components in playback control components",default:\K!1&true&s&xpuiJs&1.2.20.1210&1.2.43.420'
|
||||
'enableEntityHeaderNew&Enable the new entity header design",default:\K!.(?=})&true&s&xpuiJs&1.2.95.200'
|
||||
'enableEqualizer&audio equalizer for Desktop and Web Player",default:\K!1&true&s&xpuiJs&1.1.88.595'
|
||||
'enableExcludeTrackFromTasteProfile&option to exclude track from taste profile via context menu",default:\K!1&true&s&xpuiJs&1.2.73.451'
|
||||
'enableExtraTracklistColumns&extra tracklist columns",default:\K!1&true&s&xpuiJs&1.2.44.405&1.2.71.421'
|
||||
@@ -1061,7 +1395,7 @@ expEx=(
|
||||
'enableHomePin&pinning of home shelves",default:\K!1&true&s&xpuiJs&1.2.45.451'
|
||||
'enableIgnoreInRecommendations&Ignore In Recommendations for desktop and web",default:\K!.(?=})&true&s&xpuiJs&1.1.87.612&1.2.50.335'
|
||||
'enableInlineCuration&new inline playlist curation tools",default:\K!1&true&s&xpuiJs&1.1.70.610&1.2.25.1011'
|
||||
'enableLikedSongsAsPlaylist&Liked Songs on list platform with playlist uri",default:\K!1&true&s&xpuiJs&1.2.75.499'
|
||||
'enableLikedSongsAsPlaylist&Liked Songs on list platform with playlist uri",default:\K!1&true&s&xpuiJs&1.2.75.499&1.2.93.667'
|
||||
'enableLikedSongsFilterTags&Show filter tags on the Liked Songs entity view",default:\K!1&true&s&xpuiJs&1.2.32.985'
|
||||
#'enableLikedSongsListPlatform&Liked Songs on list platform",default:\K!1&true&s&xpuiJs&1.2.41.434'
|
||||
'enableListPrivateByDefaultSetting&List Private By Default setting in Desktop Social Settings",default:\K!1&true&s&xpuiJs&1.2.78.397'
|
||||
@@ -1077,7 +1411,7 @@ expEx=(
|
||||
'enableMoreLikeThisPlaylist&More Like This playlist for playlists the user cannot edit",default:\K!1&true&s&xpuiJs&1.2.32.985&1.2.73.474'
|
||||
'enableNearbyJams&support for Nearby Jams feature in the Device Picker",default:\K!1&true&s&xpuiJs&1.2.52.442'
|
||||
'enableNewArtistEventsPage&Display the new Artist events page",default:\K!1&true&s&xpuiJs&1.2.18.997&1.2.32.997'
|
||||
'enableNewConcertFeed&Enables new concert feed experience",default:\K!1&true&s&xpuiJs&1.2.37.701&1.2.42.290&1.2.50.335'
|
||||
'enableNewConcertFeed&Enables new concert feed experience",default:\K!1&true&s&xpuiJs&1.2.37.701&1.2.50.335'
|
||||
'enableNewConcertLocationExperience&new concert location experience modal selector.",default:\K!1&true&s&xpuiJs&1.2.34.783&1.2.42.290'
|
||||
'enableNewEntityHeaders&New Entity Headers",default:\K!1&true&s&xpuiJs&1.2.15.826&1.2.28.0'
|
||||
'enableNewEpisodes&new episodes view",default:\K!1&true&s&xpuiJs&1.1.84.716&1.2.62.580'
|
||||
@@ -1100,13 +1434,14 @@ expEx=(
|
||||
'enablePlaybackBarAnimation&animation of the playback bar",default:\K!1&true&s&xpuiJs&1.2.34.783&1.2.82.428'
|
||||
'enablePlaylistCreationFlow&new playlist creation flow in Web Player and DesktopX",default:\K!1&true&s&xpuiJs&1.1.70.610&1.1.93.896'
|
||||
'enablePlaylistPermissionsProd&Playlist Permissions flows for Prod",default:\K!.(?=})&true&s&xpuiJs&1.1.75.572&1.2.50.335'
|
||||
'enablePlaylistReleaseDateColumn&Enables the release date column in playlist tracklists",default:\K!.(?=})&true&s&xpuiJs&1.2.95.200'
|
||||
'enablePodcastChaptersInNpv&showing podcast chapters in NPV",default:\K!.(?=})&true&s&xpuiJs&1.2.22.975&1.2.50.335'
|
||||
'enablePodcastChapterPage&the podcast chapter entity page",default:\K!.(?=})&true&s&xpuiJs&1.2.85.504'
|
||||
'enablePodcastChapterPage&the podcast chapter entity page",default:\K!.(?=})&true&s&xpuiJs&1.2.85.504&1.2.93.667'
|
||||
'enablePodcastDescriptionAutomaticLinkification&Linkifies anything looking like a url in a podcast description.",default:\K!1&true&s&xpuiJs&1.2.19.937'
|
||||
'enablePremiumUserForMiniPlayer&premium user flag for mini player",default:\K!1&true&s&xpuiJs&1.2.32.985'
|
||||
'enablePrereleaseRadar&Show a curated list of upcoming albums to a user",default:\K!1&true&s&xpuiJs&1.2.39.578&1.2.45.454'
|
||||
'enableProfileVisibilityControls&profile visibility controls in the settings . profile page",default:\K!1&true&s&xpuiJs&1.2.74.462&1.2.85.519'
|
||||
'enableProgressBarEpisodeChapters&pisode chapters markers in the progress bar",default:\K!1&true&s&xpuiJs&1.2.68.525&1.2.74&1.2.74.477'
|
||||
'enableProgressBarEpisodeChapters&pisode chapters markers in the progress bar",default:\K!1&true&s&xpuiJs&1.2.68.525&1.2.74.477'
|
||||
'enableProgressBarRefactorWithChapters&refactored ProgressBar implementation with chapter support",default:\K!1&true&s&xpuiJs&1.2.74.462&1.2.82.428'
|
||||
'enableQueueOnRightPanel&Enable Queue on the right panel.",default:\K!.(?=})&true&s&xpuiJs&1.2.26.1180&1.2.61.443'
|
||||
'enableQueueOnRightPanelAnimations&animations for Queue on the right panel.",default:\K!.(?=})&true&s&xpuiJs&1.2.32.985&1.2.50.335'
|
||||
@@ -1126,14 +1461,14 @@ expEx=(
|
||||
'enableSearchV3&new Search experience",default:\K!1&true&s&xpuiJs&1.1.87.612&1.2.34.783'
|
||||
'enableScrollDrivenAnimations&croll driven animations for cards and shelved",default:\K!1&true&s&xpuiJs&1.2.39.578'
|
||||
'enableShareActionBarButton&Shows a share button in entity page action bars that opens the share dialog",default:\K!.(?=})&true&s&xpuiJs&1.2.85.504'
|
||||
'enableShareDialog&the share dialog modal instead of the share submenu",default:\K!.(?=})&true&s&xpuiJs&1.2.85.504'
|
||||
'enableShareDialog&the share dialog modal instead of the share submenu",default:\K!.(?=})&true&s&xpuiJs&1.2.85.504&1.2.93.667'
|
||||
'enableSharingButtonOnMiniPlayer&sharing button on MiniPlayer .this also moves the ... icon close to the title.",default:\K!1&true&s&xpuiJs&1.2.39.578&1.2.43.420'
|
||||
'enableShortLinks&short links for sharing",default:\K!1&true&s&xpuiJs&1.2.34.783'
|
||||
'enableShowFollowsSetting&control if followers and following lists are shown on profile",default:\K!.(?=})&true&s&xpuiJs&1.2.1.958&1.2.50.335'
|
||||
'enableShowRating&new UI for rating books and podcasts",default:\K!1&true&s&xpuiJs&1.2.32.985&1.2.62.580'
|
||||
'enableShuffleSettings&shuffle settings section in advanced settings",default:\K!1&true&s&xpuiJs&1.2.75.499'
|
||||
'enableSidebarAnimations&animations on the left and right on the sidebars and makes the right sidebar collapsible",default:\K!1&true&s&xpuiJs&1.2.34.783&1.2.37.701'
|
||||
'enableSilenceTrimmer&silence trimming in podcasts",default:\K!1&true&s&xpuiJs&1.1.99.871'
|
||||
'enableSilenceTrimmer&silence trimming in podcasts",default:\K!1&true&s&xpuiJs&1.1.99.871&1.2.93.667'
|
||||
'enableSkipNextTooltip&tooltip that shows a preview of the next item in queue.",values:.{1,3},default:.{1,4}\KDisabled&Expanded&s&xpuiJs&1.2.65.255&1.2.85.519'
|
||||
'enableSocialConnectOnDesktop&the Social Connect API that powers group listening sessions for Desktop",values:.{1,3},default:.{1,4}\KDISABLED&ENABLED&s&xpuiJs&1.2.21.1104&1.2.45.454'
|
||||
'enableSmallerLineHeight&line height 1.5 on the .body ..",default:\K!1&true&s&xpuiJs&1.2.18.997&1.2.23.1125'
|
||||
@@ -1146,6 +1481,7 @@ expEx=(
|
||||
'enableTiltable3DArtwork&tiltable 3D parallax effect on artwork .Cinema Mode and Cover Art Modal.",default:\K!1&true&s&xpuiJs&1.2.76.256'
|
||||
'enableTogglePlaylistColumns&ability to toggle playlist column visibility",default:\K!1&true&s&xpuiJs&1.2.17.832&1.2.66.447'
|
||||
'enableTracklistColumnsSorting&column reordering functionality in tracklists",default:\K!1&true&s&xpuiJs&1.2.69.448'
|
||||
'enableTranscriptTextSelection&text selection and copy in episode transcripts on desktop",default:\K!.(?=})&true&s&xpuiJs&1.2.95.200'
|
||||
'enableUserCommentsForEpisodes&user comments for podcast episodes",default:\K!1&true&s&xpuiJs&1.2.49.439'
|
||||
'enableUserCreatedArtwork&user created artworks for playlists",default:\K!1&true&s&xpuiJs&1.2.34.783&1.2.40.599'
|
||||
'enableUserProfileEdit&editing of user.s own profile in Web Player and DesktopX",default:\K!1&true&s&xpuiJs&1.1.87.612&1.2.25.1011'
|
||||
@@ -1164,7 +1500,7 @@ expEx=(
|
||||
'enableYLXPrereleaseAlbums&album pre-releases in YLX",default:\K!1&true&s&xpuiJs&1.2.32.985'
|
||||
'enableYLXPrereleaseAudiobooks&audiobook pre-releases in YLX",default:\K!1&true&s&xpuiJs&1.2.32.985&1.2.47.366'
|
||||
'enableYLXPrereleases&album pre-releases in YLX",default:\K!1&true&s&xpuiJs&1.2.31.1205&1.2.31.1205'
|
||||
'enableYlxReverseSorting&Enable reverse sort direction in Your Library",default:\K!1&true&s&xpuiJs&1.2.60.564'
|
||||
'enableYlxReverseSorting&Enable reverse sort direction in Your Library",default:\K!1&true&s&xpuiJs&1.2.60.564&1.2.94.583'
|
||||
'enableYLXTypeaheadSearch&jump to the first matching item",default:\K!1&true&s&xpuiJs&1.2.13.656'
|
||||
'enableZoomSettingsUIDesktop&zoom settings from the settings page on Desktop",default:\K!1&true&s&xpuiJs&1.2.17.832&1.2.53.437'
|
||||
'isVideoQualityEnabled&video quality settings and the in-player quality picker",default:\K!1&true&s&xpuiJs&1.2.84.194'
|
||||
@@ -1181,6 +1517,9 @@ premiumExpEx=(
|
||||
'enableYourSoundCapsuleModal&showing a modal on desktop to users who have clicked on a Your Sound Capsule share link",default:\K!1&true&s&xpuiJs&1.2.38.720'
|
||||
)
|
||||
|
||||
trap cleanup_temp_dirs EXIT
|
||||
trap 'exit 130' HUP INT TERM
|
||||
|
||||
run_prepare
|
||||
run_uninstall_check
|
||||
run_interactive_check
|
||||
|
||||
Reference in New Issue
Block a user