691 lines
29 KiB
JavaScript
691 lines
29 KiB
JavaScript
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
|
|
let username = store.get('username', ""); // Load username from store, default to "brandon" if not set
|
|
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 `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>MusicShare Electron</title>
|
|
<style>
|
|
body { font-family: sans-serif; margin: 0; padding: 20px; background: #222; color: #fff; }
|
|
.header { display: flex; justify-content: space-between; align-items: center; }
|
|
h1 { margin: 0; }
|
|
/* Removed .gear style */
|
|
.container { display: flex; flex-direction: column; align-items: center; }
|
|
.song-container { position: relative; width: 300px; height: 300px; margin: 20px; background-size: cover; background-position: center; border: 2px solid #fff; } /* Default border */
|
|
.song-info { position: absolute; bottom: 0; width: 100%; background: rgba(0,0,0,0.5); padding: 10px; text-align: center; font-size: 16px; }
|
|
.pfp { position: absolute; top: 5px; left: 5px; width: 50px; height: 50px; border-radius: 50%; cursor: pointer; border: 3px solid #fff; /* Default border, width adjusted */ object-fit: cover; /* Ensure image covers the area */ background-color: #555; /* BG if image fails */ }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
</div>
|
|
<div class="container">
|
|
<div id="mySongContainer" class="song-container">
|
|
<img id="myProfilePic" class="pfp" src="${apiUrl}/pfp/${username}" alt="My Profile Picture" onerror="this.style.visibility='hidden';">
|
|
<div class="song-info" id="mySongInfo">Now Playing: Waiting for song...</div>
|
|
</div>
|
|
<div id="otherSongContainer" class="song-container">
|
|
${otherUsername ? `<img id="otherProfilePic" class="pfp" src="${apiUrl}/pfp/${otherUsername}" alt="Other Profile Picture" onerror="this.style.visibility='hidden';">` : '<div style="padding: 10px; text-align: center;">Click profile picture to set tracked user.</div>'}
|
|
<div class="song-info" id="otherSongInfo">${otherUsername ? "Other user's song: Loading..." : "No other user set"}</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
const { ipcRenderer } = require('electron');
|
|
const localApiUrl = "${apiUrl}"; // Make apiUrl available in this script scope
|
|
const localUsername = "${username}";
|
|
let localOtherUsername = "${otherUsername || ''}"; // Keep track locally
|
|
|
|
// --- NEW: Function to fetch color and apply border ---
|
|
async function fetchAndApplyColor(user, elementId) {
|
|
if (!user) return; // Don't fetch if username is empty
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return; // Don't fetch if element doesn't exist
|
|
|
|
try {
|
|
const response = await fetch(\`\${localApiUrl}/pfpColor/\${user}\`);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
if (data && data.colorHexData) {
|
|
element.style.borderColor = data.colorHexData;
|
|
console.log(\`Set border color for \${user} (\${elementId}) to \${data.colorHexData}\`);
|
|
} else {
|
|
element.style.borderColor = '#fff'; // Reset to default if no color found
|
|
console.warn(\`No color found for \${user}, using default border.\`);
|
|
}
|
|
} else {
|
|
console.error(\`Failed to fetch color for \${user}. Status: \${response.status}\`);
|
|
element.style.borderColor = '#fff'; // Reset to default on error
|
|
}
|
|
} catch (error) {
|
|
console.error(\`Error fetching or applying color for \${user}:\`, error);
|
|
element.style.borderColor = '#fff'; // Reset to default on error
|
|
}
|
|
}
|
|
// --- END NEW ---
|
|
|
|
// --- Run color fetch on initial load ---
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
fetchAndApplyColor(localUsername, 'myProfilePic');
|
|
if (localOtherUsername) {
|
|
fetchAndApplyColor(localOtherUsername, 'otherProfilePic');
|
|
}
|
|
});
|
|
// --- END ---
|
|
|
|
// Open my profile page when my profile picture is clicked.
|
|
document.getElementById('myProfilePic').addEventListener('click', () => {
|
|
ipcRenderer.send('open-profile', { user: localUsername, isOwn: true });
|
|
});
|
|
|
|
// Open other user's profile if set, otherwise open it to allow setting the user.
|
|
const otherPfp = document.getElementById('otherProfilePic');
|
|
const otherContainer = document.getElementById('otherSongContainer');
|
|
|
|
const openOtherProfile = () => {
|
|
// Always open the profile for the 'other' user slot, passing the current tracked username (or empty)
|
|
ipcRenderer.send('open-profile', { user: localOtherUsername, isOwn: false });
|
|
};
|
|
|
|
if (otherPfp) {
|
|
otherPfp.addEventListener('click', openOtherProfile);
|
|
} else {
|
|
// If there's no PFP (meaning no otherUsername is set), make the container clickable
|
|
otherContainer.style.cursor = 'pointer';
|
|
otherContainer.addEventListener('click', openOtherProfile);
|
|
}
|
|
|
|
// Function to update UI elements when otherUsername changes
|
|
ipcRenderer.on('other-user-updated', (event, newOtherUsername) => {
|
|
localOtherUsername = newOtherUsername; // Update local variable
|
|
const otherInfo = document.getElementById('otherSongInfo');
|
|
let otherPfpElement = document.getElementById('otherProfilePic'); // Use let
|
|
const container = document.getElementById('otherSongContainer');
|
|
|
|
if (newOtherUsername) {
|
|
otherInfo.innerText = "Other user's song: Loading...";
|
|
if (!otherPfpElement) {
|
|
// Add the img element if it doesn't exist
|
|
const img = document.createElement('img');
|
|
img.id = 'otherProfilePic';
|
|
img.className = 'pfp';
|
|
img.src = \`\${localApiUrl}/pfp/\${newOtherUsername}\`;
|
|
img.alt = 'Other Profile Picture';
|
|
img.onerror = function() { this.style.visibility='hidden'; }; // Add onerror handler
|
|
img.addEventListener('click', openOtherProfile); // Re-add listener
|
|
container.insertBefore(img, container.firstChild); // Add it inside
|
|
container.style.cursor = 'default'; // Reset cursor
|
|
// Remove the placeholder text if it exists
|
|
const placeholder = container.querySelector('div[style*="padding"]');
|
|
if(placeholder) placeholder.remove();
|
|
otherPfpElement = img; // Assign the newly created element
|
|
|
|
// --- NEW: Fetch color for the new PFP ---
|
|
fetchAndApplyColor(newOtherUsername, 'otherProfilePic');
|
|
// --- END NEW ---
|
|
|
|
} else {
|
|
otherPfpElement.src = \`\${localApiUrl}/pfp/\${newOtherUsername}\`;
|
|
otherPfpElement.style.visibility = 'visible'; // Ensure it's visible
|
|
otherPfpElement.style.borderColor = '#fff'; // Reset border until fetched
|
|
const placeholder = container.querySelector('div[style*="padding"]');
|
|
if(placeholder) placeholder.remove();
|
|
|
|
// --- NEW: Fetch color for the updated PFP ---
|
|
fetchAndApplyColor(newOtherUsername, 'otherProfilePic');
|
|
// --- END NEW ---
|
|
}
|
|
|
|
// Ensure the click listener is on the pfp if it exists
|
|
if(otherPfpElement) {
|
|
container.style.cursor = 'default';
|
|
container.removeEventListener('click', openOtherProfile); // remove container listener
|
|
}
|
|
|
|
} else {
|
|
otherInfo.innerText = "No other user set";
|
|
if (otherPfpElement) {
|
|
otherPfpElement.remove(); // Remove the pfp image
|
|
}
|
|
if (!container.querySelector('div[style*="padding"]')) {
|
|
const placeholder = document.createElement('div');
|
|
placeholder.style.padding = '10px';
|
|
placeholder.style.textAlign = 'center';
|
|
placeholder.innerText = 'Click here to set tracked user.';
|
|
container.insertBefore(placeholder, otherInfo); // Add placeholder text
|
|
}
|
|
container.style.cursor = 'pointer'; // Make container clickable again
|
|
container.addEventListener('click', openOtherProfile);
|
|
}
|
|
});
|
|
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
// ------------------------------
|
|
// 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 `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Profile - ${displayUser}</title>
|
|
<style>
|
|
body { font-family: sans-serif; margin: 0; padding: 0; background: #222; color: #fff; }
|
|
.banner { position: relative; width: 100%; height: 250px; overflow: hidden; background: #444; /* Default background */ }
|
|
.banner img, .banner video { width: 100%; height: 100%; object-fit: cover; }
|
|
.pfp-container { position: absolute; top: 10px; left: 10px; width: 80px; height: 80px; border: 3px solid #fff; border-radius: 50%; overflow: hidden; cursor: ${isOwn ? 'pointer' : 'default'}; background: #555; /* Default bg for pfp */ } /* Default border */
|
|
.pfp-container img { width: 100%; height: 100%; display: block; /* Prevents small gap */ object-fit: cover; }
|
|
.profile-info { padding: 20px; }
|
|
.editable { background: #333; padding: 10px; margin-bottom: 10px; border-radius: 5px; }
|
|
.back { cursor: pointer; padding: 10px; color: #fff; font-size: 24px; position: absolute; top: 5px; right: 10px; z-index: 10; }
|
|
button { padding: 5px 10px; font-size: 16px; margin-top: 5px; cursor: pointer; }
|
|
input[type="text"], input[type="color"], textarea { padding: 5px; font-size: 16px; width: calc(100% - 12px); margin-top: 5px; background: #444; border: 1px solid #555; color: #fff; }
|
|
label { display: block; margin-bottom: 3px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="back" id="backBtn">← Back</div>
|
|
<div class="banner" id="bannerArea">
|
|
${pfpUser ? `
|
|
<div class="pfp-container" id="profilePicContainer">
|
|
<img id="profilePic" src="${apiUrl}/pfp/${pfpUser}" alt="Profile Picture" onerror="this.style.display='none'; this.parentElement.style.background='#555';">
|
|
</div>` : `
|
|
<div class="pfp-container" id="profilePicContainer">
|
|
</div>`}
|
|
</div>
|
|
<div class="profile-info">
|
|
<h2 id="usernameDisplay">${displayUser}</h2>
|
|
<p id="profileBio">Loading bio...</p>
|
|
<p id="profileSong">Loading profile song...</p>
|
|
|
|
${!isOwn ? `
|
|
<div class="editable">
|
|
<label for="otherUsernameInput">Tracked Username:</label>
|
|
<input type="text" id="otherUsernameInput" placeholder="Enter username to track" value="${currentOtherUsername || ''}">
|
|
<button id="saveOtherUsername">Save Tracked User</button>
|
|
<p id="saveStatus" style="margin-top: 5px; font-size: 14px;"></p>
|
|
</div>
|
|
` : ''}
|
|
|
|
${isOwn ? `
|
|
<div class="editable">
|
|
<label>Change Bio:</label>
|
|
<textarea id="bioInput" rows="3" cols="40"></textarea><br>
|
|
<button id="saveBio">Save Bio</button>
|
|
</div>
|
|
<div class="editable">
|
|
<label>Change Profile Song:</label>
|
|
<input type="text" id="songInput" placeholder="Enter song ID"><br>
|
|
<button id="saveSong">Save Profile Song</button>
|
|
</div>
|
|
<div class="editable">
|
|
<label>Change Profile Picture:</label>
|
|
<input type="file" id="pfpInput" accept="image/*"><br>
|
|
<button id="uploadPfp">Upload PFP</button>
|
|
</div>
|
|
<div class="editable">
|
|
<label>Change Banner:</label>
|
|
<input type="file" id="bannerInput" accept="image/*,video/mp4"><br>
|
|
<button id="uploadBanner">Upload Banner</button>
|
|
</div>
|
|
<div class="editable">
|
|
<label>Set Accent Color:</label>
|
|
<input type="color" id="pfpColorInput"><br>
|
|
<button id="savePfpColor">Save Color</button>
|
|
</div>
|
|
<div class="editable">
|
|
<label>Set Accent Color:</label>
|
|
<input type="color" id="pfpColorInput"><br>
|
|
<button id="savePfpColor">Save Color</button>
|
|
</div>
|
|
<div class="editable">
|
|
<label>Change Username:</label>
|
|
<input type="text" id="usernameInput" placeholder="Enter new username" value="${username}">
|
|
<button id="saveUsername">Save Username</button>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
|
|
<script>
|
|
const apiUrl = "${apiUrl}"; // Ensure apiUrl is available for profile.js context if needed via global scope or pass explicitly
|
|
// Assume profile.js is in the same directory or accessible
|
|
// Using require here relies on nodeIntegration: true and contextIsolation: false
|
|
try {
|
|
const { loadProfile, setupEventListeners } = require('./profile.js');
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const userToLoad = "${isOwn ? user : currentOtherUsername}"; // Load data for the actual user if set
|
|
if (userToLoad) {
|
|
loadProfile(userToLoad, ${isOwn});
|
|
// --- NEW: Execute profile pfp color fetch ---
|
|
${profilePfpColorScript}
|
|
// --- END NEW ---
|
|
} else {
|
|
// Handle case where no other user is set yet - maybe clear fields
|
|
document.getElementById('profileBio').innerText = "Bio: Not available";
|
|
document.getElementById('profileSong').innerText = "Profile Song: Not available";
|
|
// Clear banner/pfp if needed
|
|
const bannerArea = document.getElementById('bannerArea');
|
|
const pfpContainer = document.getElementById('profilePicContainer');
|
|
if (pfpContainer) pfpContainer.innerHTML = ''; // Clear PFP content
|
|
}
|
|
// Pass the currentOtherUsername to setupEventListeners
|
|
setupEventListeners("${user}", ${isOwn}, "${currentOtherUsername || ''}");
|
|
});
|
|
} catch (e) {
|
|
console.error("Failed to load profile script:", e);
|
|
alert("Error loading profile functionality. Please ensure profile.js is present.");
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
|
|
// ------------------------------
|
|
// Main Window & IPC handlers
|
|
// ------------------------------
|
|
function createWindow() {
|
|
if (!username) {
|
|
promptForUsername();
|
|
} else {
|
|
startMainApp();
|
|
}
|
|
}
|
|
|
|
|
|
// REMOVED: ipcMain.on('open-settings', ...);
|
|
|
|
function promptForUsername() {
|
|
let promptWin = new BrowserWindow({
|
|
width: 300,
|
|
height: 220,
|
|
resizable: false,
|
|
frame: false,
|
|
alwaysOnTop: true,
|
|
webPreferences: {
|
|
nodeIntegration: true,
|
|
contextIsolation: false,
|
|
}
|
|
});
|
|
|
|
promptWin.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(`
|
|
<html>
|
|
<head>
|
|
<title>Set Username</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; text-align: center; background: #222; color: #fff; }
|
|
.container { padding: 20px; }
|
|
input { width: 80%; padding: 8px; margin-top: 10px; background: #444; border: 1px solid #555; color: #fff; }
|
|
button { padding: 8px 15px; margin-top: 10px; cursor: pointer; background: #007BFF; color: white; border: none; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h2>Enter a Username</h2>
|
|
<input type="text" id="usernameInput" placeholder="Your username">
|
|
<button onclick="setUsername()">Save</button>
|
|
</div>
|
|
<script>
|
|
const { ipcRenderer } = require('electron');
|
|
function setUsername() {
|
|
const username = document.getElementById('usernameInput').value.trim();
|
|
if (username) {
|
|
ipcRenderer.send('set-initial-username', username);
|
|
} else {
|
|
alert("Username cannot be empty.");
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`));
|
|
|
|
ipcMain.once('set-initial-username', (event, newUsername) => {
|
|
username = newUsername;
|
|
store.set('username', username);
|
|
promptWin.close();
|
|
startMainApp();
|
|
});
|
|
}
|
|
|
|
function startMainApp() {
|
|
win = new BrowserWindow({
|
|
width: 400,
|
|
height: 800,
|
|
webPreferences: {
|
|
nodeIntegration: true,
|
|
contextIsolation: false,
|
|
}
|
|
});
|
|
|
|
win.removeMenu();
|
|
win.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(getMainPageHTML()));
|
|
win.on('closed', () => {
|
|
win = null;
|
|
});
|
|
}
|
|
|
|
|
|
ipcMain.on('back-to-main', () => {
|
|
if (win && !win.isDestroyed()) {
|
|
lastSong = ""; // Reset last song so UI updates on reload
|
|
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)));
|
|
}
|
|
});
|
|
|
|
ipcMain.on('set-username', (event, newUsername) => {
|
|
if (!newUsername.trim()) {
|
|
event.reply('username-set-confirm', { success: false, message: "Username cannot be empty." });
|
|
return;
|
|
}
|
|
username = newUsername.trim();
|
|
store.set('username', username);
|
|
console.log("Username changed to:", username);
|
|
|
|
event.reply('username-set-confirm', { success: true, newUsername: username });
|
|
|
|
// Reload profile with updated username
|
|
if (win && !win.isDestroyed()) {
|
|
win.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(getProfilePageHTML(username, true, 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();
|
|
}
|
|
}); |