From 41f64672995afa8bdfcc143e1554c46cdf3177c0 Mon Sep 17 00:00:00 2001 From: Alan Brooks Date: Sun, 5 Apr 2026 23:45:46 -0400 Subject: [PATCH] wrong branch oops :sob: --- functions/album/[id].js | 45 +++++++++- functions/artist/[id].js | 43 ++++++++- functions/playlist/[id].js | 44 ++++++++- functions/track/[id].js | 43 ++++++++- js/storage.js | 177 +++++++++++++++++++++++++++++++++++-- public/instances.json | 21 ++++- 6 files changed, 354 insertions(+), 19 deletions(-) diff --git a/functions/album/[id].js b/functions/album/[id].js index 1b002f5..b75c0bf 100644 --- a/functions/album/[id].js +++ b/functions/album/[id].js @@ -47,11 +47,52 @@ class TidalAPI { class ServerAPI { constructor() { - this.apiInstances = ['https://hifi.geeked.wtf']; + this.INSTANCES_URLS = [ + 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', + 'https://tidal-uptime.props-76styles.workers.dev/', + ]; + this.apiInstances = null; } async getInstances() { - return this.apiInstances; + if (this.apiInstances) return this.apiInstances; + + 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) => !/\.squid\.wtf/i.test(url)); + return this.apiInstances; + } + + console.error('Failed to load instances from all uptime APIs'); + return [ + 'https://hifi.geeked.wtf', + 'https://eu-central.monochrome.tf', + 'https://us-west.monochrome.tf', + 'https://arran.monochrome.tf', + '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) { diff --git a/functions/artist/[id].js b/functions/artist/[id].js index a924773..1c62591 100644 --- a/functions/artist/[id].js +++ b/functions/artist/[id].js @@ -47,11 +47,50 @@ class TidalAPI { class ServerAPI { constructor() { - this.apiInstances = ['https://hifi.geeked.wtf']; + this.INSTANCES_URLS = [ + 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', + 'https://tidal-uptime.props-76styles.workers.dev/', + ]; + this.apiInstances = null; } async getInstances() { - return this.apiInstances; + if (this.apiInstances) return this.apiInstances; + + 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); + 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) { diff --git a/functions/playlist/[id].js b/functions/playlist/[id].js index 4e1dd21..15fe6f6 100644 --- a/functions/playlist/[id].js +++ b/functions/playlist/[id].js @@ -47,11 +47,51 @@ class TidalAPI { class ServerAPI { constructor() { - this.apiInstances = ['https://hifi.geeked.wtf']; + this.INSTANCES_URLS = [ + 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', + 'https://tidal-uptime.props-76styles.workers.dev/', + ]; + this.apiInstances = null; } async getInstances() { - return this.apiInstances; + if (this.apiInstances) return this.apiInstances; + + 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) => !/\.squid\.wtf/i.test(url)); + 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://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) { diff --git a/functions/track/[id].js b/functions/track/[id].js index a274b12..cf50d44 100644 --- a/functions/track/[id].js +++ b/functions/track/[id].js @@ -69,11 +69,50 @@ class TidalAPI { class ServerAPI { constructor() { - this.apiInstances = ['https://hifi.geeked.wtf']; + this.INSTANCES_URLS = [ + 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', + 'https://tidal-uptime.props-76styles.workers.dev/', + ]; + this.apiInstances = null; } async getInstances() { - return this.apiInstances; + if (this.apiInstances) return this.apiInstances; + + 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); + 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) { diff --git a/js/storage.js b/js/storage.js index d7470b5..222fa10 100644 --- a/js/storage.js +++ b/js/storage.js @@ -4,11 +4,11 @@ import { SVG_RIGHT_ARROW } from './icons'; export const apiSettings = { STORAGE_KEY: 'monochrome-api-instances-v9', - PINNED_INSTANCE: Object.freeze({ url: 'https://hifi.geeked.wtf', version: '2.7' }), - defaultInstances: { - api: [{ url: 'https://hifi.geeked.wtf', version: '2.7' }], - streaming: [{ url: 'https://hifi.geeked.wtf', version: '2.7' }], - }, + INSTANCES_URLS: [ + 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', + 'https://tidal-uptime.props-76styles.workers.dev/', + ], + defaultInstances: { api: [], streaming: [] }, userInstances: null, instancesLoaded: false, _loadPromise: null, @@ -29,13 +29,136 @@ export const apiSettings = { }, async loadInstancesFromGitHub() { - this.instancesLoaded = true; - return this.defaultInstances; + if (this.instancesLoaded) { + return this.defaultInstances; + } + + if (this._loadPromise) { + return this._loadPromise; + } + + 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; + + // Prefer first URL, only try others as fallback + const urls = [...this.INSTANCES_URLS]; + + 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://hifi.geeked.wtf', version: '2.7' }, + { url: 'https://eu-central.monochrome.tf', version: '2.7' }, + { url: 'https://us-west.monochrome.tf', version: '2.7' }, + { url: 'https://api.monochrome.tf', version: '2.5' }, + { url: 'https://monochrome-api.samidy.com', version: '2.3' }, + { url: 'https://maus.qqdl.site', version: '2.6' }, + { url: 'https://vogel.qqdl.site', version: '2.6' }, + { url: 'https://katze.qqdl.site', version: '2.6' }, + { url: 'https://hund.qqdl.site', version: '2.6' }, + { url: 'https://tidal.kinoplus.online', version: '2.2' }, + { url: 'https://wolf.qqdl.site', version: '2.2' }, + ], + streaming: [ + { url: 'https://hifi.geeked.wtf', version: '2.7' }, + { url: 'https://maus.qqdl.site', version: '2.6' }, + { url: 'https://vogel.qqdl.site', version: '2.6' }, + { url: 'https://katze.qqdl.site', version: '2.6' }, + { url: 'https://hund.qqdl.site', version: '2.6' }, + { url: 'https://wolf.qqdl.site', version: '2.6' }, + ], + }; + this.instancesLoaded = true; + this._loadPromise = null; + return this.defaultInstances; + } + + let groupedInstances = { api: [], streaming: [] }; + + const isBlockedInstance = (item) => { + const url = typeof item === 'string' ? item : item.url; + return url && /\.squid\.wtf/i.test(url); + }; + + if (data.api && Array.isArray(data.api)) { + groupedInstances.api = data.api.filter((item) => !isBlockedInstance(item)); + } + + if (data.streaming && Array.isArray(data.streaming)) { + groupedInstances.streaming = data.streaming.filter((item) => !isBlockedInstance(item)); + } 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; + })(); + + return this._loadPromise; }, async getInstances(type = 'api', _sortBySpeed = false) { - const instancesObj = await this.loadInstancesFromGitHub(); - return instancesObj[type] || instancesObj.api || []; + let instancesObj; + + instancesObj = await this.loadInstancesFromGitHub(); + const userInst = this._loadUserInstances(); + + const defaultUrls = instancesObj[type] || instancesObj.api || []; + const userUrls = userInst[type] || []; + + const combined = [ + ...userUrls.map((u) => (typeof u === 'string' ? { url: u, isUser: true } : { ...u, isUser: true })), + ...defaultUrls, + ]; + + if (combined.length === 0) return []; + + return combined; }, addUserInstance(type, url) { @@ -68,6 +191,42 @@ export const apiSettings = { this.instancesLoaded = false; this._loadPromise = null; localStorage.removeItem(this.STORAGE_KEY); + + const instances = await this.loadInstancesFromGitHub(); + + const shuffle = (array) => { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; + }; + + const prioritySort = (array) => { + const getUrl = (item) => (typeof item === 'string' ? item : item.url || ''); + const top = []; + const middle = []; + const bottom = []; + for (const item of array) { + const url = getUrl(item); + if (url.includes('hifi.geeked.wtf')) top.push(item); + else if (url.includes('.qqdl.site')) bottom.push(item); + else middle.push(item); + } + return [...top, ...shuffle(middle), ...shuffle(bottom)]; + }; + + if (instances.api && instances.api.length) { + instances.api = prioritySort([...instances.api]); + } + + if (instances.streaming && instances.streaming.length) { + instances.streaming = prioritySort([...instances.streaming]); + } + + this.saveInstances(instances); + + // Return API instances for the UI to render (default view) return this.getInstances('api'); }, saveInstances(instances, type) { diff --git a/public/instances.json b/public/instances.json index b9fce85..d5a8bb9 100644 --- a/public/instances.json +++ b/public/instances.json @@ -1,8 +1,25 @@ { "api": [ - "https://hifi.geeked.wtf" + "https://eu-central.monochrome.tf", + "https://us-west.monochrome.tf", + "https://arran.monochrome.tf", + "https://api.monochrome.tf/", + "https://monochrome-api.samidy.com", + "https://triton.squid.wtf", + "https://wolf.qqdl.site", + "https://maus.qqdl.site", + "https://vogel.qqdl.site", + "https://hund.qqdl.site", + "https://tidal.kinoplus.online" ], "streaming": [ - "https://hifi.geeked.wtf" + "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", + "https://hifi.p1nkhamster.xyz/" ] }