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
Now Playing: Waiting for song...
${otherUsername ? `

` : '
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
`;
}
// ------------------------------
// 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();
}
});