Compare commits

...
9 Commits
Author SHA1 Message Date
jetfir3andGitHub a68b29fd9d Support 1.2.95.453.g0eeebbed 2026-08-05 20:10:27 -04:00
jetfir3andGitHub 307db7769d Fix typo regression 2026-08-04 13:30:02 -04:00
jetfir3andGitHub 73b74cbd65 Support 1.2.95.452.g5c9bdf32 2026-08-04 13:23:23 -04:00
jetfir3 03207b6117 Add binary validation 2026-07-29 15:38:54 -04:00
jetfir3 b6df3cdcc5 Fix typos 2026-07-29 15:20:56 -04:00
jetfir3 876e4dc0cd Improve macOS installation and codesigning 2026-07-29 14:53:59 -04:00
jetfir3 f4b9f7d20f Fix Linux rollback handling 2026-07-29 14:19:27 -04:00
jetfir3 852acb2acb Improve SPA safety 2026-07-29 13:57:25 -04:00
jetfir3 7f190c6660 Harden Linux client patching 2026-07-29 12:40:29 -04:00
2 changed files with 387 additions and 92 deletions
+1 -1
View File
@@ -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.94.583.g60394bd5
<strong>Latest supported version:</strong> 1.2.95.453.g0eeebbed
</p>
</center>
+386 -91
View File
@@ -1,12 +1,12 @@
#!/usr/bin/env bash
buildVer="1.2.94.583.g60394bd5"
rollbackVer="1.2.93.667.g7b5cc0ce"
buildVer="1.2.95.453.g0eeebbed"
rollbackVer="1.2.94.583.g60394bd5"
latestB_X="4881"
latestB_A="4872"
rollbackB_X="2388"
rollbackB_A="2385"
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" \
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODY4MDk0ODgsIm5iZiI6" \
"MTc4NDIxNzQ4OCwicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gteDg2XzY0L3Nw" \
"b3RpZnktYXV0b3VwZGF0ZS0xLjIuOTQuNTgzLmc2MDM5NGJkNS00ODgxLnRieiJ9" \
".kxtysiyM75Sk0uZ2wZjnWnDrf40Hhjw8qUkW_Ott7-aa-Z3BEmRO7px-QDf25Of" \
"fox48sICQGMnbTdw-ysa0G3XRqeHqfwnvH_LI4Y2kB2hG-Lv3DHAnVQMY7W9vZR1" \
"syDeM94UOe6M6SGNDMquAh0LbbPg-TwNhiXWgGBdCK5BxPTirN0ABK3XYKTfkreK" \
"UYVC7MsODGWRRypMmJuO36dUvY8UCmptQ0r_cJVuXTZWmS2FiB1mouKYMXxRBh4v" \
"eJvXV8v-gCKePxFTV5wIBBVaioNjlNSUnphKlwoibRswnWVE1tmxDjVsYcl3Wpn-" \
"GsP5spgl8Q6hDnWljPFEz1w")
"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" \
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODY4MDk0ODgsIm5iZiI6" \
"MTc4NDIxNzQ4OCwicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gtYXJtNjQvc3Bv" \
"dGlmeS1hdXRvdXBkYXRlLTEuMi45NC41ODMuZzYwMzk0YmQ1LTQ4NzIudGJ6In0." \
"dgikJbvCtP540BHTmB-PCnZR7bagMsocETWW64nGYaJiz_dOxNqszB2MkBDPaboQ" \
"LJpPtyocB92z-gVpbZeQNTkaK0jYBp4yIzF5RBk6AUMQNmnKkmTaggELxtit0euc" \
"bT0_ieDyqTl3tskBo68rrw1ifNnwZVRPsy07dL_RYQrY3meXuQqf8r0KxhqTysgj" \
"1pN7OQwO_sAS8WpNpZZ1vIeTMVf9ZQGmqh19Bx6dbCcDgrzTPgG7BrRHtcOa6tXG" \
"WbfqVVyKVYSwDNVazIRR4NAjK4n0mfy_7t5r3eVfe04JTITNo0yX_kbybLMReRCh" \
"lACVioAhwrvZWfirYjK8jw")
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODg1NjYyOTMsIm5iZiI6" \
"MTc4NTk3NDI5MywicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gtYXJtNjQvc3Bv" \
"dGlmeS1hdXRvdXBkYXRlLTEuMi45NS40NTMuZzBlZWViYmVkLTM2NzIudGJ6In0." \
"fRBIOAjThS9W-m_0EEw7oxd2tpIViU26gBsVdScwrnaT1XtJxhLp28bP26kd6pF4" \
"DsFxz5lmb5UogRCMhjdLMVvwUrJlMYR_IDKOkWxiOzFXG-CBEBCbylkm-hT5JG85" \
"fy7u5ZtXq-fUiyl5VEchP1vYH8JMZiHHC4i2V1SM9O1ExOeqxNccu_4m29Hh4gFK" \
"5qPIMnlD8egTqy_wstsZa7M5sHkmdjsJ4-SXxGy87ONtnbO9UxPOEQYemNOf9Zqq" \
"iGRs10px09Mb4Mk-ZnBiLl9jhLrTsVHSnBHr4Alet1D7kcMmxRnFxkBAeUHCaTw3" \
"xUtqn8T49yF538bgsKK5kw")
rollbackA_X=$(printf "%s" \
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODY4MDU4ODQsIm5iZiI6" \
"MTc4NDIxMzg4NCwicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gteDg2XzY0L3Nw" \
"b3RpZnktYXV0b3VwZGF0ZS0xLjIuOTMuNjY3Lmc3YjVjYzBjZS0yMzg4LnRieiJ9" \
".TImdHMAe0XIJldwwf_pHsz4K-C-dcaYg2cJqrXZovmmB8WRxRCPvYwSJnduwGi8" \
"Y-aiEMTqnLul0DF0AquzXFRpu8u1aX9lET64wv9PYdUZ7ijBCskNWIZKL8uyf5WN" \
"eJeA2qNMXxdwQtwX2bYwCCcwpq13Fi6iIjSHo5tnexz1vgmujHd-4PW4nfqE6SPV" \
"I2gLbvTAmtlgpvFkO8dSPbDEzMb-mGSD2H_qRdfmAbD6bdhyljwHG3ZJeMwGsdc3" \
"Vffh-GSjMG5viu3nbEXo6KXj2yzAUg1GOhpNefTPTTXkaGFUqeqi7KtNMQVLkyz7" \
"n49X1xTa1tK-50c2Gyv5HPw")
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODg0Mzg0ODksIm5iZiI6" \
"MTc4NTg0NjQ4OSwicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gteDg2XzY0L3Nw" \
"b3RpZnktYXV0b3VwZGF0ZS0xLjIuOTQuNTgzLmc2MDM5NGJkNS00ODgxLnRieiJ9" \
".VjPA-sh1Tjg72unZXmb-Mn-NRL64czXwOMkQzy0IN6gsRaYrwqsIXAMPldiJHgo" \
"SSp6HtSBkepcI9bagoM6US0rT9FEDkrjUn03kp4wl0tHxyahfDVFCbAMskc5VCVf" \
"yb1LK_GhNX2bvzTz9lLq1tSlNXclQNQB-NH21IfnvUViNF6-eXoTkvPSMNsLNg-n" \
"t8kOePUNm1ypOZ4SgJxxUyz4QaIftyvIH-7PEeyrbgIyKYukEB8RbtjjPlH3DfUi" \
"lmYMaJKOuDz2dww_r-4nPMIdCABdwyPj7RzZ1Yx5wMGVpAVpbNYg5nqRrTOs2PxO" \
"lmmjgMDdLBfx6PKz1eww6Xw")
rollbackA_A=$(printf "%s" \
"eyJpc3MiOiJzY2RuLXVybC1zaWduZXIiLCJleHAiOjE3ODY4MDU4ODMsIm5iZiI6" \
"MTc4NDIxMzg4MywicGF0aCI6Ii91cGdyYWRlL2NsaWVudC9vc3gtYXJtNjQvc3Bv" \
"dGlmeS1hdXRvdXBkYXRlLTEuMi45My42NjcuZzdiNWNjMGNlLTIzODUudGJ6In0." \
"gzHERnomKyNB7L2bZjz3wOt_W6NoDPk-bHvNQRsWecdinnv1mXchC92ICK8dBJeh" \
"W2yfoFb-GaANn1Ky48Eie5FeO_rE0C8Y1u6FJnPbz9qgvFEx_2ebVF3c6cPF5hag" \
"i3b3pCjsvWbYVsi4tPIJaJ3S7WX_mmg-KiVeseUFDvEOvdT_Rf_H0YM2hNwGdjhw" \
"74y0O2H_a-4jgG2qSSMBByI5ldfdypm_S-GiH7uIajNtHEmxt-XpoeHbbnunKoMy" \
"XCpCq1x1j1FYrMfNaWFkJtf7LEY2STMa33lXoIEnkxmW1VwwKkywoLBlsfxFE0a0" \
"0wFx8b66ioOoAGY07D8wFg")
"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' ;;
@@ -486,9 +486,22 @@ run_prepare() {
}
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 || {
@@ -496,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
}
@@ -517,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
@@ -584,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
@@ -630,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
@@ -648,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() {
@@ -673,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}" ]] && {
@@ -688,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; }
}
@@ -696,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"
}
@@ -788,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
@@ -826,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
@@ -882,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
}
}
@@ -927,7 +1212,7 @@ 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&1.2.93.667'
@@ -950,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&1.2.93.667'
'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=(
@@ -967,7 +1252,7 @@ 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'
'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'
@@ -998,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'
@@ -1015,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'
'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'
@@ -1093,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'
@@ -1121,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'
@@ -1144,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&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'
@@ -1190,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'
@@ -1208,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'
@@ -1225,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