const { app, BrowserWindow, ipcMain } = require('electron'); const { exec } = require('child_process'); const axios = require('axios'); const Store = require('electron-store').default; // Ensure .default is used for ES module compatibility if needed const store = new Store(); let win; let lastSong = ""; // Load stored otherUsername if available. let otherUsername = store.get('otherUsername', ""); const username = "brandon"; // Your own username const apiUrl = "https://ms.bbrunson.com"; // Your API endpoint // ------------------------------ // Main Page HTML // ------------------------------ // Function to generate main page HTML dynamically based on otherUsername state function getMainPageHTML() { return ` MusicShare Electron
My Profile Picture
Now Playing: Waiting for song...
${otherUsername ? `Other Profile Picture` : '
Click profile picture to set tracked user.
'}
${otherUsername ? "Other user's song: Loading..." : "No other user set"}
`; } // ------------------------------ // Settings Page HTML (REMOVED) // ------------------------------ // function getSettingsPageHTML() { ... } // This function is no longer needed // ------------------------------ // Profile Page HTML (for both own and other user) // ------------------------------ // Added currentOtherUsername parameter function getProfilePageHTML(user, isOwn, currentOtherUsername) { const displayUser = isOwn ? user : (currentOtherUsername || "Not Set"); // Show current tracked user or "Not Set" const pfpUser = isOwn ? user : currentOtherUsername; // Use actual username for PFP if set // --- NEW: Fetch color logic for profile page PFP --- const profilePfpColorScript = pfpUser ? ` fetch(\`\${apiUrl}/pfpColor/${pfpUser}\`) .then(response => response.ok ? response.json() : Promise.reject('Failed to fetch')) .then(data => { if (data && data.colorHexData) { const pfpContainer = document.getElementById('profilePicContainer'); if(pfpContainer) pfpContainer.style.borderColor = data.colorHexData; } }) .catch(err => console.error('Error fetching profile pfp color:', err)); ` : ''; // --- END NEW --- return ` Profile - ${displayUser}
← Back

${displayUser}

Loading bio...

Loading profile song...

${!isOwn ? `

` : ''} ${isOwn ? `





` : ''}
`; } // ------------------------------ // Main Window & IPC handlers // ------------------------------ function createWindow() { win = new BrowserWindow({ width: 400, height: 800, webPreferences: { nodeIntegration: true, contextIsolation: false, // Keep false if using require in renderer as shown // preload: path.join(__dirname, 'preload.js') // Consider using preload for better security later } }); // win.webContents.openDevTools(); win.removeMenu(); // Remove menu bar for cleaner UI win.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(getMainPageHTML())); // Use dynamic HTML generation // Handle window close event if needed win.on('closed', () => { win = null; // Dereference the window object }); } // REMOVED: ipcMain.on('open-settings', ...); ipcMain.on('back-to-main', () => { if (win && !win.isDestroyed()) { // Reload the main page HTML dynamically win.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(getMainPageHTML())); } }); ipcMain.on('set-other-user', (event, user) => { const oldUsername = otherUsername; otherUsername = user.trim(); // Store the new username (trim whitespace) store.set('otherUsername', otherUsername); console.log("Other username set to:", otherUsername); // Optionally, send a message back to the renderer process (profile page) to confirm event.reply('other-user-set-confirm', { success: true, newUser: otherUsername }); // Send update to main window's renderer process if (win && !win.isDestroyed() && win.webContents) { // No need to check URL, send the update regardless. The main page renderer script will handle it if it's active. win.webContents.send('other-user-updated', otherUsername); } // If the username actually changed, trigger an immediate check for the new user's song if (otherUsername !== oldUsername) { checkOtherUserSong(); // Check the new user's song immediately } }); ipcMain.on('open-profile', (event, data) => { // data: { user, isOwn } // When opening the 'other' profile, 'user' might be empty if none is set yet. // We pass the current `otherUsername` from the main process to the HTML generator. if (win && !win.isDestroyed()) { // Pass the ACTUAL user associated with the profile being opened (data.user) // and the currently tracked otherUsername (needed for the input field on the 'other' profile page) win.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(getProfilePageHTML(data.user, data.isOwn, otherUsername))); } }); app.whenReady().then(() => { createWindow(); // Initial check checkNowPlaying(); // Existing polling for currently playing song (and fetching other user's song info) remains: setInterval(checkNowPlaying, 5000); // Check every 5 seconds app.on('activate', () => { // On macOS 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 (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); // ------------------------------ // Existing functions for song updates (modified checkNowPlaying) // ------------------------------ function lookupAppleMusicInfo(title, artist) { // Use try-catch for robustness try { const query = encodeURIComponent(`${artist} ${title}`); const url = `https://itunes.apple.com/search?term=${query}&entity=song&limit=1`; return axios.get(url) .then(response => { if (response.data.results && response.data.results.length > 0) { const result = response.data.results[0]; return { trackId: result.trackId?.toString() || "", // Handle potential missing ID albumArt: result.artworkUrl100 ? result.artworkUrl100.replace('100x100', '512x512') : "" // Handle missing art }; } return { trackId: "", albumArt: "" }; }) .catch(err => { console.error("Error looking up Apple Music info:", err.message); return { trackId: "", albumArt: "" }; }); } catch (error) { console.error("Error constructing Apple Music lookup URL:", error); return Promise.resolve({ trackId: "", albumArt: "" }); // Return a resolved promise } } function lookupAppleMusicInfoById(trackId) { if (!trackId) { return Promise.resolve({ albumArt: "" }); // No ID, no lookup } try { const url = `https://itunes.apple.com/lookup?id=${trackId}`; return axios.get(url) .then(response => { if (response.data.results && response.data.results.length > 0) { const result = response.data.results[0]; return { albumArt: result.artworkUrl100 ? result.artworkUrl100.replace('100x100', '512x512') : "" // Handle missing art }; } return { albumArt: "" }; }) .catch(err => { console.error("Error looking up Apple Music info by ID:", err.message); return { albumArt: "" }; }); } catch (error) { console.error("Error constructing Apple Music lookup URL by ID:", error); return Promise.resolve({ albumArt: "" }); // Return a resolved promise } } // Split checking own song and other user's song function checkOwnSong() { return new Promise((resolve, reject) => { exec(`powershell -ExecutionPolicy Bypass -File get-media.ps1`, (error, stdout) => { if (error) { console.error("Error executing PowerShell:", error); // Don't reject, just resolve with no update status resolve({ updated: false }); return; } try { const data = JSON.parse(stdout); if (!data.title && !data.artist) { // Check for empty strings specifically if (lastSong !== "No song playing") { lastSong = "No song playing"; updateMySong("No song playing", "", ""); sendSongUpdate("", "", ""); // Send empty update to clear server status resolve({ updated: true }); } else { resolve({ updated: false }); // No change } } else { const currentSong = `${data.title} - ${data.artist}`; if (currentSong !== lastSong) { lastSong = currentSong; lookupAppleMusicInfo(data.title, data.artist).then(info => { sendSongUpdate(data.title, data.artist, info.trackId); updateMySong(currentSong, info.albumArt, info.trackId); resolve({ updated: true, song: currentSong }); }); } else { resolve({ updated: false }); // No change } } } catch (err) { console.error("Error parsing PowerShell output:", stdout, err); resolve({ updated: false }); // Resolve anyway } }); }); } function checkOtherUserSong() { if (!otherUsername) { updateOtherSong("No other user set", ""); // Clear other user info if username is removed return Promise.resolve(); // Nothing to do } return axios.post(apiUrl, { action: "fetch", user: otherUsername }) .then(async response => { // Make async to use await const data = response.data; let otherSongText = `No song playing for ${otherUsername}`; let albumArtUrl = ""; let amId = ""; if (data && (data.title || data.artist)) { // Check if data exists and has title/artist otherSongText = `${data.title || 'Unknown Title'} - ${data.artist || 'Unknown Artist'}`; amId = data.amUri || ""; // Get AM ID if present if (amId) { const info = await lookupAppleMusicInfoById(amId); // Wait for lookup albumArtUrl = info.albumArt; // Optionally add AM ID to text: otherSongText += ` (AM ID: ${amId})`; } } else if (data && data.amUri) { // Handle case where only AM URI might be stored (e.g., from profile song) amId = data.amUri; const info = await lookupAppleMusicInfoById(amId); albumArtUrl = info.albumArt; // Try to get song name/artist from ID lookup if possible (Apple API might provide it) // This part is complex; for now, just show ID if title/artist missing otherSongText = `Song (AM ID: ${amId})`; // Placeholder text } updateOtherSong(otherSongText, albumArtUrl); }) .catch(err => { console.error(`Error fetching song info for ${otherUsername}:`, err.response ? err.response.data : err.message); // Display error state in UI for other user updateOtherSong(`Error loading song for ${otherUsername}`, ""); }); } // Combined check function called by interval async function checkNowPlaying() { await checkOwnSong(); // Wait for own song check to complete await checkOtherUserSong(); // Then check other user's song } function sendSongUpdate(title, artist, amUri) { axios.post(apiUrl, { action: "write", user: username, title: title || "", // Send empty strings if null/undefined artist: artist || "", amUri: amUri || "" }) .then(() => { // console.log(`Sent song update: ${title} by ${artist} (AM ID: ${amUri})`); // Less verbose logging }) .catch(error => { console.error("Error sending song update:", error.message); }); } // Removed getPfpColor as it's handled in the renderer now function updateMySong(songText, albumArtUrl, trackId) { if (win && !win.isDestroyed() && win.webContents) { const jsCode = ` (function() { const container = document.getElementById('mySongContainer'); const info = document.getElementById('mySongInfo'); if (container && info) { container.style.backgroundImage = "url('${albumArtUrl || ''}')"; info.textContent = "Now Playing: ${songText.replace(/'/g, "\\'")}${trackId ? `` : ''}"; // Removed AM ID display here for cleaner look } })(); `; win.webContents.executeJavaScript(jsCode).catch(e => console.error("Error executing JS for my song:", e)); } } function updateOtherSong(songText, albumArtUrl) { if (win && !win.isDestroyed() && win.webContents) { const jsCode = ` (function() { const container = document.getElementById('otherSongContainer'); const info = document.getElementById('otherSongInfo'); if (container && info) { // Only update background if URL is provided if ('${albumArtUrl || ''}') { container.style.backgroundImage = "url('${albumArtUrl}')"; } else { container.style.backgroundImage = "none"; // Clear background if no art } info.textContent = "${songText.replace(/'/g, "\\'")}"; } })(); `; win.webContents.executeJavaScript(jsCode).catch(e => console.error("Error executing JS for other song:", e)); } } app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } });