mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
feat: implement URL probing functionality and enhance time formatting in EPG component
This commit is contained in:
1 parent
826d8d24b1
commit
04e8709d7b
8 files changed
+198
-62
No files matched your search
@@ -44,17 +44,15 @@
|
||||
"serve": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"commands": [
|
||||
"nx run web:serve",
|
||||
"nx run electron-backend:serve-electron"
|
||||
]
|
||||
"command": "nx run electron-backend:serve-electron"
|
||||
}
|
||||
},
|
||||
"serve-electron": {
|
||||
"executor": "nx-electron:execute",
|
||||
"options": {
|
||||
"buildTarget": "electron-backend:build",
|
||||
"args": ["--remote-debugging-port=9222"]
|
||||
"waitUntilTargets": ["web:serve"],
|
||||
"remoteDebuggingPort": 9222
|
||||
}
|
||||
},
|
||||
"package": {
|
||||
|
||||
@@ -254,6 +254,8 @@ contextBridge.exposeInMainWorld('electron', {
|
||||
}) => ipcRenderer.invoke('XTREAM_REQUEST', payload),
|
||||
xtreamCancelSession: (sessionId: string) =>
|
||||
ipcRenderer.invoke('XTREAM_CANCEL_SESSION', sessionId),
|
||||
xtreamProbeUrl: (url: string, method?: 'GET' | 'HEAD') =>
|
||||
ipcRenderer.invoke('XTREAM_PROBE_URL', { url, method }),
|
||||
refreshPlaylist: (payload: PlaylistRefreshPayload) =>
|
||||
ipcRenderer.invoke('PLAYLIST:REFRESH', payload),
|
||||
cancelPlaylistRefresh: (operationId: string) =>
|
||||
|
||||
@@ -10,6 +10,10 @@ export default class App {
|
||||
static application: Electron.App;
|
||||
static BrowserWindow;
|
||||
|
||||
private static shouldOpenDevTools() {
|
||||
return process.env.ELECTRON_OPEN_DEVTOOLS === '1';
|
||||
}
|
||||
|
||||
public static isDevelopmentMode() {
|
||||
// First check ELECTRON_IS_DEV environment variable (used by E2E tests)
|
||||
// This allows E2E tests to run in production mode without packaging
|
||||
@@ -161,7 +165,9 @@ export default class App {
|
||||
// load the index.html of the app.
|
||||
if (App.isDevelopmentMode()) {
|
||||
App.mainWindow.loadURL(`http://localhost:${rendererAppPort}`);
|
||||
App.mainWindow.webContents.openDevTools();
|
||||
if (App.shouldOpenDevTools()) {
|
||||
App.mainWindow.webContents.openDevTools();
|
||||
}
|
||||
} else {
|
||||
App.mainWindow.loadFile(
|
||||
join(__dirname, '..', rendererAppName, 'index.html')
|
||||
|
||||
@@ -44,6 +44,23 @@ function getRecentlyAddedContentTypes(
|
||||
return ['movie', 'series'];
|
||||
}
|
||||
|
||||
function selectContentFields() {
|
||||
return {
|
||||
id: schema.content.id,
|
||||
category_id: schema.content.categoryId,
|
||||
title: schema.content.title,
|
||||
rating: schema.content.rating,
|
||||
added: schema.content.added,
|
||||
poster_url: schema.content.posterUrl,
|
||||
epg_channel_id: schema.content.epgChannelId,
|
||||
tv_archive: schema.content.tvArchive,
|
||||
tv_archive_duration: schema.content.tvArchiveDuration,
|
||||
direct_source: schema.content.directSource,
|
||||
xtream_id: schema.content.xtreamId,
|
||||
type: schema.content.type,
|
||||
};
|
||||
}
|
||||
|
||||
export async function hasContent(
|
||||
db: AppDatabase,
|
||||
playlistId: string,
|
||||
@@ -72,16 +89,7 @@ export async function getContent(
|
||||
type: 'live' | 'movie' | 'series'
|
||||
) {
|
||||
const baseQuery = db
|
||||
.select({
|
||||
id: schema.content.id,
|
||||
category_id: schema.content.categoryId,
|
||||
title: schema.content.title,
|
||||
rating: schema.content.rating,
|
||||
added: schema.content.added,
|
||||
poster_url: schema.content.posterUrl,
|
||||
xtream_id: schema.content.xtreamId,
|
||||
type: schema.content.type,
|
||||
})
|
||||
.select(selectContentFields())
|
||||
.from(schema.content)
|
||||
.innerJoin(
|
||||
schema.categories,
|
||||
@@ -112,15 +120,8 @@ export async function getGlobalRecentlyAdded(
|
||||
|
||||
return db
|
||||
.select({
|
||||
id: schema.content.id,
|
||||
category_id: schema.content.categoryId,
|
||||
title: schema.content.title,
|
||||
rating: schema.content.rating,
|
||||
added: schema.content.added,
|
||||
...selectContentFields(),
|
||||
added_at: schema.content.added,
|
||||
poster_url: schema.content.posterUrl,
|
||||
xtream_id: schema.content.xtreamId,
|
||||
type: schema.content.type,
|
||||
playlist_id: schema.playlists.id,
|
||||
playlist_name: schema.playlists.name,
|
||||
})
|
||||
@@ -150,6 +151,10 @@ type XtreamContentValue = {
|
||||
rating: string;
|
||||
added: string;
|
||||
posterUrl: string;
|
||||
epgChannelId?: string | null;
|
||||
tvArchive?: number | null;
|
||||
tvArchiveDuration?: number | null;
|
||||
directSource?: string | null;
|
||||
xtreamId: number;
|
||||
type: 'live' | 'movie' | 'series';
|
||||
};
|
||||
@@ -161,12 +166,16 @@ type XtreamContentSource = Record<string, unknown> & {
|
||||
last_modified?: string;
|
||||
added?: string;
|
||||
stream_icon?: string;
|
||||
poster?: string;
|
||||
cover?: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
series_id?: string | number;
|
||||
stream_id?: string | number;
|
||||
poster?: string;
|
||||
cover?: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
epg_channel_id?: string;
|
||||
tv_archive?: string | number;
|
||||
tv_archive_duration?: string | number;
|
||||
direct_source?: string;
|
||||
series_id?: string | number;
|
||||
stream_id?: string | number;
|
||||
};
|
||||
|
||||
function toXtreamContentValue(
|
||||
@@ -205,6 +214,25 @@ function toXtreamContentValue(
|
||||
posterUrl: String(
|
||||
source.stream_icon || source.poster || source.cover || ''
|
||||
),
|
||||
epgChannelId:
|
||||
type === 'live'
|
||||
? String(source.epg_channel_id ?? '').trim() || null
|
||||
: null,
|
||||
tvArchive:
|
||||
type === 'live'
|
||||
? Number.parseInt(String(source.tv_archive ?? '0'), 10) || 0
|
||||
: null,
|
||||
tvArchiveDuration:
|
||||
type === 'live'
|
||||
? Number.parseInt(
|
||||
String(source.tv_archive_duration ?? '0'),
|
||||
10
|
||||
) || 0
|
||||
: null,
|
||||
directSource:
|
||||
type === 'live'
|
||||
? String(source.direct_source ?? '').trim() || null
|
||||
: null,
|
||||
xtreamId:
|
||||
type === 'series'
|
||||
? parseInt(String(source.series_id || '0'))
|
||||
@@ -343,16 +371,7 @@ export async function getContentByXtreamId(
|
||||
playlistId: string
|
||||
) {
|
||||
const result = await db
|
||||
.select({
|
||||
id: schema.content.id,
|
||||
category_id: schema.content.categoryId,
|
||||
title: schema.content.title,
|
||||
rating: schema.content.rating,
|
||||
added: schema.content.added,
|
||||
poster_url: schema.content.posterUrl,
|
||||
xtream_id: schema.content.xtreamId,
|
||||
type: schema.content.type,
|
||||
})
|
||||
.select(selectContentFields())
|
||||
.from(schema.content)
|
||||
.innerJoin(
|
||||
schema.categories,
|
||||
@@ -397,16 +416,7 @@ export async function searchContent(
|
||||
}
|
||||
|
||||
const candidates = await db
|
||||
.select({
|
||||
id: schema.content.id,
|
||||
category_id: schema.content.categoryId,
|
||||
title: schema.content.title,
|
||||
rating: schema.content.rating,
|
||||
added: schema.content.added,
|
||||
poster_url: schema.content.posterUrl,
|
||||
xtream_id: schema.content.xtreamId,
|
||||
type: schema.content.type,
|
||||
})
|
||||
.select(selectContentFields())
|
||||
.from(schema.content)
|
||||
.innerJoin(
|
||||
schema.categories,
|
||||
@@ -449,14 +459,7 @@ export async function globalSearch(
|
||||
|
||||
const candidates = await db
|
||||
.select({
|
||||
id: schema.content.id,
|
||||
category_id: schema.content.categoryId,
|
||||
title: schema.content.title,
|
||||
rating: schema.content.rating,
|
||||
added: schema.content.added,
|
||||
poster_url: schema.content.posterUrl,
|
||||
xtream_id: schema.content.xtreamId,
|
||||
type: schema.content.type,
|
||||
...selectContentFields(),
|
||||
playlist_id: schema.categories.playlistId,
|
||||
playlist_name: schema.playlists.name,
|
||||
})
|
||||
|
||||
@@ -245,3 +245,46 @@ type ActiveXtreamRequest = {
|
||||
};
|
||||
|
||||
const activeXtreamRequests = new Map<string, ActiveXtreamRequest>();
|
||||
|
||||
ipcMain.handle(
|
||||
'XTREAM_PROBE_URL',
|
||||
async (
|
||||
_event,
|
||||
payload: {
|
||||
url: string;
|
||||
method?: 'GET' | 'HEAD';
|
||||
}
|
||||
) => {
|
||||
const config: AxiosRequestConfig = {
|
||||
method: payload.method ?? 'HEAD',
|
||||
url: payload.url,
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
},
|
||||
timeout: 10000,
|
||||
maxRedirects: 5,
|
||||
validateStatus: () => true,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await axios(config);
|
||||
return {
|
||||
status: response.status,
|
||||
url: payload.url,
|
||||
};
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response) {
|
||||
return {
|
||||
status: error.response.status,
|
||||
url: payload.url,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 0,
|
||||
url: payload.url,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
Reference in new issue
Block a user