feat(APIs): custom api instances

This commit is contained in:
Samidy 2026-03-14 02:58:22 +03:00
parent e190519040
commit 99debbb7b2
3 changed files with 109 additions and 10 deletions

View file

@ -2823,8 +2823,32 @@ export function initializeSettings(scrobbler, player, api, ui) {
if (!button) return;
const li = button.closest('li');
const index = parseInt(li.dataset.index, 10);
const type = li.dataset.type || 'api'; // Default to api if not present
const type = button.dataset.type || li?.dataset.type || 'api';
if (button.classList.contains('add-instance')) {
const url = prompt(`Enter custom ${type.toUpperCase()} instance URL (e.g. https://my-instance.com):`);
if (url && url.trim()) {
let formattedUrl = url.trim();
if (!formattedUrl.startsWith('http')) {
formattedUrl = 'https://' + formattedUrl;
}
api.settings.addUserInstance(type, formattedUrl);
ui.renderApiSettings();
}
return;
}
if (button.classList.contains('delete-instance')) {
const url = li.dataset.url;
if (url && confirm(`Delete custom instance ${url}?`)) {
api.settings.removeUserInstance(type, url);
ui.renderApiSettings();
}
return;
}
const index = parseInt(li?.dataset.index, 10);
if (isNaN(index)) return;
const instances = await api.settings.getInstances(type);

View file

@ -6,9 +6,25 @@ export const apiSettings = {
'https://tidal-uptime.props-76styles.workers.dev/',
],
defaultInstances: { api: [], streaming: [] },
userInstances: null,
instancesLoaded: false,
_loadPromise: null,
_loadUserInstances() {
if (this.userInstances) return this.userInstances;
try {
const stored = localStorage.getItem('monochrome-user-api-instances-v1');
this.userInstances = stored ? JSON.parse(stored) : { api: [], streaming: [] };
} catch {
this.userInstances = { api: [], streaming: [] };
}
return this.userInstances;
},
_saveUserInstances() {
localStorage.setItem('monochrome-user-api-instances-v1', JSON.stringify(this.userInstances));
},
async loadInstancesFromGitHub() {
if (this.instancesLoaded) {
return this.defaultInstances;
@ -126,11 +142,42 @@ export const apiSettings = {
let instancesObj;
instancesObj = await this.loadInstancesFromGitHub();
const userInst = this._loadUserInstances();
const targetUrls = instancesObj[type] || instancesObj.api || [];
if (targetUrls.length === 0) return [];
const defaultUrls = instancesObj[type] || instancesObj.api || [];
const userUrls = userInst[type] || [];
return targetUrls;
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) {
const userInst = this._loadUserInstances();
if (!userInst[type]) userInst[type] = [];
if (!userInst[type].some((u) => (typeof u === 'string' ? u === url : u.url === url))) {
userInst[type].push({ url, isUser: true, version: 'custom' });
this._saveUserInstances();
return true;
}
return false;
},
removeUserInstance(type, url) {
const userInst = this._loadUserInstances();
if (!userInst[type]) return false;
const initialLength = userInst[type].length;
userInst[type] = userInst[type].filter((u) => (typeof u === 'string' ? u !== url : u.url !== url));
if (userInst[type].length !== initialLength) {
this._saveUserInstances();
return true;
}
return false;
},
async refreshInstances() {
@ -160,9 +207,23 @@ export const apiSettings = {
saveInstances(instances, type) {
if (type) {
try {
this._loadUserInstances();
const userInst = instances.filter((i) => i.isUser);
const defaultInst = instances.filter((i) => !i.isUser);
this.userInstances[type] = userInst;
this._saveUserInstances();
const stored = localStorage.getItem(this.STORAGE_KEY);
let fullObj = stored ? JSON.parse(stored) : { api: [], streaming: [] };
fullObj[type] = instances;
if (fullObj && fullObj.data) {
fullObj.data[type] = defaultInst;
} else {
if (!fullObj) fullObj = { api: [], streaming: [] };
fullObj[type] = defaultInst;
}
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(fullObj));
} catch (e) {
console.error('Failed to save instances:', e);

View file

@ -4797,18 +4797,29 @@ export class UIRenderer {
? instance.name || instance.displayName || instance.id || instanceUrl
: instanceUrl;
const instanceVersion = isObject && instance.version ? String(instance.version) : '';
const isUser = isObject && instance.isUser;
const safeName = escapeHtml(instanceName || 'Unknown instance');
const safeUrl = escapeHtml(instanceUrl || '');
const safeVersion = escapeHtml(instanceVersion);
return `
<li data-index="${index}" data-type="${type}">
<li data-index="${index}" data-type="${type}" data-url="${safeUrl}">
<div style="flex: 1; min-width: 0;">
<div class="instance-url">${safeName}</div>
<div class="instance-url">${safeName} ${isUser ? '<span style="font-size: 0.6rem; opacity: 0.7; background: var(--muted); padding: 1px 4px; border-radius: 3px; margin-left: 4px; vertical-align: middle;">U</span>' : ''}</div>
${safeUrl && safeUrl !== safeName ? `<div style="font-size: 0.8rem; color: var(--muted-foreground); margin-top: 0.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${safeUrl}</div>` : ''}
${safeVersion ? `<div style="font-size: 0.75rem; color: var(--muted-foreground); margin-top: 0.1rem;">v${safeVersion}</div>` : ''}
</div>
<div class="controls">
${
isUser
? `
<button class="delete-instance" title="Delete Instance">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg>
</button>`
: ''
}
<button class="move-up" title="Move Up" ${index === 0 ? 'disabled' : ''}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 19V5M5 12l7-7 7 7"/>
@ -4826,8 +4837,11 @@ export class UIRenderer {
.join('');
return `
<li class="group-header" style="font-weight: bold; padding: 1rem 0 0.5rem; background: transparent; border: none; pointer-events: none;">
${type === 'api' ? 'API Instances' : 'Streaming Instances'}
<li class="group-header" style="display: flex; justify-content: space-between; align-items: center; font-weight: bold; padding: 1rem 0 0.5rem; background: transparent; border: none;">
<span>${type === 'api' ? 'API Instances' : 'Streaming Instances'}</span>
<button class="add-instance" data-type="${type}" title="Add Custom Instance" style="background: var(--primary); color: var(--primary-foreground); border: none; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; cursor: pointer; pointer-events: auto;">
Add
</button>
</li>
${listHtml}
`;