This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* API Client - Frontend API wrapper for NodeCast TV
|
||||
*/
|
||||
|
||||
const API = {
|
||||
/**
|
||||
* Make API request
|
||||
*/
|
||||
async request(method, endpoint, data = null) {
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
// Add authentication token if available
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (token) {
|
||||
options.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
const response = await fetch(`/api${endpoint}`, options);
|
||||
|
||||
let result;
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
result = await response.json();
|
||||
} else {
|
||||
const text = await response.text();
|
||||
result = { error: text || 'API request failed' };
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
// If unauthorized, redirect to login
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem('authToken');
|
||||
window.location.href = '/login.html';
|
||||
return;
|
||||
}
|
||||
throw new Error(result.error || `Server responded with ${response.status}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// Sources
|
||||
sources: {
|
||||
getAll: () => API.request('GET', '/sources'),
|
||||
getByType: (type) => API.request('GET', `/sources/type/${type}`),
|
||||
getById: (id) => API.request('GET', `/sources/${id}`),
|
||||
create: (data) => API.request('POST', '/sources', data),
|
||||
update: (id, data) => API.request('PUT', `/sources/${id}`, data),
|
||||
delete: (id) => API.request('DELETE', `/sources/${id}`),
|
||||
toggle: (id) => API.request('POST', `/sources/${id}/toggle`),
|
||||
test: (id) => API.request('POST', `/sources/${id}/test`),
|
||||
sync: (id) => API.request('POST', `/sources/${id}/sync`), // Manual sync
|
||||
getStatus: () => API.request('GET', '/sources/status'), // Get all statuses
|
||||
estimate: (id) => API.request('GET', `/sources/${id}/estimate`), // Estimate M3U size
|
||||
estimateByUrl: (url, type) => API.request('POST', '/sources/estimate', { url, type }), // Estimate by URL (before creation)
|
||||
},
|
||||
|
||||
// Channels (hidden items)
|
||||
channels: {
|
||||
getHidden: (sourceId = null) => API.request('GET', `/channels/hidden${sourceId ? `?sourceId=${sourceId}` : ''}`),
|
||||
hide: (sourceId, itemType, itemId) => API.request('POST', '/channels/hide', { sourceId, itemType, itemId }),
|
||||
show: (sourceId, itemType, itemId) => API.request('POST', '/channels/show', { sourceId, itemType, itemId }),
|
||||
isHidden: (sourceId, itemType, itemId) => API.request('GET', `/channels/hidden/check?sourceId=${sourceId}&itemType=${itemType}&itemId=${itemId}`),
|
||||
bulkHide: (items) => API.request('POST', '/channels/hide/bulk', { items }),
|
||||
bulkShow: (items) => API.request('POST', '/channels/show/bulk', { items }),
|
||||
// Fast bulk operations - single SQL statement
|
||||
showAll: (sourceId, contentType) => API.request('POST', '/channels/show/all', { sourceId, contentType }),
|
||||
hideAll: (sourceId, contentType) => API.request('POST', '/channels/hide/all', { sourceId, contentType })
|
||||
},
|
||||
|
||||
// Favorites
|
||||
favorites: {
|
||||
getAll: (sourceId = null, itemType = null) => {
|
||||
let url = '/favorites';
|
||||
const params = [];
|
||||
if (sourceId) params.push(`sourceId=${sourceId}`);
|
||||
if (itemType) params.push(`itemType=${itemType}`);
|
||||
if (params.length) url += '?' + params.join('&');
|
||||
return API.request('GET', url);
|
||||
},
|
||||
add: (sourceId, itemId, itemType = 'channel') =>
|
||||
API.request('POST', '/favorites', { sourceId, itemId, itemType }),
|
||||
remove: (sourceId, itemId, itemType = 'channel') =>
|
||||
API.request('DELETE', '/favorites', { sourceId, itemId, itemType }),
|
||||
check: (sourceId, itemId, itemType = 'channel') =>
|
||||
API.request('GET', `/favorites/check?sourceId=${sourceId}&itemId=${itemId}&itemType=${itemType}`)
|
||||
},
|
||||
|
||||
// Proxy
|
||||
proxy: {
|
||||
// Xtream
|
||||
xtream: {
|
||||
auth: (sourceId) => API.request('GET', `/proxy/xtream/${sourceId}/auth`),
|
||||
liveCategories: (sourceId, options = {}) => {
|
||||
const params = options.includeHidden ? '?includeHidden=true' : '';
|
||||
return API.request('GET', `/proxy/xtream/${sourceId}/live_categories${params}`);
|
||||
},
|
||||
liveStreams: (sourceId, categoryId = null, options = {}) => {
|
||||
const params = [];
|
||||
if (categoryId) params.push(`category_id=${categoryId}`);
|
||||
if (options.includeHidden) params.push('includeHidden=true');
|
||||
const query = params.length ? `?${params.join('&')}` : '';
|
||||
return API.request('GET', `/proxy/xtream/${sourceId}/live_streams${query}`);
|
||||
},
|
||||
vodCategories: (sourceId, options = {}) => {
|
||||
const params = options.includeHidden ? '?includeHidden=true' : '';
|
||||
return API.request('GET', `/proxy/xtream/${sourceId}/vod_categories${params}`);
|
||||
},
|
||||
vodStreams: (sourceId, categoryId = null, options = {}) => {
|
||||
const params = [];
|
||||
if (categoryId) params.push(`category_id=${categoryId}`);
|
||||
if (options.includeHidden) params.push('includeHidden=true');
|
||||
const query = params.length ? `?${params.join('&')}` : '';
|
||||
return API.request('GET', `/proxy/xtream/${sourceId}/vod_streams${query}`);
|
||||
},
|
||||
seriesCategories: (sourceId, options = {}) => {
|
||||
const params = options.includeHidden ? '?includeHidden=true' : '';
|
||||
return API.request('GET', `/proxy/xtream/${sourceId}/series_categories${params}`);
|
||||
},
|
||||
series: (sourceId, categoryId = null, options = {}) => {
|
||||
const params = [];
|
||||
if (categoryId) params.push(`category_id=${categoryId}`);
|
||||
if (options.includeHidden) params.push('includeHidden=true');
|
||||
const query = params.length ? `?${params.join('&')}` : '';
|
||||
return API.request('GET', `/proxy/xtream/${sourceId}/series${query}`);
|
||||
},
|
||||
seriesInfo: (sourceId, seriesId) =>
|
||||
API.request('GET', `/proxy/xtream/${sourceId}/series_info?series_id=${seriesId}`),
|
||||
shortEpg: (sourceId, streamId) => API.request('GET', `/proxy/xtream/${sourceId}/short_epg?stream_id=${streamId}`),
|
||||
getStreamUrl: (sourceId, streamId, type = 'live', container = 'ts') =>
|
||||
API.request('GET', `/proxy/xtream/${sourceId}/stream/${streamId}/${type}?container=${container}`)
|
||||
},
|
||||
|
||||
// EPG
|
||||
epg: {
|
||||
get: (sourceId) => API.request('GET', `/proxy/epg/${sourceId}`),
|
||||
getForChannels: (sourceId, channelIds) => API.request('POST', `/proxy/epg/${sourceId}/channels`, { channelIds })
|
||||
},
|
||||
|
||||
// Cache management
|
||||
cache: {
|
||||
clear: (sourceId) => API.request('DELETE', `/proxy/cache/${sourceId}`)
|
||||
}
|
||||
},
|
||||
|
||||
// Settings
|
||||
settings: {
|
||||
get: () => API.request('GET', '/settings'),
|
||||
update: (data) => API.request('PUT', '/settings', data),
|
||||
reset: () => API.request('DELETE', '/settings'),
|
||||
getDefaults: () => API.request('GET', '/settings/defaults')
|
||||
},
|
||||
|
||||
// Users (admin only)
|
||||
users: {
|
||||
getAll: () => API.request('GET', '/auth/users'),
|
||||
create: (data) => API.request('POST', '/auth/users', data),
|
||||
update: (id, data) => API.request('PUT', `/auth/users/${id}`, data),
|
||||
delete: (id) => API.request('DELETE', `/auth/users/${id}`)
|
||||
}
|
||||
};
|
||||
|
||||
// Make API available globally
|
||||
window.API = API;
|
||||
Reference in New Issue
Block a user