mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
refactor(electron): remove unused preload, HTML, and server files
This commit is contained in:
1 parent
e639353997
commit
4cd7ab0afe
8 files changed
-1309
No files matched your search
-639
@@ -1,639 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import { app, BrowserWindow, ipcMain, session } from 'electron';
|
||||
import { promises as fsPromises } from 'fs';
|
||||
import { parse } from 'iptv-playlist-parser';
|
||||
import { Channel } from '../shared/channel.interface';
|
||||
import {
|
||||
AUTO_UPDATE_PLAYLISTS,
|
||||
AUTO_UPDATE_PLAYLISTS_RESPONSE,
|
||||
CHANNEL_SET_USER_AGENT,
|
||||
DELETE_ALL_PLAYLISTS,
|
||||
EPG_ERROR,
|
||||
EPG_FETCH,
|
||||
EPG_FETCH_DONE,
|
||||
EPG_FORCE_FETCH,
|
||||
EPG_GET_CHANNELS,
|
||||
EPG_GET_CHANNELS_BY_RANGE,
|
||||
EPG_GET_CHANNELS_BY_RANGE_RESPONSE,
|
||||
EPG_GET_CHANNELS_DONE,
|
||||
EPG_GET_PROGRAM,
|
||||
EPG_GET_PROGRAM_DONE,
|
||||
ERROR,
|
||||
IS_PLAYLISTS_MIGRATION_POSSIBLE,
|
||||
IS_PLAYLISTS_MIGRATION_POSSIBLE_RESPONSE,
|
||||
MIGRATE_PLAYLISTS,
|
||||
MIGRATE_PLAYLISTS_RESPONSE,
|
||||
OPEN_FILE,
|
||||
OPEN_MPV_PLAYER,
|
||||
OPEN_VLC_PLAYER,
|
||||
PLAYLIST_PARSE_BY_URL,
|
||||
PLAYLIST_PARSE_RESPONSE,
|
||||
PLAYLIST_UPDATE,
|
||||
PLAYLIST_UPDATE_RESPONSE,
|
||||
SET_MPV_PLAYER_PATH,
|
||||
SET_VLC_PLAYER_PATH,
|
||||
SETTINGS_UPDATE,
|
||||
STALKER_REQUEST,
|
||||
STALKER_RESPONSE,
|
||||
XTREAM_REQUEST,
|
||||
XTREAM_RESPONSE,
|
||||
} from '../shared/ipc-commands';
|
||||
import { Playlist } from '../shared/playlist.interface';
|
||||
import { createPlaylistObject } from '../shared/playlist.utils';
|
||||
import { ParsedPlaylist } from '../src/typings.d';
|
||||
import { Server } from './server';
|
||||
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
const child_process = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const mpvAPI = require('node-mpv');
|
||||
|
||||
/** @deprecated - used only for migration */
|
||||
const Nedb = require('nedb-promises');
|
||||
|
||||
/** @deprecated - used only for migration */
|
||||
const userData = process.env['e2e']
|
||||
? process.cwd() + '/e2e'
|
||||
: app.getPath('userData');
|
||||
|
||||
/** @deprecated - used only for migration */
|
||||
const dbPath = `${userData}/db/data.db`;
|
||||
/** @deprecated - used only for migration */
|
||||
const db = new Nedb({
|
||||
filename: dbPath,
|
||||
autoload: true,
|
||||
});
|
||||
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
const MPV_PLAYER_PATH = 'MPV_PLAYER_PATH';
|
||||
const VLC_PLAYER_PATH = 'VLC_PLAYER_PATH';
|
||||
|
||||
export class Api {
|
||||
/** Instance of the main application window */
|
||||
mainWindow: BrowserWindow;
|
||||
|
||||
/** Default user agent stored as a fallback value */
|
||||
defaultUserAgent: string;
|
||||
|
||||
/** Default referer url value */
|
||||
defaultReferer: string;
|
||||
|
||||
/** Instance of the epg browser window */
|
||||
workerWindow: BrowserWindow;
|
||||
|
||||
store;
|
||||
|
||||
mpv;
|
||||
|
||||
settings;
|
||||
|
||||
server;
|
||||
|
||||
constructor(store) {
|
||||
this.server = new Server(this);
|
||||
this.store = store;
|
||||
this.mpv = this.createMpvInstance();
|
||||
this.mpv
|
||||
.on('quit', () => (this.mpv = null))
|
||||
.on('crash', () => (this.mpv = null));
|
||||
|
||||
ipcMain
|
||||
.on(PLAYLIST_PARSE_BY_URL, (event, args) => {
|
||||
try {
|
||||
axios
|
||||
.get(args.url, { httpsAgent: agent })
|
||||
.then((result) => {
|
||||
const parsedPlaylist = this.parsePlaylist(
|
||||
result.data
|
||||
);
|
||||
const playlistObject = createPlaylistObject(
|
||||
args.title,
|
||||
parsedPlaylist,
|
||||
args.url,
|
||||
'URL'
|
||||
);
|
||||
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
|
||||
payload: playlistObject,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
event.sender.send(ERROR, {
|
||||
message: err.response.statusText,
|
||||
status: err.response.status,
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
event.sender.send(ERROR, {
|
||||
message: err.response.statusText,
|
||||
status: err.response.status,
|
||||
});
|
||||
}
|
||||
})
|
||||
.on(OPEN_FILE, (event, args) => {
|
||||
fs.readFile(
|
||||
args.filePath,
|
||||
'utf-8',
|
||||
(err: NodeJS.ErrnoException, data: string) => {
|
||||
if (err) {
|
||||
console.log(
|
||||
'An error ocurred reading the file :' +
|
||||
err.message
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedPlaylist = this.parsePlaylist(data);
|
||||
const playlistObject = createPlaylistObject(
|
||||
args.fileName,
|
||||
parsedPlaylist,
|
||||
args.filePath,
|
||||
'FILE'
|
||||
);
|
||||
|
||||
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
|
||||
payload: playlistObject,
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
.on(
|
||||
PLAYLIST_UPDATE,
|
||||
(
|
||||
event,
|
||||
args: {
|
||||
id: string;
|
||||
title: string;
|
||||
filePath?: string;
|
||||
url?: string;
|
||||
}
|
||||
) => {
|
||||
if (args.filePath && args.id) {
|
||||
this.fetchPlaylistByFilePath(args, event);
|
||||
} else if (args.url && args.id) {
|
||||
this.fetchPlaylistByUrl(args, event);
|
||||
}
|
||||
}
|
||||
)
|
||||
.on(
|
||||
CHANNEL_SET_USER_AGENT,
|
||||
(_event, args: { userAgent: string; referer?: string }) => {
|
||||
if (args.userAgent || args.referer !== undefined) {
|
||||
this.setUserAgent(
|
||||
args.userAgent || 'localhost',
|
||||
args.referer || 'localhost'
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
.on(IS_PLAYLISTS_MIGRATION_POSSIBLE, (event) => {
|
||||
db.count({
|
||||
type: { $exists: false },
|
||||
}).then((count: number) => {
|
||||
event.sender.send(
|
||||
IS_PLAYLISTS_MIGRATION_POSSIBLE_RESPONSE,
|
||||
{
|
||||
result: count > 0,
|
||||
message:
|
||||
count > 0
|
||||
? `${count} playlists were found, which can be migrated from the database used in the last version of the application.`
|
||||
: 'No playlists for migration',
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
.on(
|
||||
AUTO_UPDATE_PLAYLISTS,
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
async (event, playlists: Partial<Playlist>[]) => {
|
||||
const results: any[] = [];
|
||||
let playlist: any;
|
||||
for (const element of playlists) {
|
||||
if (element.url && element._id) {
|
||||
playlist = await this.fetchPlaylistByUrl({
|
||||
id: element._id,
|
||||
title: element.title || '',
|
||||
url: element.url,
|
||||
});
|
||||
results.push(playlist);
|
||||
} else if (element.filePath && element._id) {
|
||||
playlist = await this.fetchPlaylistByFilePath({
|
||||
id: element._id,
|
||||
title: element.title || '',
|
||||
filePath: element.filePath,
|
||||
});
|
||||
results.push(playlist);
|
||||
}
|
||||
}
|
||||
event.sender.send(
|
||||
AUTO_UPDATE_PLAYLISTS_RESPONSE,
|
||||
results.filter((item) => item !== undefined)
|
||||
);
|
||||
}
|
||||
)
|
||||
.on(MIGRATE_PLAYLISTS, (event) => {
|
||||
this.getAllPlaylists().then((playlists) => {
|
||||
event.sender.send(MIGRATE_PLAYLISTS_RESPONSE, {
|
||||
payload: playlists,
|
||||
});
|
||||
});
|
||||
})
|
||||
.on(DELETE_ALL_PLAYLISTS, (event) => {
|
||||
this.removeAllPlaylists(event);
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.on(OPEN_MPV_PLAYER, async (event, { url }) => {
|
||||
try {
|
||||
if (this.mpv === null) {
|
||||
this.mpv = this.createMpvInstance();
|
||||
}
|
||||
if (this.mpv.isRunning()) {
|
||||
await this.mpv.load(url);
|
||||
} else {
|
||||
await this.mpv.start();
|
||||
await this.mpv.load(url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
event.sender.send(ERROR, {
|
||||
message: `Error: ${
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
error?.verbose ??
|
||||
'Something went wrong. Make sure that mpv player is installed on your system.'
|
||||
} `,
|
||||
});
|
||||
}
|
||||
})
|
||||
.on(SET_MPV_PLAYER_PATH, (_event, mpvPlayerPath) => {
|
||||
console.log('... setting mpv player path', mpvPlayerPath);
|
||||
store.set(MPV_PLAYER_PATH, mpvPlayerPath);
|
||||
|
||||
// recreate mpv player instance with new binary path if it was changed
|
||||
if (store.get(MPV_PLAYER_PATH, mpvPlayerPath) !== mpvPlayerPath)
|
||||
this.mpv = this.createMpvInstance();
|
||||
})
|
||||
.on(OPEN_VLC_PLAYER, (event, { url }) => {
|
||||
const proc = child_process.spawn(
|
||||
this.getVlcPath(),
|
||||
[`"${url as string}"`],
|
||||
{
|
||||
shell: true,
|
||||
}
|
||||
);
|
||||
|
||||
proc.on('exit', (code) => {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
console.log(`VLC exited with code ${code}`);
|
||||
});
|
||||
})
|
||||
.on(SET_VLC_PLAYER_PATH, (_event, vlcPlayerPath) => {
|
||||
console.log('... setting vlc player path', vlcPlayerPath);
|
||||
store.set(VLC_PLAYER_PATH, vlcPlayerPath);
|
||||
})
|
||||
.on(SETTINGS_UPDATE, (_event, arg) => {
|
||||
this.settings = arg;
|
||||
this.server.updateSettings();
|
||||
});
|
||||
|
||||
// listeners for EPG events
|
||||
ipcMain
|
||||
.on(EPG_GET_PROGRAM, (_event, arg) =>
|
||||
this.workerWindow.webContents.send(EPG_GET_PROGRAM, arg)
|
||||
)
|
||||
.on(EPG_GET_CHANNELS, (_event, arg) =>
|
||||
this.workerWindow.webContents.send(EPG_GET_CHANNELS, arg)
|
||||
)
|
||||
.on(EPG_GET_CHANNELS_DONE, (_event, arg) =>
|
||||
this.mainWindow.webContents.send(EPG_GET_CHANNELS_DONE, arg)
|
||||
)
|
||||
.on(EPG_GET_PROGRAM_DONE, (_event, arg) => {
|
||||
this.mainWindow.webContents.send(EPG_GET_PROGRAM_DONE, arg);
|
||||
})
|
||||
.on(EPG_FETCH, (_event, arg) =>
|
||||
this.workerWindow.webContents.send(EPG_FETCH, arg?.url)
|
||||
)
|
||||
.on(EPG_FETCH_DONE, (_event, arg) =>
|
||||
this.mainWindow.webContents.send(EPG_FETCH_DONE, arg)
|
||||
)
|
||||
.on(EPG_ERROR, (_event, arg) =>
|
||||
this.mainWindow.webContents.send(EPG_ERROR, arg)
|
||||
)
|
||||
.on(EPG_GET_CHANNELS_BY_RANGE, (_event, arg) => {
|
||||
this.workerWindow.webContents.send(
|
||||
EPG_GET_CHANNELS_BY_RANGE,
|
||||
arg
|
||||
);
|
||||
})
|
||||
.on(EPG_GET_CHANNELS_BY_RANGE_RESPONSE, (_event, arg) =>
|
||||
this.mainWindow.webContents.send(
|
||||
EPG_GET_CHANNELS_BY_RANGE_RESPONSE,
|
||||
arg
|
||||
)
|
||||
)
|
||||
.on(EPG_FORCE_FETCH, (_event, arg) =>
|
||||
this.workerWindow.webContents.send(EPG_FORCE_FETCH, arg)
|
||||
);
|
||||
|
||||
ipcMain
|
||||
.on(
|
||||
XTREAM_REQUEST,
|
||||
(
|
||||
event,
|
||||
arg: { url: string; params: Record<string, string> }
|
||||
) => {
|
||||
const xtreamApiPath = '/player_api.php';
|
||||
|
||||
axios
|
||||
.get(arg.url + xtreamApiPath, {
|
||||
params: arg.params ?? {},
|
||||
})
|
||||
.then((result) => {
|
||||
event.sender.send(XTREAM_RESPONSE, {
|
||||
payload: result.data,
|
||||
action: arg.params.action,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
event.sender.send(ERROR, {
|
||||
message:
|
||||
err.response?.statusText ??
|
||||
'Error: not found',
|
||||
status: err.response?.status ?? 404,
|
||||
});
|
||||
});
|
||||
}
|
||||
)
|
||||
.on(STALKER_REQUEST, (event, arg: any) => {
|
||||
axios
|
||||
.get(arg.url, {
|
||||
params: arg.params ?? {},
|
||||
headers: {
|
||||
Cookie: `mac=${arg.macAddress as string}`,
|
||||
...(arg.params.token
|
||||
? {
|
||||
Authorization: `Bearer ${
|
||||
arg.params.token as string
|
||||
}`,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
event.sender.send(STALKER_RESPONSE, {
|
||||
payload: result.data,
|
||||
action: arg.params.action,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
event.sender.send(ERROR, {
|
||||
message:
|
||||
err.response?.statusText ?? 'Error: not found',
|
||||
status: err.response?.status ?? 404,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createMpvInstance() {
|
||||
const mpvPlayerPath = this.store.get(MPV_PLAYER_PATH);
|
||||
console.log('... getting mpv player path', mpvPlayerPath);
|
||||
return new mpvAPI(
|
||||
{ ...(mpvPlayerPath ? { binary: mpvPlayerPath } : {}) },
|
||||
['--autofit=70%']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user agent header for all http requests
|
||||
* @param userAgent user agent to use
|
||||
* @param referer referer to use
|
||||
*/
|
||||
setUserAgent(userAgent: string, referer?: string): void {
|
||||
if (userAgent === undefined || userAgent === null || userAgent === '') {
|
||||
userAgent = this.defaultUserAgent;
|
||||
}
|
||||
|
||||
// Remove trailing slash from referer if it exists
|
||||
let originURL: string;
|
||||
if (referer?.endsWith('/')) {
|
||||
originURL = referer.slice(0, -1);
|
||||
}
|
||||
|
||||
session.defaultSession.webRequest.onBeforeSendHeaders(
|
||||
(details, callback) => {
|
||||
details.requestHeaders['User-Agent'] = userAgent;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
details.requestHeaders['Referer'] = referer as string;
|
||||
details.requestHeaders['Origin'] = originURL as string;
|
||||
callback({ requestHeaders: details.requestHeaders });
|
||||
}
|
||||
);
|
||||
console.log(`Success: Set "${userAgent}" as user agent header`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets epg browser window
|
||||
* @param workerWindow
|
||||
*/
|
||||
setEpgWorkerWindow(workerWindow: BrowserWindow): void {
|
||||
this.workerWindow = workerWindow;
|
||||
|
||||
// store default user agent as fallback
|
||||
this.defaultUserAgent = this.workerWindow.webContents.getUserAgent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets browser window of the main app window
|
||||
* @param mainWindow
|
||||
*/
|
||||
setMainWindow(mainWindow: BrowserWindow): void {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the fetched playlist string to the playlist object, updates it in the database and sends the updated playlists array back to the renderer
|
||||
* @param id id of the playlist to update
|
||||
* @param playlistString updated playlist as string
|
||||
*/
|
||||
getRefreshedPlaylist(
|
||||
args: { id: string; title: string; filePath?: string; url?: string },
|
||||
playlistString: string
|
||||
) {
|
||||
const parsedPlaylist: ParsedPlaylist =
|
||||
this.parsePlaylist(playlistString);
|
||||
const playlist = createPlaylistObject(
|
||||
args.title,
|
||||
parsedPlaylist,
|
||||
args.url ? args.url : args.filePath,
|
||||
args.url ? 'URL' : 'FILE'
|
||||
);
|
||||
return {
|
||||
...playlist,
|
||||
_id: args.id,
|
||||
};
|
||||
}
|
||||
|
||||
sendPlaylistRefreshResponse(
|
||||
playlistId: string,
|
||||
playlist: Playlist,
|
||||
event: Electron.IpcMainEvent
|
||||
) {
|
||||
event.sender.send(PLAYLIST_UPDATE_RESPONSE, {
|
||||
message: `Success! The playlist was successfully updated (${
|
||||
(playlist.playlist.items as Channel[]).length
|
||||
} channels)`,
|
||||
playlist: {
|
||||
...playlist,
|
||||
_id: playlistId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the playlist from the given url and triggers the update operation
|
||||
* @param id id of the playlist to update
|
||||
* @param playlistString updated playlist as string
|
||||
* @param event ipc event to send the response back to the renderer
|
||||
*/
|
||||
async fetchPlaylistByUrl(
|
||||
args: { id: string; title: string; url?: string },
|
||||
event?: Electron.IpcMainEvent
|
||||
) {
|
||||
if (!args.url) return;
|
||||
try {
|
||||
const result = await axios.get(args.url, { httpsAgent: agent });
|
||||
|
||||
const refreshedPlaylist = this.getRefreshedPlaylist(
|
||||
args,
|
||||
result.data
|
||||
);
|
||||
if (event) {
|
||||
this.sendPlaylistRefreshResponse(
|
||||
refreshedPlaylist._id,
|
||||
refreshedPlaylist,
|
||||
event
|
||||
);
|
||||
} else {
|
||||
return refreshedPlaylist;
|
||||
}
|
||||
} catch (err) {
|
||||
if (event)
|
||||
event.sender.send(ERROR, {
|
||||
message: `File not found. Please check the entered playlist URL again.`,
|
||||
status: err.response?.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the playlist from the given path from the file system and triggers the update operation
|
||||
* @param id id of the playlist to update
|
||||
* @param playlistString updated playlist as string
|
||||
* @param event ipc event to send the response back to the renderer
|
||||
*/
|
||||
async fetchPlaylistByFilePath(
|
||||
args: { id: string; title: string; filePath?: string },
|
||||
event?: Electron.IpcMainEvent
|
||||
) {
|
||||
if (!args.filePath) return;
|
||||
let refreshedPlaylist: Playlist;
|
||||
|
||||
try {
|
||||
const playlist = await fsPromises.readFile(args.filePath, 'utf-8');
|
||||
refreshedPlaylist = this.getRefreshedPlaylist(args, playlist);
|
||||
|
||||
if (event) {
|
||||
this.sendPlaylistRefreshResponse(
|
||||
refreshedPlaylist._id,
|
||||
refreshedPlaylist,
|
||||
event
|
||||
);
|
||||
} else {
|
||||
return refreshedPlaylist;
|
||||
}
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends an error message to the renderer process */
|
||||
handleFileNotFoundError(
|
||||
error: {
|
||||
errno: string;
|
||||
code: string;
|
||||
syscall: string;
|
||||
path: string;
|
||||
},
|
||||
event?: Electron.IpcMainEvent
|
||||
): void {
|
||||
console.error(error);
|
||||
if (event) {
|
||||
event.sender.send(ERROR, {
|
||||
message: `Sorry, playlist was not found (${error.path})`,
|
||||
status: 'ENOENT',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses string based array to playlist object
|
||||
* @param m3uString m3u playlist as string
|
||||
*/
|
||||
parsePlaylist(m3uString: string): ParsedPlaylist {
|
||||
return parse(m3uString);
|
||||
}
|
||||
|
||||
getDefaultVlcPath() {
|
||||
if (process.platform === 'win32') {
|
||||
return path.join(
|
||||
'C:',
|
||||
'Program Files (x86)',
|
||||
'VideoLAN',
|
||||
'VLC',
|
||||
'vlc.exe'
|
||||
);
|
||||
} else if (process.platform === 'linux') {
|
||||
return '/usr/bin/vlc';
|
||||
} else if (process.platform === 'darwin') {
|
||||
return '/Applications/VLC.app/Contents/MacOS/VLC';
|
||||
}
|
||||
}
|
||||
|
||||
getVlcPath() {
|
||||
const customVlcPath = this.store.get(VLC_PLAYER_PATH);
|
||||
if (customVlcPath) {
|
||||
return customVlcPath;
|
||||
} else {
|
||||
return this.getDefaultVlcPath();
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated - used only for migration */
|
||||
getAllPlaylists() {
|
||||
return db
|
||||
.find({ type: { $exists: false } })
|
||||
.sort({ position: 1, importDate: -1 });
|
||||
}
|
||||
|
||||
/** @deprecated - used only for migration */
|
||||
async removeAllPlaylists(event: Electron.IpcMainEvent) {
|
||||
const removeCount = await db.remove({}, { multi: true });
|
||||
console.info(removeCount, ' playlists were removed');
|
||||
fs.unlink(dbPath, (err) => {
|
||||
if (err && err.code == 'ENOENT') {
|
||||
console.info("File doesn't exist, won't remove it.");
|
||||
} else if (err) {
|
||||
console.error('Error occurred while trying to remove file');
|
||||
} else {
|
||||
console.info(`${dbPath} was deleted`);
|
||||
event.sender.send(IS_PLAYLISTS_MIGRATION_POSSIBLE_RESPONSE, {
|
||||
result: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Hidden EPG worker</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
require('./epg-worker.js');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,175 +0,0 @@
|
||||
const electron = require('electron');
|
||||
const ipcRenderer = electron.ipcRenderer;
|
||||
const zlib = require('zlib');
|
||||
const parser = require('epg-parser');
|
||||
const axios = require('axios');
|
||||
import {
|
||||
EPG_ERROR,
|
||||
EPG_FETCH,
|
||||
EPG_FETCH_DONE,
|
||||
EPG_FORCE_FETCH,
|
||||
EPG_GET_CHANNELS,
|
||||
EPG_GET_CHANNELS_BY_RANGE,
|
||||
EPG_GET_CHANNELS_BY_RANGE_RESPONSE,
|
||||
EPG_GET_CHANNELS_DONE,
|
||||
EPG_GET_PROGRAM,
|
||||
EPG_GET_PROGRAM_DONE,
|
||||
} from '../shared/ipc-commands';
|
||||
import { EpgChannel } from '../src/app/player/models/epg-channel.model';
|
||||
import { EpgProgram } from '../src/app/player/models/epg-program.model';
|
||||
|
||||
// EPG data store
|
||||
let EPG_DATA: { channels: EpgChannel[]; programs: EpgProgram[] } = {
|
||||
channels: [],
|
||||
programs: [],
|
||||
};
|
||||
let EPG_DATA_MERGED: {
|
||||
[id: string]: EpgChannel & { programs: EpgProgram[] };
|
||||
} = {};
|
||||
const loggerLabel = '[EPG Worker]';
|
||||
|
||||
/** List with fetched EPG URLs */
|
||||
const fetchedUrls: string[] = [];
|
||||
|
||||
/**
|
||||
* Fetches the epg data from the given url
|
||||
* @param epgUrl url of the epg file
|
||||
*/
|
||||
const fetchEpgDataFromUrl = (epgUrl: string) => {
|
||||
try {
|
||||
let axiosConfig = {};
|
||||
if (epgUrl.endsWith('.gz')) {
|
||||
axiosConfig = {
|
||||
responseType: 'arraybuffer',
|
||||
};
|
||||
}
|
||||
axios
|
||||
.get(epgUrl.trim(), axiosConfig)
|
||||
.then((response) => {
|
||||
console.log(loggerLabel, 'url content was fetched...');
|
||||
const { data } = response;
|
||||
if (epgUrl.endsWith('.gz')) {
|
||||
console.log(loggerLabel, 'start unzipping...');
|
||||
zlib.gunzip(data, (_err, output) => {
|
||||
parseAndSetEpg(output);
|
||||
});
|
||||
} else {
|
||||
parseAndSetEpg(data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(loggerLabel, err);
|
||||
ipcRenderer.send(EPG_ERROR);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(loggerLabel, error);
|
||||
ipcRenderer.send(EPG_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses and sets the epg data
|
||||
* @param xmlString xml file content from the fetched url as string
|
||||
*/
|
||||
const parseAndSetEpg = (xmlString) => {
|
||||
console.log(loggerLabel, 'start parsing...');
|
||||
const parsedEpg = parser.parse(xmlString.toString());
|
||||
EPG_DATA = {
|
||||
channels: [...EPG_DATA.channels, ...parsedEpg.channels],
|
||||
programs: [...EPG_DATA.programs, ...parsedEpg.programs],
|
||||
};
|
||||
// map programs to channels
|
||||
EPG_DATA_MERGED = convertEpgData();
|
||||
ipcRenderer.send(EPG_FETCH_DONE);
|
||||
console.log(loggerLabel, 'done, parsing was finished...');
|
||||
};
|
||||
|
||||
const convertEpgData = () => {
|
||||
const result: {
|
||||
[id: string]: EpgChannel & { programs: EpgProgram[] };
|
||||
} = {};
|
||||
|
||||
EPG_DATA?.programs?.forEach((program) => {
|
||||
if (!result[program.channel]) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const channel = EPG_DATA?.channels?.find(
|
||||
(channel) => channel.id === program.channel
|
||||
) as EpgChannel;
|
||||
result[program.channel] = {
|
||||
...channel,
|
||||
programs: [program],
|
||||
};
|
||||
} else {
|
||||
result[program.channel] = {
|
||||
...result[program.channel],
|
||||
programs: [...result[program.channel].programs, program],
|
||||
};
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// fetches epg data from the provided URL
|
||||
ipcRenderer.on(EPG_FETCH, (event, epgUrl: string) => {
|
||||
console.log(loggerLabel, 'epg fetch command was triggered');
|
||||
if (fetchedUrls.indexOf(epgUrl) > -1) {
|
||||
ipcRenderer.send(EPG_FETCH_DONE);
|
||||
return;
|
||||
}
|
||||
fetchedUrls.push(epgUrl);
|
||||
fetchEpgDataFromUrl(epgUrl);
|
||||
});
|
||||
|
||||
// returns the epg data for the provided channel name and date
|
||||
ipcRenderer.on(EPG_GET_PROGRAM, (event, args) => {
|
||||
const channelName = args.channel?.name;
|
||||
const tvgId = args.channel?.tvg?.id;
|
||||
if (!EPG_DATA || !EPG_DATA.channels) return;
|
||||
const foundChannel = EPG_DATA?.channels?.find((epgChannel) => {
|
||||
if (tvgId && tvgId === epgChannel.id) {
|
||||
return epgChannel;
|
||||
} else if (
|
||||
epgChannel.name.find((nameObj) => {
|
||||
if (
|
||||
nameObj.value &&
|
||||
nameObj.value.trim() === channelName.trim()
|
||||
)
|
||||
return nameObj;
|
||||
})
|
||||
) {
|
||||
return epgChannel;
|
||||
}
|
||||
});
|
||||
|
||||
if (foundChannel) {
|
||||
const programs = EPG_DATA?.programs?.filter(
|
||||
(ch) => ch.channel === foundChannel.id
|
||||
);
|
||||
ipcRenderer.send(EPG_GET_PROGRAM_DONE, {
|
||||
payload: { channel: foundChannel, items: programs },
|
||||
});
|
||||
} else {
|
||||
console.log('EPG program for the channel was not found...');
|
||||
ipcRenderer.send(EPG_GET_PROGRAM_DONE, {
|
||||
payload: { channel: {}, items: [] },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on(EPG_GET_CHANNELS, () => {
|
||||
ipcRenderer.send(EPG_GET_CHANNELS_DONE, {
|
||||
payload: EPG_DATA,
|
||||
});
|
||||
});
|
||||
|
||||
ipcRenderer.on(EPG_GET_CHANNELS_BY_RANGE, (event, args) => {
|
||||
ipcRenderer.send(EPG_GET_CHANNELS_BY_RANGE_RESPONSE, {
|
||||
payload: Object.entries(EPG_DATA_MERGED)
|
||||
.slice(args.skip, args.limit)
|
||||
.map((entry) => entry[1]),
|
||||
});
|
||||
});
|
||||
|
||||
ipcRenderer.on(EPG_FORCE_FETCH, (event, url: string) => {
|
||||
fetchEpgDataFromUrl(url);
|
||||
});
|
||||
@@ -1,154 +0,0 @@
|
||||
/* eslint-disable no-useless-catch */
|
||||
import { app, BrowserWindow, Menu } from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import { Api } from './api';
|
||||
import { AppMenu } from './menu';
|
||||
|
||||
const fixPath = require('fix-path');
|
||||
const {
|
||||
setupTitlebar,
|
||||
attachTitlebarToWindow,
|
||||
} = require('custom-electron-titlebar/main');
|
||||
const contextMenu = require('electron-context-menu');
|
||||
const Store = require('electron-store');
|
||||
const store = new Store();
|
||||
|
||||
const WINDOW_BOUNDS = 'WINDOW_BOUNDS';
|
||||
|
||||
fixPath();
|
||||
setupTitlebar();
|
||||
let win: BrowserWindow | null = null;
|
||||
const args = process.argv.slice(1),
|
||||
serve = args.some((val) => val === '--serve');
|
||||
|
||||
const api = new Api(store);
|
||||
contextMenu();
|
||||
require('@electron/remote/main').initialize();
|
||||
|
||||
function createWindow(): BrowserWindow {
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
width: 1000,
|
||||
height: 800,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
nodeIntegration: true,
|
||||
allowRunningInsecureContent: serve ? true : false,
|
||||
contextIsolation: false,
|
||||
webSecurity: false,
|
||||
},
|
||||
resizable: true,
|
||||
darkTheme: true,
|
||||
icon: path.join(__dirname, '../dist/assets/icons/icon.png'),
|
||||
titleBarStyle: 'hidden',
|
||||
frame: false,
|
||||
minWidth: 400,
|
||||
minHeight: 500,
|
||||
title: 'IPTVnator',
|
||||
...store.get(WINDOW_BOUNDS),
|
||||
});
|
||||
attachTitlebarToWindow(win);
|
||||
|
||||
require('@electron/remote/main').enable(win.webContents);
|
||||
|
||||
if (serve) {
|
||||
win.on('ready-to-show', () => win?.webContents.openDevTools());
|
||||
win.loadURL('http://localhost:4200');
|
||||
} else {
|
||||
win.loadURL(
|
||||
url.format({
|
||||
pathname: path.join(__dirname, '../dist/index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
win.on('close', () => {
|
||||
if (win) store.set(WINDOW_BOUNDS, win.getNormalBounds());
|
||||
});
|
||||
|
||||
// Emitted when the window is closed.
|
||||
win.on('closed', () => {
|
||||
// Dereference the window object, usually you would store window
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
win = null;
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates hidden window for EPG worker
|
||||
* Hidden window is used as an additional thread to avoid blocking of the UI by long operations
|
||||
*/
|
||||
function createEpgWorkerWindow() {
|
||||
const window = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
window.loadFile('./electron/epg-worker.html');
|
||||
if (serve) {
|
||||
window.webContents.openDevTools();
|
||||
}
|
||||
|
||||
window.once('ready-to-show', () => {
|
||||
api.setEpgWorkerWindow(window);
|
||||
});
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
try {
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
// Added 400 ms to fix the black background issue while using transparent window. More details at https://github.com/electron/electron/issues/15947
|
||||
app.on('ready', () => {
|
||||
// create main window and set menu
|
||||
const win = createWindow();
|
||||
const menu = new AppMenu(win);
|
||||
Menu.setApplicationMenu(menu.getMenu());
|
||||
api.setMainWindow(win);
|
||||
|
||||
// create hidden window for epg worker
|
||||
createEpgWorkerWindow();
|
||||
});
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (win === null) {
|
||||
win = createWindow();
|
||||
const menu = new AppMenu(win);
|
||||
Menu.setApplicationMenu(menu.getMenu());
|
||||
api.setMainWindow(win);
|
||||
}
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
if (win) store.set(WINDOW_BOUNDS, win.getNormalBounds());
|
||||
});
|
||||
} catch (e) {
|
||||
// Catch Error
|
||||
throw e;
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
SHOW_WHATS_NEW,
|
||||
VIEW_ADD_PLAYLIST,
|
||||
VIEW_SETTINGS,
|
||||
} from '../shared/ipc-commands';
|
||||
const openAboutWindow = require('about-window').default;
|
||||
|
||||
export class AppMenu {
|
||||
/** Application menu */
|
||||
menu: Menu = new Menu();
|
||||
|
||||
/** Application window */
|
||||
window: BrowserWindow;
|
||||
|
||||
constructor(appWindow: BrowserWindow) {
|
||||
this.window = appWindow;
|
||||
this.initMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates context menu
|
||||
* @param win browser window object
|
||||
*/
|
||||
initMenu(): void {
|
||||
this.menu.append(this.getFileMenu());
|
||||
|
||||
// copy-paste shortcuts workaround for mac os
|
||||
if (process.platform === 'darwin') {
|
||||
this.menu.append(this.getEditMenu());
|
||||
}
|
||||
|
||||
this.menu.append(this.getHelpMenu());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the application menu
|
||||
*/
|
||||
getMenu(): Menu {
|
||||
return this.menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the file menu
|
||||
* @param win application window
|
||||
*/
|
||||
private getFileMenu(): MenuItem {
|
||||
return new MenuItem({
|
||||
label: 'File',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Add playlist',
|
||||
click: () => {
|
||||
if (!this.window.isDestroyed())
|
||||
this.window.webContents.send(VIEW_ADD_PLAYLIST);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
click: () => {
|
||||
if (!this.window.isDestroyed())
|
||||
this.window.webContents.send(VIEW_SETTINGS);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Exit',
|
||||
click: () => app.quit(),
|
||||
accelerator: 'CmdOrCtrl+Q',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the edit menu
|
||||
* @param win application window
|
||||
*/
|
||||
private getHelpMenu(): MenuItem {
|
||||
return new MenuItem({
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'What is new',
|
||||
click: () => {
|
||||
if (!this.window.isDestroyed())
|
||||
this.window.webContents.send(SHOW_WHATS_NEW);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Report a bug',
|
||||
click: () => {
|
||||
shell.openExternal(
|
||||
'https://github.com/4gray/iptvnator'
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Buy me a coffee',
|
||||
click: () => {
|
||||
shell.openExternal('https://github.com/sponsors/4gray');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Open DevTools',
|
||||
click: () => {
|
||||
if (!this.window.isDestroyed())
|
||||
this.window.webContents.openDevTools();
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'About',
|
||||
click: () =>
|
||||
openAboutWindow({
|
||||
icon_path: path.join(
|
||||
__dirname,
|
||||
'../dist/assets/icons/icon.png'
|
||||
),
|
||||
copyright: 'Copyright (c) 2020-2023 4gray',
|
||||
package_json_dir: path.join(__dirname, '../'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the help menu
|
||||
* @param win application window
|
||||
*/
|
||||
private getEditMenu(): MenuItem {
|
||||
return new MenuItem({
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ role: 'cut' },
|
||||
{ role: 'copy' },
|
||||
{ role: 'paste' },
|
||||
{ role: 'delete' },
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Titlebar, TitlebarColor } from 'custom-electron-titlebar';
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
new Titlebar({
|
||||
backgroundColor: TitlebarColor.fromHex('#1b1c1c'),
|
||||
});
|
||||
});
|
||||
@@ -1,85 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>{{it.title}}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
header {
|
||||
width: 100%;
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
}
|
||||
header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
}
|
||||
.up-channel {
|
||||
background-color: #007bff;
|
||||
}
|
||||
.down-channel {
|
||||
background-color: #28a745;
|
||||
}
|
||||
footer {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
footer p {
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// AJAX isteği yapmak için fetch API kullanımı
|
||||
function callAjax(url) {
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Success:', data);
|
||||
alert('AJAX request successful! Check console for data.');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('There was a problem with your fetch operation:', error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>{{it.headerText}}</h1>
|
||||
</header>
|
||||
|
||||
<div class="button-container">
|
||||
<button class="up-channel" onclick="callAjax('{{it.upChannelUrl}}')">{{it.upChannelText}}</button>
|
||||
<button class="down-channel" onclick="callAjax('{{it.downChannelUrl}}')">{{it.downChannelText}}</button>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>{{it.footer}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,86 +0,0 @@
|
||||
import { IncomingMessage, ServerResponse } from "http";
|
||||
import { REMOTE_CONTROL_CHANGE_CHANNEL } from "../shared/ipc-commands";
|
||||
|
||||
const http = require('http');
|
||||
const Sqrl = require('squirrelly');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
export class Server {
|
||||
/** Server instance */
|
||||
server: any;
|
||||
/** Api instance */
|
||||
api: any;
|
||||
|
||||
/**
|
||||
* Creates a new server instance
|
||||
* @param port port number
|
||||
* @param api application instance
|
||||
*/
|
||||
constructor(_api: any) {
|
||||
this.api = _api;
|
||||
this.server = http.createServer(this.requestListener.bind(this));
|
||||
}
|
||||
|
||||
updateSettings() {
|
||||
if (this.api.settings.remoteControl && !this.server.listening) {
|
||||
this.server.listen(this.api.settings.remoteControlPort);
|
||||
} else if (this.server.listening && !this.api.settings.remoteControl) {
|
||||
this.server.close();
|
||||
}
|
||||
}
|
||||
|
||||
async getTranslation() {
|
||||
const language = this.api.settings && this.api.settings.language ? this.api.settings.language : 'en';
|
||||
const languageFileContent = await fs.promises.readFile(path.join(__dirname, `../src/assets/i18n/${language}.json`), 'utf-8');
|
||||
return JSON.parse(languageFileContent);
|
||||
}
|
||||
|
||||
getTranslationValue(key: string, translation: any, defaultValue: string) {
|
||||
if (translation && translation.REMOTE_CONTROL) {
|
||||
const translationValue = translation.REMOTE_CONTROL[key];
|
||||
if (translationValue) {
|
||||
return translationValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
async requestListener(request: IncomingMessage, response: ServerResponse) {
|
||||
const currentPage = request.url?.split('?')[0]?.split('/').splice(-1)[0];
|
||||
const translation = await this.getTranslation();
|
||||
switch (currentPage) {
|
||||
case '': {
|
||||
const indexFile = await fs.promises.readFile(path.join(__dirname, './remote-control/', 'index.sqrl'), 'utf-8');
|
||||
const renderedHtml = Sqrl.render(indexFile, {
|
||||
headerText: this.getTranslationValue('HEADER', translation, 'Remote Control'),
|
||||
upChannelText: this.getTranslationValue('UP_CHANNEL', translation, 'Up Channel'),
|
||||
downChannelText: this.getTranslationValue('DOWN_CHANNEL', translation, 'Down Channel'),
|
||||
upChannelUrl: '/upChannel',
|
||||
downChannelUrl: '/downChannel',
|
||||
title: this.getTranslationValue('TITLE', translation, 'IPTVNator'),
|
||||
footer: this.getTranslationValue('FOOTER', translation, 'IPTVNator')
|
||||
});
|
||||
response.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
response.end(renderedHtml);
|
||||
break;
|
||||
}
|
||||
case 'upChannel': {
|
||||
this.api.mainWindow.webContents.send(REMOTE_CONTROL_CHANGE_CHANNEL, { type: 'up' });
|
||||
response.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
response.end('Up Channel');
|
||||
break;
|
||||
}
|
||||
case 'downChannel': {
|
||||
this.api.mainWindow.webContents.send(REMOTE_CONTROL_CHANGE_CHANNEL, { type: 'down' });
|
||||
response.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
response.end('Down Channel');
|
||||
break;
|
||||
}
|
||||
default:
|
||||
response.writeHead(404, { 'Content-Type': 'text/html' });
|
||||
response.end('404');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user