Change instances.json in favour of Workers which have live API uptime
This commit is contained in:
parent
acf64cbc17
commit
01309a9a15
12 changed files with 300 additions and 178 deletions
|
|
@ -105,7 +105,7 @@ monochrome/
|
|||
├── 📁 public/ # Static assets
|
||||
│ ├── assets/ # Images, icons, fonts
|
||||
│ ├── manifest.json # PWA manifest
|
||||
│ └── instances.json # API instances configuration
|
||||
│ └── instances.json # API instances configuration (deprecated)
|
||||
├── 📄 index.html # Application entry point
|
||||
├── 📄 vite.config.js # Build and PWA configuration
|
||||
├── 📄 package.json # Dependencies and scripts
|
||||
|
|
|
|||
|
|
@ -31,7 +31,12 @@ These instances provide the tidal-ui web interface, not monochrome:
|
|||
|
||||
## API Instances
|
||||
|
||||
Monochrome uses the Hi-Fi API under the hood. These are available API endpoints that can be used with Monochrome or other Hi-Fi based applications:
|
||||
Monochrome uses the Hi-Fi API under the hood. Live, up-to-date status trackers (which return JSON) can be found below:
|
||||
|
||||
- https://tidal-uptime.jiffy-puffs-1j.workers.dev/
|
||||
- https://tidal-uptime.props-76styles.workers.dev/
|
||||
|
||||
These are available API endpoints that can be used with Monochrome or other Hi-Fi based applications:
|
||||
|
||||
### Official & Community APIs
|
||||
|
||||
|
|
|
|||
|
|
@ -2,27 +2,52 @@
|
|||
|
||||
class ServerAPI {
|
||||
constructor() {
|
||||
this.INSTANCES_URL = 'https://raw.githubusercontent.com/Monochrome-music/monochrome/main/instances.json';
|
||||
this.INSTANCES_URLS = [
|
||||
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
|
||||
'https://tidal-uptime.props-76styles.workers.dev/'
|
||||
];
|
||||
this.apiInstances = null;
|
||||
}
|
||||
|
||||
async getInstances() {
|
||||
if (this.apiInstances) return this.apiInstances;
|
||||
try {
|
||||
const response = await fetch(this.INSTANCES_URL);
|
||||
if (!response.ok) throw new Error('Failed to fetch instances');
|
||||
const data = await response.json();
|
||||
this.apiInstances = data.api || [];
|
||||
return this.apiInstances;
|
||||
} catch (error) {
|
||||
console.error('Failed to load instances:', error);
|
||||
return [
|
||||
'https://triton.squid.wtf',
|
||||
'https://wolf.qqdl.site',
|
||||
'https://tidal-api.binimum.org',
|
||||
'https://monochrome-api.samidy.com',
|
||||
];
|
||||
|
||||
let data = null;
|
||||
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
data = await response.json();
|
||||
break;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to fetch from ${url}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
this.apiInstances = (data.api || [])
|
||||
.map(item => item.url || item)
|
||||
.filter(url => !url.includes('spotisaver.net'));
|
||||
return this.apiInstances;
|
||||
}
|
||||
|
||||
console.error('Failed to load instances from all uptime APIs');
|
||||
return [
|
||||
"https://eu-central.monochrome.tf",
|
||||
"https://us-west.monochrome.tf",
|
||||
"https://arran.monochrome.tf",
|
||||
"https://triton.squid.wtf",
|
||||
"https://api.monochrome.tf",
|
||||
"https://monochrome-api.samidy.com",
|
||||
"https://maus.qqdl.site",
|
||||
"https://vogel.qqdl.site",
|
||||
"https://katze.qqdl.site",
|
||||
"https://hund.qqdl.site",
|
||||
"https://tidal.kinoplus.online",
|
||||
"https://wolf.qqdl.site"
|
||||
];
|
||||
}
|
||||
|
||||
async fetchWithRetry(relativePath) {
|
||||
|
|
|
|||
|
|
@ -2,27 +2,52 @@
|
|||
|
||||
class ServerAPI {
|
||||
constructor() {
|
||||
this.INSTANCES_URL = 'https://raw.githubusercontent.com/Monochrome-music/monochrome/main/public/instances.json';
|
||||
this.INSTANCES_URLS = [
|
||||
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
|
||||
'https://tidal-uptime.props-76styles.workers.dev/'
|
||||
];
|
||||
this.apiInstances = null;
|
||||
}
|
||||
|
||||
async getInstances() {
|
||||
if (this.apiInstances) return this.apiInstances;
|
||||
try {
|
||||
const response = await fetch(this.INSTANCES_URL);
|
||||
if (!response.ok) throw new Error('Failed to fetch instances');
|
||||
const data = await response.json();
|
||||
this.apiInstances = data.api || [];
|
||||
return this.apiInstances;
|
||||
} catch (error) {
|
||||
console.error('Failed to load instances:', error);
|
||||
return [
|
||||
'https://triton.squid.wtf',
|
||||
'https://wolf.qqdl.site',
|
||||
'https://tidal-api.binimum.org',
|
||||
'https://monochrome-api.samidy.com',
|
||||
];
|
||||
|
||||
let data = null;
|
||||
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
data = await response.json();
|
||||
break;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to fetch from ${url}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
this.apiInstances = (data.api || [])
|
||||
.map(item => item.url || item)
|
||||
.filter(url => !url.includes('spotisaver.net'));
|
||||
return this.apiInstances;
|
||||
}
|
||||
|
||||
console.error('Failed to load instances from all uptime APIs');
|
||||
return [
|
||||
"https://eu-central.monochrome.tf",
|
||||
"https://us-west.monochrome.tf",
|
||||
"https://arran.monochrome.tf",
|
||||
"https://triton.squid.wtf",
|
||||
"https://api.monochrome.tf",
|
||||
"https://monochrome-api.samidy.com",
|
||||
"https://maus.qqdl.site",
|
||||
"https://vogel.qqdl.site",
|
||||
"https://katze.qqdl.site",
|
||||
"https://hund.qqdl.site",
|
||||
"https://tidal.kinoplus.online",
|
||||
"https://wolf.qqdl.site"
|
||||
];
|
||||
}
|
||||
|
||||
async fetchWithRetry(relativePath) {
|
||||
|
|
|
|||
|
|
@ -2,27 +2,52 @@
|
|||
|
||||
class ServerAPI {
|
||||
constructor() {
|
||||
this.INSTANCES_URL = 'https://raw.githubusercontent.com/Monochrome-music/monochrome/main/public/instances.json';
|
||||
this.INSTANCES_URLS = [
|
||||
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
|
||||
'https://tidal-uptime.props-76styles.workers.dev/'
|
||||
];
|
||||
this.apiInstances = null;
|
||||
}
|
||||
|
||||
async getInstances() {
|
||||
if (this.apiInstances) return this.apiInstances;
|
||||
try {
|
||||
const response = await fetch(this.INSTANCES_URL);
|
||||
if (!response.ok) throw new Error('Failed to fetch instances');
|
||||
const data = await response.json();
|
||||
this.apiInstances = data.api || [];
|
||||
return this.apiInstances;
|
||||
} catch (error) {
|
||||
console.error('Failed to load instances:', error);
|
||||
return [
|
||||
'https://triton.squid.wtf',
|
||||
'https://wolf.qqdl.site',
|
||||
'https://tidal-api.binimum.org',
|
||||
'https://monochrome-api.samidy.com',
|
||||
];
|
||||
|
||||
let data = null;
|
||||
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
data = await response.json();
|
||||
break;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to fetch from ${url}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
this.apiInstances = (data.api || [])
|
||||
.map(item => item.url || item)
|
||||
.filter(url => !url.includes('spotisaver.net'));
|
||||
return this.apiInstances;
|
||||
}
|
||||
|
||||
console.error('Failed to load instances from all uptime APIs');
|
||||
return [
|
||||
"https://eu-central.monochrome.tf",
|
||||
"https://us-west.monochrome.tf",
|
||||
"https://arran.monochrome.tf",
|
||||
"https://triton.squid.wtf",
|
||||
"https://api.monochrome.tf",
|
||||
"https://monochrome-api.samidy.com",
|
||||
"https://maus.qqdl.site",
|
||||
"https://vogel.qqdl.site",
|
||||
"https://katze.qqdl.site",
|
||||
"https://hund.qqdl.site",
|
||||
"https://tidal.kinoplus.online",
|
||||
"https://wolf.qqdl.site"
|
||||
];
|
||||
}
|
||||
|
||||
async fetchWithRetry(relativePath) {
|
||||
|
|
|
|||
|
|
@ -14,27 +14,52 @@ function getTrackArtists(track = {}, { fallback = 'Unknown Artist' } = {}) {
|
|||
|
||||
class ServerAPI {
|
||||
constructor() {
|
||||
this.INSTANCES_URL = 'https://raw.githubusercontent.com/Monochrome-music/monochrome/main/public/instances.json';
|
||||
this.INSTANCES_URLS = [
|
||||
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
|
||||
'https://tidal-uptime.props-76styles.workers.dev/'
|
||||
];
|
||||
this.apiInstances = null;
|
||||
}
|
||||
|
||||
async getInstances() {
|
||||
if (this.apiInstances) return this.apiInstances;
|
||||
try {
|
||||
const response = await fetch(this.INSTANCES_URL);
|
||||
if (!response.ok) throw new Error('Failed to fetch instances');
|
||||
const data = await response.json();
|
||||
this.apiInstances = data.api || [];
|
||||
return this.apiInstances;
|
||||
} catch (error) {
|
||||
console.error('Failed to load instances from GitHub:', error);
|
||||
return [
|
||||
'https://triton.squid.wtf',
|
||||
'https://wolf.qqdl.site',
|
||||
'https://tidal-api.binimum.org',
|
||||
'https://monochrome-api.samidy.com',
|
||||
];
|
||||
|
||||
let data = null;
|
||||
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
data = await response.json();
|
||||
break;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to fetch from ${url}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
this.apiInstances = (data.api || [])
|
||||
.map(item => item.url || item)
|
||||
.filter(url => !url.includes('spotisaver.net'));
|
||||
return this.apiInstances;
|
||||
}
|
||||
|
||||
console.error('Failed to load instances from all uptime APIs');
|
||||
return [
|
||||
"https://eu-central.monochrome.tf",
|
||||
"https://us-west.monochrome.tf",
|
||||
"https://arran.monochrome.tf",
|
||||
"https://triton.squid.wtf",
|
||||
"https://api.monochrome.tf",
|
||||
"https://monochrome-api.samidy.com",
|
||||
"https://maus.qqdl.site",
|
||||
"https://vogel.qqdl.site",
|
||||
"https://katze.qqdl.site",
|
||||
"https://hund.qqdl.site",
|
||||
"https://tidal.kinoplus.online",
|
||||
"https://wolf.qqdl.site"
|
||||
];
|
||||
}
|
||||
|
||||
async fetchWithRetry(relativePath) {
|
||||
|
|
|
|||
|
|
@ -106,19 +106,7 @@ export function trackToggleMute(muted) {
|
|||
trackEvent('Toggle Mute', { muted });
|
||||
}
|
||||
|
||||
export function trackSeek(position, duration) {
|
||||
const progress = duration ? Math.round((position / duration) * 100) : 0;
|
||||
// Track seek at 25%, 50%, 75% milestones
|
||||
if (progress >= 25 && progress < 30) {
|
||||
trackEvent('Seek', { milestone: '25%', position });
|
||||
} else if (progress >= 50 && progress < 55) {
|
||||
trackEvent('Seek', { milestone: '50%', position });
|
||||
} else if (progress >= 75 && progress < 80) {
|
||||
trackEvent('Seek', { milestone: '75%', position });
|
||||
}
|
||||
}
|
||||
|
||||
// Track listening progress milestones (10%, 25%, 50%, 75%, 90%, 100%)
|
||||
// Track listening progress milestones (10%, 50%, 90%, 100%)
|
||||
export function trackListeningProgress(track, percent) {
|
||||
trackEvent('Listening Progress', {
|
||||
track_id: track?.id || 'unknown',
|
||||
|
|
|
|||
23
js/api.js
23
js/api.js
|
|
@ -42,17 +42,28 @@ export class LosslessAPI {
|
|||
|
||||
async fetchWithRetry(relativePath, options = {}) {
|
||||
const type = options.type || 'api';
|
||||
const instances = await this.settings.getInstances(type);
|
||||
let instances = await this.settings.getInstances(type);
|
||||
if (instances.length === 0) {
|
||||
throw new Error(`No API instances configured for type: ${type}`);
|
||||
}
|
||||
|
||||
if (options.minVersion) {
|
||||
instances = instances.filter(instance => {
|
||||
if (!instance.version) return false;
|
||||
return parseFloat(instance.version) >= parseFloat(options.minVersion);
|
||||
});
|
||||
if (instances.length === 0) {
|
||||
throw new Error(`No API instances configured for type: ${type} with minVersion: ${options.minVersion}`);
|
||||
}
|
||||
}
|
||||
|
||||
const maxTotalAttempts = instances.length * 2; // Allow some retries across instances
|
||||
let lastError = null;
|
||||
let instanceIndex = Math.floor(Math.random() * instances.length);
|
||||
|
||||
for (let attempt = 1; attempt <= maxTotalAttempts; attempt++) {
|
||||
const baseUrl = instances[instanceIndex % instances.length];
|
||||
const instance = instances[instanceIndex % instances.length];
|
||||
const baseUrl = typeof instance === 'string' ? instance : instance.url;
|
||||
const url = baseUrl.endsWith('/') ? `${baseUrl}${relativePath.substring(1)}` : `${baseUrl}${relativePath}`;
|
||||
|
||||
try {
|
||||
|
|
@ -644,7 +655,7 @@ export class LosslessAPI {
|
|||
const cached = await this.cache.get('mix', id);
|
||||
if (cached) return cached;
|
||||
|
||||
const response = await this.fetchWithRetry(`/mix/?id=${id}`, { type: 'api' });
|
||||
const response = await this.fetchWithRetry(`/mix/?id=${id}`, { type: 'api', minVersion: '2.3' });
|
||||
const data = await response.json();
|
||||
|
||||
const mixData = data.mix;
|
||||
|
|
@ -778,7 +789,7 @@ export class LosslessAPI {
|
|||
if (cached) return cached;
|
||||
|
||||
try {
|
||||
const response = await this.fetchWithRetry(`/artist/similar/?id=${artistId}`, { type: 'api' });
|
||||
const response = await this.fetchWithRetry(`/artist/similar/?id=${artistId}`, { type: 'api', minVersion: '2.3' });
|
||||
const data = await response.json();
|
||||
|
||||
// Handle various response structures
|
||||
|
|
@ -829,7 +840,7 @@ export class LosslessAPI {
|
|||
if (cached) return cached;
|
||||
|
||||
try {
|
||||
const response = await this.fetchWithRetry(`/album/similar/?id=${albumId}`, { type: 'api' });
|
||||
const response = await this.fetchWithRetry(`/album/similar/?id=${albumId}`, { type: 'api', minVersion: '2.3' });
|
||||
const data = await response.json();
|
||||
|
||||
const items = data.items || data.albums || data.data || (Array.isArray(data) ? data : []);
|
||||
|
|
@ -976,7 +987,7 @@ export class LosslessAPI {
|
|||
if (cached) return cached;
|
||||
|
||||
try {
|
||||
const response = await this.fetchWithRetry(`/recommendations/?id=${id}`, { type: 'api' });
|
||||
const response = await this.fetchWithRetry(`/recommendations/?id=${id}`, { type: 'api', minVersion: '2.4' });
|
||||
const json = await response.json();
|
||||
const data = json.data || json;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import {
|
|||
trackSkipTrack,
|
||||
trackToggleShuffle,
|
||||
trackToggleRepeat,
|
||||
trackSeek,
|
||||
trackAddToQueue,
|
||||
trackPlayNext,
|
||||
trackLikeTrack,
|
||||
|
|
@ -137,9 +136,6 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
|
|||
progressFill.style.width = `${(currentTime / duration) * 100}%`;
|
||||
currentTimeEl.textContent = formatTime(currentTime);
|
||||
|
||||
// Track seek milestones
|
||||
trackSeek(currentTime, duration);
|
||||
|
||||
// Log to history after 10 seconds of playback
|
||||
if (currentTime >= 10 && player.currentTrack && player.currentTrack.id !== historyLoggedTrackId) {
|
||||
historyLoggedTrackId = player.currentTrack.id;
|
||||
|
|
@ -1668,7 +1664,6 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
|
|||
api.getTrackRecommendations(clickedTrack.id).then((recs) => {
|
||||
if (recs && recs.length > 0) {
|
||||
player.addToQueue(recs);
|
||||
showNotification(`Added ${recs.length} recommendations to queue`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
190
js/storage.js
190
js/storage.js
|
|
@ -1,120 +1,128 @@
|
|||
//storage.js
|
||||
export const apiSettings = {
|
||||
STORAGE_KEY: 'monochrome-api-instances-v8',
|
||||
INSTANCES_URL: 'instances.json',
|
||||
STORAGE_KEY: 'monochrome-api-instances-v9',
|
||||
INSTANCES_URLS: [
|
||||
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
|
||||
'https://tidal-uptime.props-76styles.workers.dev/'
|
||||
],
|
||||
defaultInstances: { api: [], streaming: [] },
|
||||
instancesLoaded: false,
|
||||
_loadPromise: null,
|
||||
|
||||
async loadInstancesFromGitHub() {
|
||||
if (this.instancesLoaded) {
|
||||
return this.defaultInstances;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(this.INSTANCES_URL);
|
||||
if (!response.ok) throw new Error('Failed to fetch instances');
|
||||
if (this._loadPromise) {
|
||||
return this._loadPromise;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
this._loadPromise = (async () => {
|
||||
const cachedData = localStorage.getItem(this.STORAGE_KEY);
|
||||
if (cachedData) {
|
||||
try {
|
||||
const parsed = JSON.parse(cachedData);
|
||||
const now = Date.now();
|
||||
// Check if cached data is less than 15 minutes old
|
||||
if (parsed.timestamp && now - parsed.timestamp < 15 * 60 * 1000) {
|
||||
this.defaultInstances = parsed.data;
|
||||
this.instancesLoaded = true;
|
||||
this._loadPromise = null;
|
||||
return this.defaultInstances;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse cached instances:', e);
|
||||
}
|
||||
}
|
||||
|
||||
let data = null;
|
||||
let fetchError = null;
|
||||
|
||||
// Shuffle URLs to pick a random one first
|
||||
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
data = await response.json();
|
||||
break; // Success, exit loop
|
||||
} catch (error) {
|
||||
console.warn(`Failed to fetch from ${url}:`, error);
|
||||
fetchError = error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
console.error('Failed to load instances from all uptime APIs:', fetchError);
|
||||
this.defaultInstances = {
|
||||
api: [
|
||||
{ url: "https://eu-central.monochrome.tf", version: "2.4" },
|
||||
{ url: "https://us-west.monochrome.tf", version: "2.4" },
|
||||
{ url: "https://arran.monochrome.tf", version: "2.4" },
|
||||
{ url: "https://triton.squid.wtf", version: "2.4" },
|
||||
{ url: "https://api.monochrome.tf", version: "2.3" },
|
||||
{ url: "https://monochrome-api.samidy.com", version: "2.3" },
|
||||
{ url: "https://maus.qqdl.site", version: "2.2" },
|
||||
{ url: "https://vogel.qqdl.site", version: "2.2" },
|
||||
{ url: "https://katze.qqdl.site", version: "2.2" },
|
||||
{ url: "https://hund.qqdl.site", version: "2.2" },
|
||||
{ url: "https://tidal.kinoplus.online", version: "2.2" },
|
||||
{ url: "https://wolf.qqdl.site", version: "2.2" }
|
||||
],
|
||||
streaming: [
|
||||
{ url: "https://arran.monochrome.tf", version: "2.4" },
|
||||
{ url: "https://triton.squid.wtf", version: "2.4" },
|
||||
{ url: "https://api.monochrome.tf", version: "2.3" },
|
||||
{ url: "https://monochrome-api.samidy.com", version: "2.3" },
|
||||
{ url: "https://maus.qqdl.site", version: "2.2" },
|
||||
{ url: "https://vogel.qqdl.site", version: "2.2" },
|
||||
{ url: "https://katze.qqdl.site", version: "2.2" },
|
||||
{ url: "https://hund.qqdl.site", version: "2.2" },
|
||||
{ url: "https://wolf.qqdl.site", version: "2.2" }
|
||||
]
|
||||
};
|
||||
this.instancesLoaded = true;
|
||||
this._loadPromise = null;
|
||||
return this.defaultInstances;
|
||||
}
|
||||
|
||||
let groupedInstances = { api: [], streaming: [] };
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
// Legacy array format
|
||||
groupedInstances.api = [...data];
|
||||
groupedInstances.streaming = [...data];
|
||||
} else {
|
||||
// New object format or legacy object format
|
||||
if (data.api && Array.isArray(data.api)) {
|
||||
const isSimpleArray = data.api.length > 0 && typeof data.api[0] === 'string';
|
||||
if (isSimpleArray) {
|
||||
groupedInstances.api = [...data.api];
|
||||
} else {
|
||||
for (const [, config] of Object.entries(data.api)) {
|
||||
if (config.cors === false && Array.isArray(config.urls)) {
|
||||
groupedInstances.api.push(...config.urls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.api && Array.isArray(data.api)) {
|
||||
groupedInstances.api = data.api.filter(instance => !instance.url.includes('spotisaver.net'));
|
||||
}
|
||||
|
||||
if (data.streaming && Array.isArray(data.streaming)) {
|
||||
groupedInstances.streaming = [...data.streaming];
|
||||
} else if (groupedInstances.api.length > 0) {
|
||||
groupedInstances.streaming = [...groupedInstances.api];
|
||||
}
|
||||
if (data.streaming && Array.isArray(data.streaming)) {
|
||||
groupedInstances.streaming = data.streaming.filter(instance => !instance.url.includes('spotisaver.net'));
|
||||
} else if (groupedInstances.api.length > 0) {
|
||||
groupedInstances.streaming = [...groupedInstances.api];
|
||||
}
|
||||
|
||||
this.defaultInstances = groupedInstances;
|
||||
this.instancesLoaded = true;
|
||||
|
||||
try {
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify({
|
||||
timestamp: Date.now(),
|
||||
data: groupedInstances
|
||||
}));
|
||||
} catch (e) {
|
||||
console.warn('Failed to cache instances:', e);
|
||||
}
|
||||
|
||||
this._loadPromise = null;
|
||||
return groupedInstances;
|
||||
} catch (error) {
|
||||
console.error('Failed to load instances from GitHub:', error);
|
||||
this.defaultInstances = {
|
||||
api: [
|
||||
'https://eu-central.monochrome.tf',
|
||||
'https://us-west.monochrome.tf',
|
||||
'https://arran.monochrome.tf',
|
||||
'https://api.monochrome.tf',
|
||||
'https://triton.squid.wtf',
|
||||
'https://wolf.qqdl.site',
|
||||
'https://monochrome-api.samidy.com',
|
||||
'https://maus.qqdl.site',
|
||||
'https://tidal.kinoplus.online',
|
||||
'https://hund.qqdl.site',
|
||||
'https://vogel.qqdl.site',
|
||||
],
|
||||
streaming: [
|
||||
'https://arran.monochrome.tf',
|
||||
'https://triton.squid.wtf',
|
||||
'https://wolf.qqdl.site',
|
||||
'https://maus.qqdl.site',
|
||||
'https://vogel.qqdl.site',
|
||||
'https://katze.qqdl.site',
|
||||
'https://hund.qqdl.site',
|
||||
],
|
||||
};
|
||||
this.instancesLoaded = true;
|
||||
return this.defaultInstances;
|
||||
}
|
||||
})();
|
||||
|
||||
return this._loadPromise;
|
||||
},
|
||||
|
||||
async getInstances(type = 'api', _sortBySpeed = false) {
|
||||
let instancesObj;
|
||||
|
||||
const stored = localStorage.getItem(this.STORAGE_KEY);
|
||||
if (stored) {
|
||||
instancesObj = JSON.parse(stored);
|
||||
|
||||
// love it when local storage doesnt update
|
||||
if (instancesObj?.api?.length === 2) {
|
||||
const hasBinimum = instancesObj.api.some((url) => {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.hostname === 'tidal-api.binimum.org';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const hasSamidy = instancesObj.api.some((url) => {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.hostname === 'monochrome-api.samidy.com';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasBinimum && hasSamidy) {
|
||||
localStorage.removeItem(this.STORAGE_KEY);
|
||||
instancesObj = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!instancesObj) {
|
||||
instancesObj = await this.loadInstancesFromGitHub();
|
||||
}
|
||||
instancesObj = await this.loadInstancesFromGitHub();
|
||||
|
||||
const targetUrls = instancesObj[type] || instancesObj.api || [];
|
||||
if (targetUrls.length === 0) return [];
|
||||
|
|
|
|||
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -81,6 +81,7 @@
|
|||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
|
|
@ -1609,6 +1610,7 @@
|
|||
"integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@keyv/serialize": "^1.1.1"
|
||||
}
|
||||
|
|
@ -1650,6 +1652,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
|
@ -1693,6 +1696,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -3270,6 +3274,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.0.1.tgz",
|
||||
"integrity": "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
|
|
@ -3318,6 +3323,7 @@
|
|||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -3341,6 +3347,7 @@
|
|||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
|
|
@ -3638,6 +3645,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
|
|
@ -4675,6 +4683,7 @@
|
|||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
|
|
@ -7296,6 +7305,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
|
|
@ -7379,6 +7389,7 @@
|
|||
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
|
|
@ -8484,6 +8495,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.0.19",
|
||||
|
|
@ -8934,6 +8946,7 @@
|
|||
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
|
|
@ -9308,6 +9321,7 @@
|
|||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
|
@ -9744,6 +9758,7 @@
|
|||
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export default defineConfig(({ mode }) => {
|
|||
},
|
||||
],
|
||||
},
|
||||
includeAssets: ['instances.json', 'discord.html'],
|
||||
includeAssets: ['discord.html'],
|
||||
manifest: false, // Use existing public/manifest.json
|
||||
}),
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue