const express = require('express') const path = require('path') const fs = require('fs') const constants = require('./constants'); const JSONStream = require('JSONStream'); const FFMPEGInfo = require('./ffmpeg-info'); const PlexServerDB = require('./dao/plex-server-db'); const Plex = require("./plex.js"); const timeSlotsService = require('./services/time-slots-service'); const randomSlotsService = require('./services/random-slots-service'); const throttle = require('./services/throttle'); function safeString(object) { let o = object; for(let i = 1; i < arguments.length; i++) { o = o[arguments[i]]; if (typeof(o) === 'undefined') { return "missing"; } } return String(o); } module.exports = { router: api } function api(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, _m3uService, eventService, ffmpegSettingsService ) { let m3uService = _m3uService; const router = express.Router() const plexServerDB = new PlexServerDB(channelService, fillerDB, customShowDB, db); router.get('/api/version', async (req, res) => { try { let ffmpegSettings = db['ffmpeg-settings'].find()[0]; let v = await (new FFMPEGInfo(ffmpegSettings)).getVersion(); res.send( { "dizquetv" : constants.VERSION_NAME, "ffmpeg" : v, "nodejs" : process.version, } ); } catch(err) { console.error(err); res.status(500).send("error"); } }); // Plex Servers router.get('/api/plex-servers', (req, res) => { try { let servers = db['plex-servers'].find() servers.sort( (a,b) => { return a.index - b.index } ); res.send(servers) } catch(err) { console.error(err); res.status(500).send("error"); } }) router.post("/api/plex-servers/status", async (req, res) => { try { let servers = db['plex-servers'].find( { name: req.body.name, }); if (servers.length != 1) { return res.status(404).send(req.t("api.plex_server_not_found")); } let plex = new Plex(servers[0]); let s = await Promise.race( [ (async() => { return await plex.checkServerStatus(); })(), new Promise( (resolve, reject) => { setTimeout( () => { resolve(-1); }, 60000); }), ]); res.send( { status: s, }); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.post("/api/plex-servers/foreignstatus", async (req, res) => { try { let server = req.body; let plex = new Plex(server); let s = await Promise.race( [ (async() => { return await plex.checkServerStatus(); })(), new Promise( (resolve, reject) => { setTimeout( () => { resolve(-1); }, 60000); }), ]); res.send( { status: s, }); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.delete('/api/plex-servers', async (req, res) => { let name = "unknown"; try { name = req.body.name; if (typeof(name) === 'undefined') { return res.status(400).send("Missing name"); } let report = await plexServerDB.deleteServer(name); res.send(report) eventService.push( "settings-update", { "message": `Plex server ${name} removed.`, "module" : "plex-server", "detail" : { "serverName" : name, "action" : "delete" }, "level" : "warn" } ); } catch(err) { console.error(err); res.status(500).send("error"); eventService.push( "settings-update", { "message": "Error deleting plex server.", "module" : "plex-server", "detail" : { "action": "delete", "serverName" : name, "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) router.post('/api/plex-servers', async (req, res) => { try { let report = await plexServerDB.updateServer(req.body); let modifiedPrograms = 0; let destroyedPrograms = 0; report.forEach( (r) => { modifiedPrograms += r.modifiedPrograms; destroyedPrograms += r.destroyedPrograms; } ); res.status(204).send("Plex server updated.");; eventService.push( "settings-update", { "message": `Plex server ${req.body.name} updated. ${modifiedPrograms} programs modified, ${destroyedPrograms} programs deleted`, "module" : "plex-server", "detail" : { "serverName" : req.body.name, "action" : "update" }, "level" : "warning" } ); } catch (err) { console.error("Could not update plex server.", err); res.status(400).send("Could not add plex server."); eventService.push( "settings-update", { "message": "Error updating plex server.", "module" : "plex-server", "detail" : { "action": "update", "serverName" : safeString(req, "body", "name"), "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) router.put('/api/plex-servers', async (req, res) => { try { await plexServerDB.addServer(req.body); res.status(201).send("Plex server added.");; eventService.push( "settings-update", { "message": `Plex server ${req.body.name} added.`, "module" : "plex-server", "detail" : { "serverName" : req.body.name, "action" : "add" }, "level" : "info" } ); } catch (err) { console.error("Could not add plex server.", err); res.status(400).send("Could not add plex server."); eventService.push( "settings-update", { "message": "Error adding plex server.", "module" : "plex-server", "detail" : { "action": "add", "serverName" : safeString(req, "body", "name"), "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) // Channels router.get('/api/channels', async (req, res) => { try { let channels = await channelService.getAllChannelNumbers(); channels.sort((a, b) => { return a.number < b.number ? -1 : 1 }) res.send(channels) } catch(err) { console.error(err); res.status(500).send("error"); } }) router.get('/api/channel/:number', async (req, res) => { try { let number = parseInt(req.params.number, 10); let channel = await channelService.getChannel(number); if (channel != null) { res.send(channel); } else { return res.status(404).send("Channel not found"); } } catch(err) { console.error(err); res.status(500).send("error"); } }) router.get('/api/channel/programless/:number', async (req, res) => { try { let number = parseInt(req.params.number, 10); let channel = await channelService.getChannel(number); if (channel != null) { let copy = {}; Object.keys(channel).forEach( (key) => { if (key != 'programs') { copy[key] = channel[key]; } } ); res.send(copy); } else { return res.status(404).send("Channel not found"); } } catch(err) { console.error(err); res.status(500).send("error"); } }) router.get('/api/channel/programs/:number', async (req, res) => { try { let number = parseInt(req.params.number, 10); let channel = await channelService.getChannel(number); if (channel != null) { let programs = channel.programs; if (typeof(programs) === 'undefined') { return res.status(404).send("Channel doesn't have programs?"); } res.writeHead(200, { 'Content-Type': 'application/json' }); let transformStream = JSONStream.stringify(); transformStream.pipe(res); for (let i = 0; i < programs.length; i++) { transformStream.write( programs[i] ); await throttle(); } transformStream.end(); } else { return res.status(404).send("Channel not found"); } } catch(err) { console.error(err); res.status(500).send("error"); } }) router.get('/api/channel/description/:number', async (req, res) => { try { let number = parseInt(req.params.number, 10); let channel = await channelService.getChannel(number); if (channel != null) { res.send({ number: channel.number, icon: channel.icon, name: channel.name, stealth: channel.stealth, }); } else { return res.status(404).send("Channel not found"); } } catch(err) { console.error(err); res.status(500).send("error"); } }) router.get('/api/channelNumbers', async (req, res) => { try { let channels = await channelService.getAllChannelNumbers(); channels.sort( (a,b) => { return parseInt(a) - parseInt(b) } ); res.send(channels) } catch(err) { console.error(err); res.status(500).send("error"); } }) // we urgently need an actual channel service router.post('/api/channel', async (req, res) => { try { await channelService.saveChannel( req.body.number, req.body ); res.send( { number: req.body.number} ) } catch(err) { console.error(err); res.status(500).send("error"); } }) router.put('/api/channel', async (req, res) => { try { await channelService.saveChannel( req.body.number, req.body ); res.send( { number: req.body.number} ) } catch(err) { console.error(err); res.status(500).send("error"); } }) router.delete('/api/channel', async (req, res) => { try { await channelService.deleteChannel(req.body.number); res.send( { number: req.body.number} ) } catch(err) { console.error(err); res.status(500).send("error"); } }) router.post('/api/upload/image', async (req, res) => { try { if(!req.files) { res.send({ status: false, message: 'No file uploaded' }); } else { const logo = req.files.image; logo.mv(path.join(process.env.DATABASE, '/images/uploads/', logo.name)); res.send({ status: true, message: 'File is uploaded', data: { name: logo.name, mimetype: logo.mimetype, size: logo.size, fileUrl: `${req.protocol}://${req.get('host')}/images/uploads/${logo.name}` } }); } } catch (err) { res.status(500).send(err); } }) // Filler router.get('/api/fillers', async (req, res) => { try { let fillers = await fillerDB.getAllFillersInfo(); res.send(fillers); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.get('/api/filler/:id', async (req, res) => { try { let id = req.params.id; if (typeof(id) === 'undefined') { return res.status(400).send("Missing id"); } let filler = await fillerDB.getFiller(id); if (filler == null) { return res.status(404).send("Filler not found"); } res.send(filler); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.post('/api/filler/:id', async (req, res) => { try { let id = req.params.id; if (typeof(id) === 'undefined') { return res.status(400).send("Missing id"); } await fillerDB.saveFiller(id, req.body ); return res.status(204).send({}); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.put('/api/filler', async (req, res) => { try { let uuid = await fillerDB.createFiller(req.body ); return res.status(201).send({id: uuid}); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.delete('/api/filler/:id', async (req, res) => { try { let id = req.params.id; if (typeof(id) === 'undefined') { return res.status(400).send("Missing id"); } await fillerDB.deleteFiller(id); return res.status(204).send({}); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.get('/api/filler/:id/channels', async(req, res) => { try { let id = req.params.id; if (typeof(id) === 'undefined') { return res.status(400).send("Missing id"); } let channels = await fillerDB.getFillerChannels(id); if (channels == null) { return res.status(404).send("Filler not found"); } res.send(channels); } catch(err) { console.error(err); res.status(500).send("error"); } } ); // Custom Shows router.get('/api/shows', async (req, res) => { try { let fillers = await customShowDB.getAllShowsInfo(); res.send(fillers); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.get('/api/show/:id', async (req, res) => { try { let id = req.params.id; if (typeof(id) === 'undefined') { return res.status(400).send("Missing id"); } let filler = await customShowDB.getShow(id); if (filler == null) { return res.status(404).send("Custom show not found"); } res.send(filler); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.post('/api/show/:id', async (req, res) => { try { let id = req.params.id; if (typeof(id) === 'undefined') { return res.status(400).send("Missing id"); } await customShowDB.saveShow(id, req.body ); return res.status(204).send({}); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.put('/api/show', async (req, res) => { try { let uuid = await customShowDB.createShow(req.body ); return res.status(201).send({id: uuid}); } catch(err) { console.error(err); res.status(500).send("error"); } }) router.delete('/api/show/:id', async (req, res) => { try { let id = req.params.id; if (typeof(id) === 'undefined') { return res.status(400).send("Missing id"); } await customShowDB.deleteShow(id); return res.status(204).send({}); } catch(err) { console.error(err); res.status(500).send("error"); } }); // FFMPEG SETTINGS router.get('/api/ffmpeg-settings', (req, res) => { try { let ffmpeg = ffmpegSettingsService.get(); res.send(ffmpeg) } catch(err) { console.error(err); res.status(500).send("error"); } }) router.put('/api/ffmpeg-settings', (req, res) => { try { let result = ffmpegSettingsService.update(req.body); let err = result.error if (typeof(err) !== 'undefined') { return res.status(400).send(err); } eventService.push( "settings-update", { "message": "FFMPEG configuration updated.", "module" : "ffmpeg", "detail" : { "action" : "update" }, "level" : "info" } ); res.send(result.ffmpeg) } catch(err) { console.error(err); res.status(500).send("error"); eventService.push( "settings-update", { "message": "Error updating FFMPEG configuration.", "module" : "ffmpeg", "detail" : { "action": "update", "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) router.post('/api/ffmpeg-settings', (req, res) => { // RESET try { let ffmpeg = ffmpegSettingsService.reset(); eventService.push( "settings-update", { "message": "FFMPEG configuration reset.", "module" : "ffmpeg", "detail" : { "action" : "reset" }, "level" : "warning" } ); res.send(ffmpeg) } catch(err) { console.error(err); res.status(500).send("error"); eventService.push( "settings-update", { "message": "Error reseting FFMPEG configuration.", "module" : "ffmpeg", "detail" : { "action": "reset", "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) // PLEX SETTINGS router.get('/api/plex-settings', (req, res) => { try { let plex = db['plex-settings'].find()[0] res.send(plex) } catch(err) { console.error(err); res.status(500).send("error"); } }) router.put('/api/plex-settings', (req, res) => { try { db['plex-settings'].update({ _id: req.body._id }, req.body) let plex = db['plex-settings'].find()[0] res.send(plex) eventService.push( "settings-update", { "message": "Plex configuration updated.", "module" : "plex", "detail" : { "action" : "update" }, "level" : "info" } ); } catch(err) { console.error(err); res.status(500).send("error"); eventService.push( "settings-update", { "message": "Error updating Plex configuration", "module" : "plex", "detail" : { "action": "update", "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) router.post('/api/plex-settings', (req, res) => { // RESET try { db['plex-settings'].update({ _id: req.body._id }, { streamPath: 'plex', debugLogging: true, directStreamBitrate: '20000', transcodeBitrate: '2000', mediaBufferSize: 1000, transcodeMediaBufferSize: 20000, maxPlayableResolution: "1920x1080", maxTranscodeResolution: "1920x1080", videoCodecs: 'h264,hevc,mpeg2video,av1', audioCodecs: 'ac3', maxAudioChannels: '2', audioBoost: '100', enableSubtitles: false, subtitleSize: '100', updatePlayStatus: false, streamProtocol: 'http', forceDirectPlay: false, pathReplace: '', pathReplaceWith: '' }) let plex = db['plex-settings'].find()[0] res.send(plex) eventService.push( "settings-update", { "message": "Plex configuration reset.", "module" : "plex", "detail" : { "action" : "reset" }, "level" : "warning" } ); } catch(err) { console.error(err); res.status(500).send("error"); eventService.push( "settings-update", { "message": "Error reseting Plex configuration", "module" : "plex", "detail" : { "action": "reset", "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) router.get('/api/xmltv-last-refresh', (req, res) => { try { res.send(JSON.stringify({ value: xmltvInterval.lastUpdated.valueOf() })) } catch(err) { console.error(err); res.status(500).send("error"); } }) // XMLTV SETTINGS router.get('/api/xmltv-settings', (req, res) => { try { let xmltv = db['xmltv-settings'].find()[0] res.send(xmltv) } catch(err) { console.error(err); res.status(500).send("error"); } }) router.put('/api/xmltv-settings', (req, res) => { try { let xmltv = db['xmltv-settings'].find()[0] db['xmltv-settings'].update( { _id: req.body._id }, { _id: req.body._id, cache: req.body.cache, refresh: req.body.refresh, enableImageCache: (req.body.enableImageCache === true), file: xmltv.file, } ); xmltv = db['xmltv-settings'].find()[0] res.send(xmltv) eventService.push( "settings-update", { "message": "xmltv settings updated.", "module" : "xmltv", "detail" : { "action" : "update" }, "level" : "info" } ); updateXmltv() } catch(err) { console.error(err); res.status(500).send("error"); eventService.push( "settings-update", { "message": "Error updating xmltv configuration", "module" : "xmltv", "detail" : { "action": "update", "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) router.post('/api/xmltv-settings', (req, res) => { try { db['xmltv-settings'].update({ _id: req.body._id }, { _id: req.body._id, cache: 12, refresh: 4, file: process.env.DATABASE + '/xmltv.xml' }) var xmltv = db['xmltv-settings'].find()[0] res.send(xmltv) eventService.push( "settings-update", { "message": "xmltv settings reset.", "module" : "xmltv", "detail" : { "action" : "reset" }, "level" : "warning" } ); updateXmltv() } catch(err) { console.error(err); res.status(500).send("error"); eventService.push( "settings-update", { "message": "Error reseting xmltv configuration", "module" : "xmltv", "detail" : { "action": "reset", "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) router.get('/api/guide/status', async (req, res) => { try { let s = await guideService.getStatus(); res.send(s); } catch(err) { console.error(err); res.status(500).send("error"); } }); router.get('/api/guide/debug', async (req, res) => { try { let s = await guideService.get(); res.send(s); } catch(err) { console.error(err); res.status(500).send("error"); } }); router.get('/api/guide/channels/:number', async (req, res) => { try { let dateFrom = new Date(req.query.dateFrom); let dateTo = new Date(req.query.dateTo); let lineup = await guideService.getChannelLineup( req.params.number , dateFrom, dateTo ); if (lineup == null) { console.log(`GET /api/guide/channels/${req.params.number} : 404 Not Found`); res.status(404).send("Channel not found in TV guide"); } else { res.send( lineup ); } } catch (err) { console.error(err); res.status(500).send("error"); } }); //HDHR SETTINGS router.get('/api/hdhr-settings', (req, res) => { try { let hdhr = db['hdhr-settings'].find()[0] res.send(hdhr) } catch(err) { console.error(err); res.status(500).send("error"); } }) router.put('/api/hdhr-settings', (req, res) => { try { db['hdhr-settings'].update({ _id: req.body._id }, req.body) let hdhr = db['hdhr-settings'].find()[0] res.send(hdhr) eventService.push( "settings-update", { "message": "HDHR configuration updated.", "module" : "hdhr", "detail" : { "action" : "update" }, "level" : "info" } ); } catch(err) { console.error(err); res.status(500).send("error"); eventService.push( "settings-update", { "message": "Error updating HDHR configuration", "module" : "hdhr", "detail" : { "action": "action", "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) router.post('/api/hdhr-settings', (req, res) => { try { db['hdhr-settings'].update({ _id: req.body._id }, { _id: req.body._id, tunerCount: 1, autoDiscovery: true, }) var hdhr = db['hdhr-settings'].find()[0] res.send(hdhr) eventService.push( "settings-update", { "message": "HDHR configuration reset.", "module" : "hdhr", "detail" : { "action" : "reset" }, "level" : "warning" } ); } catch(err) { console.error(err); res.status(500).send("error"); eventService.push( "settings-update", { "message": "Error reseting HDHR configuration", "module" : "hdhr", "detail" : { "action": "reset", "error" : safeString(err, "message"), }, "level" : "danger" } ); } }) // XMLTV.XML Download router.get('/api/xmltv.xml', async (req, res) => { try { const host = `${req.protocol}://${req.get('host')}`; res.set('Cache-Control', 'no-store') res.type('application/xml'); let xmltvSettings = db['xmltv-settings'].find()[0]; const fileContent = await fs.readFileSync(xmltvSettings.file, 'utf8'); const fileFinal = fileContent.replace(/\{\{host\}\}/g, host); res.send(fileFinal); } catch(err) { console.error(err); res.status(500).send("error"); } }) //tool services router.post('/api/channel-tools/time-slots', async (req, res) => { try { let toolRes = await timeSlotsService(req.body.programs, req.body.schedule); if ( typeof(toolRes.userError) !=='undefined') { console.error("time slots error: " + toolRes.userError); return res.status(400).send(toolRes.userError); } await streamToolResult(toolRes, res); } catch(err) { console.error(err); res.status(500).send("Internal error"); } }); router.post('/api/channel-tools/random-slots', async (req, res) => { try { let toolRes = await randomSlotsService(req.body.programs, req.body.schedule); if ( typeof(toolRes.userError) !=='undefined') { console.error("random slots error: " + toolRes.userError); return res.status(400).send(toolRes.userError); } await streamToolResult(toolRes, res); } catch(err) { console.error(err); res.status(500).send("Internal error"); } }); // CHANNELS.M3U Download router.get('/api/channels.m3u', async (req, res) => { try { res.type('text'); const host = `${req.protocol}://${req.get('host')}`; const data = await m3uService.getChannelList(host); res.send(data); } catch(err) { console.error(err); res.status(500).send("error"); } }) function updateXmltv() { xmltvInterval.updateXML() xmltvInterval.restartInterval() } async function streamToolResult(toolRes, res) { let programs = toolRes.programs; delete toolRes.programs; let s = JSON.stringify(toolRes); s = s.slice(0, -1); res.writeHead(200, { 'Content-Type': 'application/json' }); let transformStream = JSONStream.stringify( s + ',"programs":[', ',' , ']}'); transformStream.pipe(res); for (let i = 0; i < programs.length; i++) { transformStream.write( programs[i] ); await throttle(); } transformStream.end(); } return router }