const { app, BrowserWindow, ipcMain, screen } = require('electron'); const { exec } = require('child_process'); const https = require('https'); const http = require('http'); const path = require('path'); let win; let lastSong = ""; let lastPaused = true; app.whenReady().then(() => { createWindow(); checkNowPlaying(); setInterval(checkNowPlaying, 2500); // Start polling for Plex now-playing details checkPlexNowPlaying(); setInterval(checkPlexNowPlaying, 3000); // Start an HTTP server to receive notification requests (POST /notify) http.createServer((req, res) => { if(req.method === 'POST' && req.url === '/notify'){ let body = ''; req.on('data', chunk => body += chunk); req.on('end', () => { try { const notifData = JSON.parse(body); if(win){ win.webContents.send('notification', notifData); } res.writeHead(200, {"Content-Type": "application/json"}); res.end(JSON.stringify({status:"ok"})); } catch(e){ console.error("Notification parse error:", e); res.writeHead(400); res.end(); } }); } else { res.writeHead(404); res.end(); } }).listen(3000, () => { console.log("Notification server listening on port 3000"); }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); function createWindow() { const displays = screen.getAllDisplays(); const externalDisplay = displays[3] || displays[0]; const x = externalDisplay.bounds.x; const y = externalDisplay.bounds.y; win = new BrowserWindow({ x: x, y: y, fullscreen: true, webPreferences: { nodeIntegration: true, contextIsolation: false, } }); win.removeMenu(); // win.webContents.openDevTools(); win.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(getMainPageHTML())); win.on('closed', () => { win = null; }); } async function checkNowPlaying() { const result = await checkOwnSong(); if (result.updated && win) { win.webContents.send('song-update', result.data); } } function checkOwnSong() { return new Promise((resolve) => { exec(`powershell -ExecutionPolicy Bypass -File get-media.ps1`, async (error, stdout) => { if (error) { console.error("PowerShell error:", error); resolve({ updated: false }); return; } try { const data = JSON.parse(stdout); // If there's NO valid data => user is paused/stopped if (!data.title && !data.artist) { // If we weren't already paused, now we are => update if (!lastPaused) { lastPaused = true; resolve({ updated: true, data: { paused: true } }); } else { // Still paused, no change resolve({ updated: false }); } } else { // We DO have valid song data => the user is playing something const currentSong = `${data.title} - ${data.artist}`; // If we were paused, or the song changed, send an update if (lastPaused || currentSong !== lastSong) { lastPaused = false; lastSong = currentSong; const albumArt = await fetchAlbumArt(data.title, data.artist); resolve({ updated: true, data: { paused: false, title: data.title, artist: data.artist, albumArt } }); } else { // No change in paused/playing state, no change in track resolve({ updated: false }); } } } catch (err) { console.error("JSON parse error:", stdout, err); resolve({ updated: false }); } }); }); } function checkPlexNowPlaying() { const plexToken = "kbGwoiA_QEGzw7MgSZrY"; // <-- replace with your Plex token const plexHost = "spyro.corp.bbrunson.com"; // <-- adjust if needed const url = `http://${plexHost}:32400/status/sessions?X-Plex-Token=${plexToken}`; http.get(url, (res) => { let data = ""; res.on('data', chunk => data += chunk); res.on('end', () => { // For simplicity we check for a known tag in the XML response. let nowPlaying = "No media playing"; if(data.includes("MediaContainer") && data.includes("Video")) { nowPlaying = "Plex is playing media"; } if(win){ win.webContents.send('plex-update', { details: nowPlaying }); } }); }).on('error', err => { console.error("Error fetching Plex now playing:", err); }); } function fetchAlbumArt(title, artist) { return new Promise((resolve) => { const query = encodeURIComponent(`${artist} ${title}`); const url = `https://itunes.apple.com/search?term=${query}&limit=1`; https.get(url, (res) => { let body = ''; res.on('data', chunk => (body += chunk)); res.on('end', () => { try { const results = JSON.parse(body).results; if (results.length > 0) { // Replace 100x100 with 300x300 for better quality const art = results[0].artworkUrl100.replace('100x100bb', '300x300bb'); resolve(art); } else { resolve("https://via.placeholder.com/100"); } } catch (e) { console.error("Album art fetch error:", e); resolve("https://via.placeholder.com/100"); } }); }).on('error', () => { resolve("https://via.placeholder.com/100"); }); }); } ipcMain.on('media-control', (event, command) => { if (command) { exec(`MediaControl.exe ${command}`, (error, stdout, stderr) => { if (error) { console.error(`Error running MediaControl.exe:`, error); } else { console.log(`Media command executed: ${stdout || command}`); } }); } }); ipcMain.on('minimize-app', (event) => { const window = BrowserWindow.getFocusedWindow(); if (window) window.minimize(); }); ipcMain.on('close-app', (event) => { win.close(); // or app.quit(); }); function getMainPageHTML() { return ` Now Playing
Plex Now Playing: Loading...
Album Art
Loading...
“Hello, is it me you’re looking for?”

`; }