mirror of
https://github.com/fabiodalez-dev/Monochrome-Android-APK.git
synced 2026-10-18 23:07:33 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c072300969 | ||
|
|
bfab3e8f90 | ||
|
|
b03298725e | ||
|
|
3e8c9e7d98 | ||
|
|
b3730f7a69 | ||
|
|
a83ed74219 | ||
|
|
a935cfbf68 | ||
|
|
baaca85f5d | ||
|
|
71c94caba3 | ||
|
|
9dbd8b2163 | ||
|
|
c5c87d8c65 | ||
|
|
8f67f21b7a |
No files matched your search
@@ -1,3 +1,11 @@
|
||||
.DS_Store
|
||||
*.apk
|
||||
saved-tokens/
|
||||
.upstream-sync-sha
|
||||
android/.gradle/
|
||||
android/build/
|
||||
android/app/build/
|
||||
android/capacitor-cordova-android-plugins/
|
||||
android/app/capacitor.build.gradle
|
||||
android/app/src/main/assets/
|
||||
android/app/src/main/res/xml/config.xml
|
||||
+35
-1
@@ -500,8 +500,42 @@ Studio e guarda l'errore reale. Tipicamente:
|
||||
|
||||
## Cronologia delle patch
|
||||
|
||||
- **2026-08-16** — v2.9.0: Sync upstream `41d3b9c` (249 commit dopo `abbc2a6`).
|
||||
Upstream ha riscritto lo stack di riproduzione ("unified API": query TIDAL
|
||||
native via `HiFiClient` + `music-api.geeked.wtf` con Turnstile + fallback
|
||||
Deezer; account con better-auth, `@capacitor/browser` per l'OAuth nativo).
|
||||
Patch rimosse: **#53** `storage.js` fallback istanze streaming (upstream ha
|
||||
tolto la lista hardcoded, `INSTANCES_URLS` è vuoto e i vecchi proxy HiFi
|
||||
sono tutti 503/irraggiungibili) e **#56** `HiFi.ts` `tracks.albums.coverArt`
|
||||
(merged upstream). Le altre 12/12 patch matchano. Aggiunto al manifest del
|
||||
wrapper l'intent-filter `monochrome://auth-callback` (login nativo).
|
||||
`build-web.sh`: `rm -f dist-web.zip` prima dello zip (`zip -r` aggiornava
|
||||
l'archivio vecchio) e prune di `assets/` sul server dopo il deploy.
|
||||
**Nota importante**: sul dominio `music.fabiodalez.it` la riproduzione NON
|
||||
funziona più — le API upstream (Turnstile sitekey, CORS del fallback Deezer)
|
||||
sono bloccate ai soli domini ufficiali (issue #725, risposta del maintainer:
|
||||
"self-hosted instances won't be able to use the API"). Servono un App API
|
||||
Key personale, oppure dev-mode + estensione con account TIDAL. L'APK usa
|
||||
`hostname: monochrome.tf` come origin del WebView e resta nel perimetro:
|
||||
**verificato su emulatore** (Pixel 6 / API 35, via CDP sul WebView) — ricerca
|
||||
100 brani, play di "Creep" e brano successivo con MediaSession `PLAYING` e
|
||||
notifica del foreground service.
|
||||
- **2026-04-10** — Initial release: 50 + 1 ottimizzazioni implementate
|
||||
(escluso #31 battery opt su richiesta esplicita dell'utente). Aggiunti
|
||||
`fm-build.sh`, `verify-patches.sh`, `PATCHES.md`. Rimosso il sed obsoleto
|
||||
`viewport-fit=cover` perché upstream già lo ship.
|
||||
- Future update: riservato per quando upstream mergerà le PR candidate
|
||||
- **2026-05-11** — v2.8.1: Sync upstream `abbc2a6`. Fix proxy Tidal:
|
||||
`tidal-api.geeked.wtf` → `tidal-proxy.monochrome.tf` (ufficiale su CDN
|
||||
Cloudflare MXP). Confermato funzionante via test streaming su monochrome.tf.
|
||||
Rimozione preconnect `fonts.coollabs.io`. Bump am-lyrics.
|
||||
- **2026-05-08** — v2.8.0: Sync upstream `8cf6740`. Nuovo proxy Qobuz primario
|
||||
(`trypt-hifi-dl-456461932686.us-west1.run.app`), fix FOUC tema (preload
|
||||
script in `<head>`), nuova istanza ufficiale `if-it-runs-ship-it.lol`.
|
||||
Tutti i patch invariati (11/11 pass).
|
||||
- **2026-05-06** — v2.7.0: Sync upstream `3936c07`. 14 nuovi commit upstream
|
||||
inclusi senza conflitti (11/11 patch pass). Novità upstream rilevanti:
|
||||
`enrichArtistsWithPicture` + `enrichTracksWithAlbumCover` come fallback lazy
|
||||
in `api.js` (complementari ai nostri fix #55/#56 su `HiFi.ts`); fix download
|
||||
quality Qobuz (LOSSLESS→`'6'`); playlist exports; rimozione browser extension.
|
||||
Manifest Android già aggiornato (POST_NOTIFICATIONS, READ_MEDIA_AUDIO,
|
||||
WAKE_LOCK presenti da prima nel wrapper).
|
||||
@@ -6,12 +6,22 @@
|
||||
// (fm-logger.js is loaded separately in <head> as a synchronous script
|
||||
// so it captures ALL console output from the very start, before any module.)
|
||||
|
||||
// ── TIDAL ORIGIN BYPASS ──
|
||||
// Tell the upstream proxy-utils.js that we handle the Origin header natively
|
||||
// (via TidalWebViewClient.java), so it should use direct CDN URLs instead of
|
||||
// routing through audio-proxy.binimum.org (which is often down/403).
|
||||
// This is the same flag the Chrome extension "Monochrome Tidal Bypass" sets.
|
||||
window.__tidalOriginExtension = true;
|
||||
|
||||
// ── STREAMING INSTANCES BOOTSTRAP ──
|
||||
// Pre-configures HiFi API streaming instances so tracks not on Qobuz
|
||||
// still play via the Tidal proxy fallback (getStreamUrl → getTrack fallback).
|
||||
(function () {
|
||||
var k = 'monochrome-user-api-instances-v1';
|
||||
var d = JSON.parse(localStorage.getItem(k) || '{}');
|
||||
if (!d.streaming || d.streaming.length === 0) {
|
||||
d.streaming = [
|
||||
{ url: 'https://eu-central.monochrome.tf', isUser: true, version: '2.10' },
|
||||
{ url: 'https://us-west.monochrome.tf', isUser: true, version: '2.10' },
|
||||
{ url: 'https://hifi-api.kennyy.com.br', isUser: true, version: '2.10' },
|
||||
];
|
||||
localStorage.setItem(k, JSON.stringify(d));
|
||||
}
|
||||
})();
|
||||
|
||||
// ── UNREGISTER SERVICE WORKER ──
|
||||
// The upstream workbox SW uses CacheFirst for audio/video, but Tidal CDN
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.monochrome.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionCode 13
|
||||
versionName "2.9.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -25,6 +25,14 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Native OAuth return (js/accounts/auth.js: monochrome://auth-callback via @capacitor/browser + App.appUrlOpen) -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="monochrome" android:host="auth-callback" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<service
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
@@ -2,7 +2,6 @@
|
||||
// Saves all console output to Downloads/FabiodalezMusic/fm-console-log.txt
|
||||
|
||||
// Tell upstream we handle Tidal Origin natively — skip audio-proxy.binimum.org.
|
||||
window.__tidalOriginExtension = true;
|
||||
|
||||
// Block Service Worker registration IMMEDIATELY (before any other script runs).
|
||||
// The upstream VitePWA plugin registers a SW that uses CacheFirst for audio,
|
||||
|
||||
+87
-52
@@ -11,6 +11,8 @@ set -euo pipefail
|
||||
# js/app.js — search debounce 3000→500 ms
|
||||
# js/api.js — search result limit = 100 (was backend default ≈25)
|
||||
# js/cache.js — per-type TTL + query normalization (trim/lowercase/diacritics)
|
||||
# js/app.js — search debounce 3000ms → 800ms
|
||||
# js/HiFi.ts — add artists.profileArt to unified search include (fix Picsum covers)
|
||||
#
|
||||
# All reverted automatically on exit. Upstream repo stays clean.
|
||||
# ─────────────────────────────────────────────────────────
|
||||
@@ -29,29 +31,30 @@ cd "$PROJECT_DIR"
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "▶ Cleaning up patched files..."
|
||||
# Revert every tracked file we may have touched.
|
||||
# NOTE: do this file-by-file because `git checkout -- a b c` fails entirely
|
||||
# if any single file is not tracked (e.g. package-lock.json which upstream
|
||||
# doesn't track — they use bun.lock).
|
||||
# Revert tracked files that may have been overwritten by git archive.
|
||||
for f in \
|
||||
index.html \
|
||||
package.json \
|
||||
vite.config.ts \
|
||||
js/app.js \
|
||||
js/api.js \
|
||||
js/cache.js \
|
||||
js/ui.js \
|
||||
js/storage.js \
|
||||
.gitignore \
|
||||
android/app/src/main/java/tf/monochrome/music/BackgroundAudioPlugin.java
|
||||
do
|
||||
if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
|
||||
git checkout -- "$f" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
# Untracked files we generated during the build
|
||||
rm -f js/android-service.js js/fm-logger.js package-lock.json
|
||||
# Remove all upstream web app files we extracted (they are not tracked in this repo).
|
||||
rm -rf \
|
||||
index.html package.json package-lock.json bun.lock bun.lockb \
|
||||
vite.config.ts vite-plugin-auth-gate.js vite-plugin-blob.ts \
|
||||
vite-plugin-svg-use.ts vite-plugin-upload.js \
|
||||
styles.css stream-stub.js tsconfig.json tsconfig-eslint.json \
|
||||
eslint.config.js lhci.yml nginx.conf \
|
||||
js/ src/ public/ assets/ functions/ images/ dist/ node_modules/ \
|
||||
.npmrc .prettierrc .stylelintrc.json .htmlhintrc .gitmodules \
|
||||
.dockerignore .vscode/ .wrangler/ \
|
||||
android/build.gradle android/capacitor.settings.gradle \
|
||||
android/gradle.properties android/settings.gradle android/variables.gradle \
|
||||
android/gradlew android/gradlew.bat android/gradle/ android/.gitignore
|
||||
rm -rf node_modules/@capgo/capacitor-media-session 2>/dev/null || true
|
||||
echo " ✓ Source restored to upstream."
|
||||
echo " ✓ Upstream web app files removed."
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
@@ -59,23 +62,46 @@ echo "════════════════════════
|
||||
echo " Fabiodalez Music — Android Build"
|
||||
echo "══════════════════════════════════════════"
|
||||
|
||||
# ── 1. Pull latest ──
|
||||
# ── 1. Pull latest upstream web app source ──
|
||||
# This repo tracks only the Android wrapper. Web app files come from upstream.
|
||||
# We use git archive (not git pull) because the histories are unrelated.
|
||||
echo ""
|
||||
echo "▶ Pulling latest from upstream/main..."
|
||||
cleanup 2>/dev/null || true
|
||||
git fetch upstream
|
||||
LOCAL=$(git rev-parse HEAD)
|
||||
REMOTE=$(git rev-parse upstream/main)
|
||||
|
||||
if [ "$LOCAL" = "$REMOTE" ]; then
|
||||
echo " Already up to date."
|
||||
UPSTREAM_SHA=$(git rev-parse upstream/main)
|
||||
SYNC_FILE="$PROJECT_DIR/.upstream-sync-sha"
|
||||
LAST_SHA=$(cat "$SYNC_FILE" 2>/dev/null || echo "")
|
||||
|
||||
if [ "$UPSTREAM_SHA" = "$LAST_SHA" ] && [ -f index.html ]; then
|
||||
echo " Already up to date ($(git rev-parse --short upstream/main))."
|
||||
read -p " Build anyway? (y/N) " -n 1 -r
|
||||
echo
|
||||
[[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
|
||||
else
|
||||
echo " $(git rev-list --count HEAD..upstream/main) new commits."
|
||||
git pull upstream main
|
||||
echo " ✓ Updated."
|
||||
N=$(git rev-list --count "${LAST_SHA:-upstream/main^}..upstream/main" 2>/dev/null || echo "?")
|
||||
echo " $N new commits. Extracting web app from upstream/main..."
|
||||
# Extract upstream web app files, excluding our Android additions and docs.
|
||||
git archive upstream/main | tar -x \
|
||||
--exclude='capacitor.config.ts' \
|
||||
--exclude='README.md' \
|
||||
--exclude='android/app' \
|
||||
--exclude='android/android-service.js' \
|
||||
--exclude='android/fm-logger.js' \
|
||||
--exclude='android/capacitor-cordova-android-plugins' \
|
||||
--exclude='ios' \
|
||||
--exclude='extension' \
|
||||
--exclude='docker' \
|
||||
--exclude='.devcontainer' \
|
||||
--exclude='.github' \
|
||||
--exclude='CONTRIBUTING.md' \
|
||||
--exclude='DOCKER.md' \
|
||||
--exclude='INSTANCES.md' \
|
||||
--exclude='THEME_GUIDE.md' \
|
||||
--exclude='license'
|
||||
echo "$UPSTREAM_SHA" > "$SYNC_FILE"
|
||||
echo " ✓ Updated to $(git rev-parse --short upstream/main)."
|
||||
fi
|
||||
|
||||
# ── 2a. Pre-npm patch: fix upstream broken override ──
|
||||
@@ -167,37 +193,27 @@ def patch(path, before, after, label):
|
||||
print(" + " + label)
|
||||
return True
|
||||
|
||||
# ── #53: Add working streaming instance to fallback list ──
|
||||
# The hardcoded fallback in storage.js doesn't include frankfurt-2.monochrome.tf,
|
||||
# which is often the ONLY working streaming proxy. On a fresh install where the
|
||||
# uptime worker fetch fails, all hardcoded instances return 403 → no playback.
|
||||
# ── #53: storage.js streaming fallback — REMOVED 2026-08-16 ──
|
||||
# Upstream (41d3b9c) dropped the hardcoded instance list: INSTANCES_URLS is
|
||||
# empty, the offline fallback is `api: [lol.samidy.workers.dev], streaming: []`
|
||||
# and playback goes through the "unified playback" API (music-api.geeked.wtf)
|
||||
# plus native TIDAL queries (HiFiClient). The old HiFi proxies are all 503/down.
|
||||
|
||||
# ── #54: REMOVED — was forcing native HiFiClient for streaming, which gives
|
||||
# only preview (1:40) because the browser client credentials aren't premium.
|
||||
# Streaming MUST go through proxy instances (they have premium credentials).
|
||||
|
||||
# ── #1: debounce 3000ms → 800ms ──
|
||||
# 3 seconds after the last keystroke is too slow for mobile. 800ms is the
|
||||
# standard for search bars and still avoids firing on every keystroke.
|
||||
# "Renders partial queries" is normal search behavior (like Spotify/Google).
|
||||
patch(
|
||||
"js/storage.js",
|
||||
""" streaming: [
|
||||
{ url: 'https://hifi.geeked.wtf', version: '2.7' },""",
|
||||
""" streaming: [
|
||||
{ url: 'https://frankfurt-2.monochrome.tf', version: '2.10' },
|
||||
{ url: 'https://hifi.geeked.wtf', version: '2.7' },""",
|
||||
"storage.js: add frankfurt-2 to streaming fallback",
|
||||
"js/app.js",
|
||||
"}, 3000);",
|
||||
"}, 800);",
|
||||
"app.js: search debounce 3000ms -> 800ms",
|
||||
)
|
||||
|
||||
# ── #54: Always try native HiFiClient for streaming (not just proxies) ──
|
||||
# The upstream code skips HiFiClient for streaming requests (type='streaming')
|
||||
# and goes straight to proxy instances. But when ALL proxies are down/403,
|
||||
# there's no fallback. Force shouldTryNative=true so HiFiClient is tried first.
|
||||
patch(
|
||||
"js/api.js",
|
||||
" const shouldTryNative = type !== 'streaming';",
|
||||
" const shouldTryNative = true; // patched: always try native, including streaming",
|
||||
"api.js: force native HiFiClient for streaming",
|
||||
)
|
||||
|
||||
# ── #1 + #2: debounce + min-chars REMOVED ──
|
||||
# The upstream 3000ms debounce works fine in practice — it lets users finish
|
||||
# typing before navigating. Our attempts to reduce it (500ms, 700ms) all
|
||||
# caused the search page to render for partial queries.
|
||||
# Keeping upstream behavior as-is.
|
||||
#
|
||||
# The limit=100 and cache normalization patches below still apply.
|
||||
|
||||
# ── #52: Enrich albums missing artist/cover from track data ──
|
||||
@@ -397,6 +413,22 @@ patch(
|
||||
"vite.config.ts: workbox audio/video CacheFirst -> NetworkOnly",
|
||||
)
|
||||
|
||||
# ── #55: HiFi.ts unified search — add artists.profileArt to include ──
|
||||
# The unified search (q=) omits 'artists.profileArt' from the include list,
|
||||
# so artist items in the included array have no profileArt relationships and
|
||||
# no artwork entries are returned. resolveArtworkId(item,'profileArt') always
|
||||
# returns null → artist.picture = null → getCoverUrl(null) → random Picsum.
|
||||
# The per-artist search (a=) already has artists.profileArt — this aligns q=.
|
||||
patch(
|
||||
"js/HiFi.ts",
|
||||
"'albums,albums.coverArt,albums.artists,tracks,tracks.artists,tracks.albums,tracks.albums.coverArt,artists,playlists,videos'",
|
||||
"'albums,albums.coverArt,albums.artists,tracks,tracks.artists,tracks.albums,tracks.albums.coverArt,artists,artists.profileArt,playlists,videos'",
|
||||
"HiFi.ts: add artists.profileArt to unified search include (fixes Picsum artist covers)",
|
||||
)
|
||||
|
||||
# ── #56: HiFi.ts artist page tracks.albums.coverArt — UPSTREAM MERGED (2026-08-16) ──
|
||||
# Upstream now ships 'albums,albums.coverArt,tracks,tracks.albums,tracks.albums.coverArt,biography,profileArt'.
|
||||
|
||||
print(" ✓ Upstream JS optimizations applied.")
|
||||
PYEOF
|
||||
|
||||
@@ -404,7 +436,10 @@ echo " ✓ JS upstream optimizations applied (debounce, cache, limits)."
|
||||
|
||||
# ── 4. Copy wrapper JS files from android/ storage ──
|
||||
cp "$PROJECT_DIR/android/android-service.js" js/android-service.js
|
||||
cp "$PROJECT_DIR/android/fm-logger.js" js/fm-logger.js
|
||||
# fm-logger must go into public/js/ so Vite copies it to dist/js/ as a static asset.
|
||||
# Putting it in js/ (source dir) would leave it out of the dist/ bundle entirely.
|
||||
mkdir -p public/js
|
||||
cp "$PROJECT_DIR/android/fm-logger.js" public/js/fm-logger.js
|
||||
echo " ✓ android-service.js + fm-logger.js copied."
|
||||
|
||||
# ── 5. Init Capacitor Android if needed ──
|
||||
|
||||
Executable
+386
@@ -0,0 +1,386 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# build-web.sh — Fabiodalez Music web build + deploy to music.fabiodalez.it
|
||||
#
|
||||
# Applies the same functional patches as build-android.sh
|
||||
# (no Android-specific changes: no android-service.js, no fm-logger,
|
||||
# no brand rename, no viewport-fit).
|
||||
#
|
||||
# Output: dist/ directory + dist-web.zip ready for cPanel upload.
|
||||
# ─────────────────────────────────────────────────────────
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
DEPLOY_HOST="fabiodalez.it"
|
||||
# cPanel subdomain document root (standard cPanel layout)
|
||||
DEPLOY_PATH="music.fabiodalez.it"
|
||||
|
||||
export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "▶ Cleaning up patched files..."
|
||||
for f in .gitignore; do
|
||||
if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
|
||||
git checkout -- "$f" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
rm -rf \
|
||||
index.html package.json package-lock.json bun.lock bun.lockb \
|
||||
vite.config.ts vite-plugin-auth-gate.js vite-plugin-blob.ts \
|
||||
vite-plugin-svg-use.ts vite-plugin-upload.js \
|
||||
styles.css stream-stub.js tsconfig.json tsconfig-eslint.json \
|
||||
eslint.config.js lhci.yml nginx.conf \
|
||||
js/ src/ public/ assets/ functions/ images/ dist/ node_modules/ \
|
||||
.npmrc .prettierrc .stylelintrc.json .htmlhintrc .gitmodules \
|
||||
.dockerignore .vscode/ .wrangler/ \
|
||||
android/build.gradle android/capacitor.settings.gradle \
|
||||
android/gradle.properties android/settings.gradle android/variables.gradle \
|
||||
android/gradlew android/gradlew.bat android/gradle/ android/.gitignore
|
||||
rm -rf node_modules/@capgo/capacitor-media-session 2>/dev/null || true
|
||||
echo " ✓ Upstream web app files removed."
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "══════════════════════════════════════════"
|
||||
echo " Fabiodalez Music — Web Build"
|
||||
echo "══════════════════════════════════════════"
|
||||
|
||||
# ── 1. Pull latest upstream ──
|
||||
echo ""
|
||||
echo "▶ Pulling latest from upstream/main..."
|
||||
cleanup 2>/dev/null || true
|
||||
git fetch upstream
|
||||
|
||||
UPSTREAM_SHA=$(git rev-parse upstream/main)
|
||||
SYNC_FILE="$PROJECT_DIR/.upstream-sync-sha"
|
||||
LAST_SHA=$(cat "$SYNC_FILE" 2>/dev/null || echo "")
|
||||
|
||||
if [ "$UPSTREAM_SHA" = "$LAST_SHA" ] && [ -f index.html ]; then
|
||||
echo " Already up to date ($(git rev-parse --short upstream/main))."
|
||||
read -p " Build anyway? (y/N) " -n 1 -r
|
||||
echo
|
||||
[[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
|
||||
else
|
||||
echo " Extracting web app from upstream/main ($(git rev-parse --short upstream/main))..."
|
||||
git archive upstream/main | tar -x \
|
||||
--exclude='capacitor.config.ts' \
|
||||
--exclude='README.md' \
|
||||
--exclude='android/' \
|
||||
--exclude='ios/' \
|
||||
--exclude='extension/' \
|
||||
--exclude='docker/' \
|
||||
--exclude='.devcontainer' \
|
||||
--exclude='.github' \
|
||||
--exclude='CONTRIBUTING.md' \
|
||||
--exclude='DOCKER.md' \
|
||||
--exclude='INSTANCES.md' \
|
||||
--exclude='THEME_GUIDE.md' \
|
||||
--exclude='license'
|
||||
echo "$UPSTREAM_SHA" > "$SYNC_FILE"
|
||||
echo " ✓ Updated to $(git rev-parse --short upstream/main)."
|
||||
fi
|
||||
|
||||
# ── 2. Fix upstream broken npm override ──
|
||||
if grep -q '"sourcemap-codec": "\^1.4.14"' package.json; then
|
||||
sed -i '' 's|"sourcemap-codec": "\^1.4.14"|"sourcemap-codec": "^1.4.8"|' package.json
|
||||
echo " ✓ package.json: sourcemap-codec override fixed."
|
||||
fi
|
||||
|
||||
# ── 3. Install deps + shim media-session ──
|
||||
echo ""
|
||||
echo "▶ Installing dependencies..."
|
||||
npm install 2>&1 | tail -3
|
||||
echo " ✓ Done."
|
||||
|
||||
SHIM_DIR="node_modules/@capgo/capacitor-media-session"
|
||||
[ -d "$SHIM_DIR" ] && rm -rf "$SHIM_DIR"
|
||||
mkdir -p "$SHIM_DIR"
|
||||
cat > "$SHIM_DIR/index.js" <<'SHIMEOF'
|
||||
export const MediaSession = {
|
||||
setActionHandler: async () => {},
|
||||
setMetadata: async () => {},
|
||||
setPlaybackState: async () => {},
|
||||
setPositionState: async () => {},
|
||||
};
|
||||
SHIMEOF
|
||||
cat > "$SHIM_DIR/package.json" <<'SHIMEOF'
|
||||
{"name":"@capgo/capacitor-media-session","version":"0.0.0-shim","main":"index.js","module":"index.js","type":"module"}
|
||||
SHIMEOF
|
||||
echo " ✓ @capgo/capacitor-media-session shimmed."
|
||||
|
||||
# ── 4. Apply functional patches ──
|
||||
echo ""
|
||||
echo "▶ Patching upstream sources..."
|
||||
python3 <<'PYEOF'
|
||||
import sys, os, re
|
||||
|
||||
PROJECT_DIR = os.environ.get("PROJECT_DIR", os.getcwd())
|
||||
|
||||
def patch(path, before, after, label):
|
||||
full = os.path.join(PROJECT_DIR, path)
|
||||
with open(full, "r", encoding="utf-8") as f:
|
||||
src = f.read()
|
||||
if before not in src:
|
||||
print(" ! " + label + ": pattern not found, skipping")
|
||||
return False
|
||||
src = src.replace(before, after, 1)
|
||||
with open(full, "w", encoding="utf-8") as f:
|
||||
f.write(src)
|
||||
print(" + " + label)
|
||||
return True
|
||||
|
||||
# ── storage.js streaming fallback: REMOVED 2026-08-16 ──
|
||||
# Upstream (41d3b9c) dropped the hardcoded instance list: INSTANCES_URLS is
|
||||
# empty, the offline fallback is `api: [lol.samidy.workers.dev], streaming: []`
|
||||
# and playback goes through the "unified playback" API (music-api.geeked.wtf)
|
||||
# plus native TIDAL queries (HiFiClient). The old HiFi proxies are all 503/down.
|
||||
|
||||
# ── search debounce 3000ms → 800ms ──
|
||||
patch(
|
||||
"js/app.js",
|
||||
"}, 3000);",
|
||||
"}, 800);",
|
||||
"app.js: search debounce 3000ms -> 800ms",
|
||||
)
|
||||
|
||||
# ── enrich albums missing artist/cover from tracks ──
|
||||
patch(
|
||||
"js/ui.js",
|
||||
""" if (finalAlbums.length === 0 && finalTracks.length > 0) {
|
||||
const albumMap = new Map();
|
||||
finalTracks.forEach((track) => {
|
||||
if (track.album && !albumMap.has(track.album.id)) {
|
||||
albumMap.set(track.album.id, track.album);
|
||||
}
|
||||
});
|
||||
finalAlbums = Array.from(albumMap.values());
|
||||
}""",
|
||||
""" if (finalAlbums.length === 0 && finalTracks.length > 0) {
|
||||
const albumMap = new Map();
|
||||
finalTracks.forEach((track) => {
|
||||
if (track.album && !albumMap.has(track.album.id)) {
|
||||
albumMap.set(track.album.id, track.album);
|
||||
}
|
||||
});
|
||||
finalAlbums = Array.from(albumMap.values());
|
||||
}
|
||||
|
||||
// Enrich albums that have no artist or cover from track data
|
||||
if (finalAlbums.length > 0 && finalTracks.length > 0) {
|
||||
const trackInfoMap = new Map();
|
||||
finalTracks.forEach((track) => {
|
||||
if (track.album && track.album.id && !trackInfoMap.has(track.album.id)) {
|
||||
trackInfoMap.set(track.album.id, {
|
||||
artist: track.artist || (track.artists && track.artists[0]) || null,
|
||||
cover: (track.album && track.album.cover) || null,
|
||||
});
|
||||
}
|
||||
});
|
||||
finalAlbums.forEach((album) => {
|
||||
const info = trackInfoMap.get(album.id);
|
||||
if (!info) return;
|
||||
if (!album.artist && !album.artists?.length && info.artist) {
|
||||
album.artist = info.artist;
|
||||
album.artists = [info.artist];
|
||||
}
|
||||
if (!album.cover && info.cover) {
|
||||
album.cover = info.cover;
|
||||
}
|
||||
});
|
||||
}""",
|
||||
"ui.js: enrich albums missing artist/cover from tracks",
|
||||
)
|
||||
|
||||
# ── cache: per-type TTL ──
|
||||
patch(
|
||||
"js/cache.js",
|
||||
" this.ttl = options.ttl || 1000 * 60 * 30;",
|
||||
""" this.ttl = options.ttl || 1000 * 60 * 30;
|
||||
this.ttlByType = {
|
||||
search_all: 1000 * 60 * 10,
|
||||
search_tracks: 1000 * 60 * 10,
|
||||
search_artists: 1000 * 60 * 60,
|
||||
search_albums: 1000 * 60 * 30,
|
||||
search_playlists: 1000 * 60 * 60,
|
||||
search_videos: 1000 * 60 * 15,
|
||||
};""",
|
||||
"cache.js: per-type TTL map",
|
||||
)
|
||||
|
||||
# ── cache: query normalization ──
|
||||
patch(
|
||||
"js/cache.js",
|
||||
""" generateKey(type, params) {
|
||||
const paramString = typeof params === 'object' ? JSON.stringify(params) : String(params);
|
||||
return `${type}:${paramString}`;
|
||||
}""",
|
||||
""" generateKey(type, params) {
|
||||
let normalized = params;
|
||||
if (typeof params === 'string' && typeof type === 'string' && type.startsWith('search')) {
|
||||
normalized = params
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\\u0300-\\u036f]/g, '');
|
||||
}
|
||||
const paramString = typeof normalized === 'object' ? JSON.stringify(normalized) : String(normalized);
|
||||
return `${type}:${paramString}`;
|
||||
}""",
|
||||
"cache.js: query normalization (trim/lowercase/NFD)",
|
||||
)
|
||||
|
||||
# ── cache: per-type TTL lookup in get() ──
|
||||
patch(
|
||||
"js/cache.js",
|
||||
""" async get(type, params) {
|
||||
const key = this.generateKey(type, params);
|
||||
|
||||
if (this.memoryCache.has(key)) {
|
||||
const cached = this.memoryCache.get(key);
|
||||
if (Date.now() - cached.timestamp < this.ttl) {
|
||||
return cached.data;
|
||||
}
|
||||
this.memoryCache.delete(key);
|
||||
}
|
||||
|
||||
if (this.db) {
|
||||
try {
|
||||
const cached = await this.getFromIndexedDB(key);
|
||||
if (cached && Date.now() - cached.timestamp < this.ttl) {""",
|
||||
""" async get(type, params) {
|
||||
const key = this.generateKey(type, params);
|
||||
const effectiveTtl = (this.ttlByType && this.ttlByType[type]) || this.ttl;
|
||||
|
||||
if (this.memoryCache.has(key)) {
|
||||
const cached = this.memoryCache.get(key);
|
||||
if (Date.now() - cached.timestamp < effectiveTtl) {
|
||||
return cached.data;
|
||||
}
|
||||
this.memoryCache.delete(key);
|
||||
}
|
||||
|
||||
if (this.db) {
|
||||
try {
|
||||
const cached = await this.getFromIndexedDB(key);
|
||||
if (cached && Date.now() - cached.timestamp < effectiveTtl) {""",
|
||||
"cache.js: per-type TTL lookup in get()",
|
||||
)
|
||||
|
||||
# ── api.js: search limit=100 ──
|
||||
patch("js/api.js",
|
||||
"const response = await this.fetchWithRetry(`/search/?q=${encodeURIComponent(query)}`, options);",
|
||||
"const response = await this.fetchWithRetry(`/search/?q=${encodeURIComponent(query)}&limit=${(options && options.limit) || 100}`, options);",
|
||||
"api.js: search() unified — limit=100")
|
||||
patch("js/api.js",
|
||||
"const response = await this.fetchWithRetry(`/search/?s=${encodeURIComponent(query)}`, options);",
|
||||
"const response = await this.fetchWithRetry(`/search/?s=${encodeURIComponent(query)}&limit=${(options && options.limit) || 100}`, options);",
|
||||
"api.js: searchTracks — limit=100")
|
||||
patch("js/api.js",
|
||||
"const response = await this.fetchWithRetry(`/search/?al=${encodeURIComponent(query)}`, options);",
|
||||
"const response = await this.fetchWithRetry(`/search/?al=${encodeURIComponent(query)}&limit=${(options && options.limit) || 100}`, options);",
|
||||
"api.js: searchAlbums — limit=100")
|
||||
patch("js/api.js",
|
||||
"const response = await this.fetchWithRetry(`/search/?p=${encodeURIComponent(query)}`, options);",
|
||||
"const response = await this.fetchWithRetry(`/search/?p=${encodeURIComponent(query)}&limit=${(options && options.limit) || 100}`, options);",
|
||||
"api.js: searchPlaylists — limit=100")
|
||||
patch("js/api.js",
|
||||
"""const response = await this.fetchWithRetry(`/search/?v=${encodeURIComponent(query)}`, {
|
||||
...options,
|
||||
});""",
|
||||
"""const response = await this.fetchWithRetry(`/search/?v=${encodeURIComponent(query)}&limit=${(options && options.limit) || 100}`, {
|
||||
...options,
|
||||
});""",
|
||||
"api.js: searchVideos — limit=100")
|
||||
|
||||
try:
|
||||
with open(os.path.join(PROJECT_DIR, "js/api.js"), "r", encoding="utf-8") as f:
|
||||
api_src = f.read()
|
||||
new_src, n = re.subn(
|
||||
r"(`/search/\?a=\$\{encodeURIComponent\(query\)\})(`)",
|
||||
r"\1&limit=${(options && options.limit) || 100}\2",
|
||||
api_src,
|
||||
)
|
||||
if n > 0:
|
||||
with open(os.path.join(PROJECT_DIR, "js/api.js"), "w", encoding="utf-8") as f:
|
||||
f.write(new_src)
|
||||
print(" + api.js: searchArtists — limit=100")
|
||||
else:
|
||||
print(" ! api.js: searchArtists pattern not found, skipping")
|
||||
except Exception as e:
|
||||
print(" ! api.js: searchArtists patch failed: " + str(e))
|
||||
|
||||
# ── HiFi.ts: add artists.profileArt to unified search include ──
|
||||
patch(
|
||||
"js/HiFi.ts",
|
||||
"'albums,albums.coverArt,albums.artists,tracks,tracks.artists,tracks.albums,tracks.albums.coverArt,artists,playlists,videos'",
|
||||
"'albums,albums.coverArt,albums.artists,tracks,tracks.artists,tracks.albums,tracks.albums.coverArt,artists,artists.profileArt,playlists,videos'",
|
||||
"HiFi.ts: add artists.profileArt to unified search include (fixes Picsum artist covers)",
|
||||
)
|
||||
|
||||
# ── HiFi.ts: tracks.albums.coverArt on artist page — UPSTREAM MERGED (2026-08-16) ──
|
||||
# Upstream now ships 'albums,albums.coverArt,tracks,tracks.albums,tracks.albums.coverArt,biography,profileArt'.
|
||||
|
||||
# ── Workbox: CacheFirst → NetworkOnly for audio/video ──
|
||||
patch(
|
||||
"vite.config.ts",
|
||||
"handler: 'CacheFirst',\n options: {\n cacheName: 'media',",
|
||||
"handler: 'NetworkOnly',\n options: {\n cacheName: 'media',",
|
||||
"vite.config.ts: workbox audio/video CacheFirst -> NetworkOnly",
|
||||
)
|
||||
|
||||
print(" ✓ All web patches applied.")
|
||||
PYEOF
|
||||
|
||||
echo " ✓ Patches done."
|
||||
|
||||
# ── 5. Build ──
|
||||
echo ""
|
||||
echo "▶ Building web app..."
|
||||
npx vite build 2>&1 | tail -3
|
||||
echo " ✓ Build complete."
|
||||
|
||||
# ── 6. Package for upload ──
|
||||
echo ""
|
||||
echo "▶ Packaging dist/ for upload..."
|
||||
# `zip -r` UPDATES an existing archive (stale entries from older builds survive) — start clean.
|
||||
rm -f dist-web.zip
|
||||
(cd dist && zip -r ../dist-web.zip . -x "*.map") 2>&1 | tail -3
|
||||
SIZE=$(du -h "$PROJECT_DIR/dist-web.zip" | awk '{print $1}')
|
||||
echo " ✓ dist-web.zip (${SIZE}) ready."
|
||||
|
||||
# ── 7. Deploy via rsync over SSH (if accessible) ──
|
||||
echo ""
|
||||
echo "▶ Attempting deploy to ${DEPLOY_HOST}:${DEPLOY_PATH}/..."
|
||||
if rsync -az --no-perms --omit-dir-times \
|
||||
-e "ssh -o ConnectTimeout=10 -o BatchMode=yes" \
|
||||
dist/ "${DEPLOY_HOST}:~/${DEPLOY_PATH}/" 2>&1; then
|
||||
echo " ✓ Deployed to ${DEPLOY_HOST}:~/${DEPLOY_PATH}/"
|
||||
# assets/ is 100% build output (hashed chunks + public/assets) → safe to prune
|
||||
# stale files from previous builds there. Root is left untouched (.htaccess, cgi-bin, …).
|
||||
rsync -az --delete --no-perms --omit-dir-times \
|
||||
-e "ssh -o ConnectTimeout=10 -o BatchMode=yes" \
|
||||
dist/assets/ "${DEPLOY_HOST}:~/${DEPLOY_PATH}/assets/" 2>&1 \
|
||||
&& echo " ✓ Pruned stale files in assets/"
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════"
|
||||
echo " Live at: https://${DEPLOY_PATH}/"
|
||||
echo "══════════════════════════════════════════"
|
||||
else
|
||||
echo " ! rsync failed — use cPanel File Manager instead:"
|
||||
echo ""
|
||||
echo " 1. Open cPanel → File Manager → music.fabiodalez.it/"
|
||||
echo " 2. Upload: $PROJECT_DIR/dist-web.zip"
|
||||
echo " 3. Extract in place"
|
||||
echo " 4. Delete dist-web.zip"
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════"
|
||||
echo " ZIP: $PROJECT_DIR/dist-web.zip (${SIZE})"
|
||||
echo "══════════════════════════════════════════"
|
||||
fi
|
||||
|
||||
# cleanup() runs automatically via trap EXIT
|
||||
Reference in new issue
Block a user