mirror of
https://github.com/yhqe/jellycord.git
synced 2026-10-03 05:46:25 +00:00
added fallback cover art for songs with no cover art
This commit is contained in:
1 parent
c146e4b9c6
commit
42960d4e28
4 files changed
+305
-56
No files matched your search
@@ -9,8 +9,8 @@ const client = new Client({
|
||||
clientId: config.discordClientId,
|
||||
});
|
||||
|
||||
const albumArtCache = new Map(); // Cache for uploaded album art URLs
|
||||
const CACHE_FILE = 'art_cache.json'; // The file to store our cache
|
||||
const albumArtCache = new Map();
|
||||
const CACHE_FILE = 'art_cache.json';
|
||||
let lastTrackId = null;
|
||||
|
||||
async function loadCache() {
|
||||
@@ -18,26 +18,24 @@ async function loadCache() {
|
||||
const data = await fs.readFile(CACHE_FILE, 'utf-8');
|
||||
|
||||
if (!data) {
|
||||
console.log(`[Cache] ${CACHE_FILE} is empty. A new cache will be created.`);
|
||||
console.log(`[cache] ${CACHE_FILE} is empty! a new cache will be created...`);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(data);
|
||||
|
||||
// --- THE FIX ---
|
||||
// Instead of reassigning albumArtCache, clear the existing map
|
||||
// and add the loaded entries to it. This preserves the global reference.
|
||||
// the fix...
|
||||
albumArtCache.clear();
|
||||
for (const [key, value] of Object.entries(parsed)) {
|
||||
albumArtCache.set(key, value);
|
||||
}
|
||||
|
||||
console.log(`[Cache] Loaded ${albumArtCache.size} items from ${CACHE_FILE}`);
|
||||
console.log(`[cache] loaded ${albumArtCache.size} items from ${CACHE_FILE}...`);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
console.log(`[Cache] ${CACHE_FILE} not found. A new cache will be created.`);
|
||||
console.log(`[cache] ${CACHE_FILE} not found. a new cache will be created!`);
|
||||
} else {
|
||||
console.warn(`[Cache] Warning: Failed to read or parse ${CACHE_FILE}. Starting with a fresh cache. Error: ${error.message}`);
|
||||
console.warn(`[cache] warning!! failed to read or parse ${CACHE_FILE}. starting with a fresh cache! error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,28 +44,26 @@ async function saveCache() {
|
||||
try {
|
||||
const dataToSave = Object.fromEntries(albumArtCache);
|
||||
await fs.writeFile(CACHE_FILE, JSON.stringify(dataToSave, null, 2));
|
||||
// This is a useful one-time log, so we keep it.
|
||||
console.log(`[Cache] Successfully saved cache with ${albumArtCache.size} items.`);
|
||||
console.log(`[cache] successfully saved cache with ${albumArtCache.size} items!`);
|
||||
} catch (error) {
|
||||
console.error('[Cache] Failed to save cache:', error);
|
||||
console.error('[cache] failed to save cache!! ', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads an image to Catbox.moe by manually constructing the multipart/form-data request.
|
||||
* This avoids using the 'form-data' library which was causing conflicts.
|
||||
* @param {string} imageUrl The local URL of the image to upload.
|
||||
* @returns {Promise<string|null>} The public URL of the uploaded image, or null on failure.
|
||||
*/
|
||||
async function uploadImageToHost(imageUrl) {
|
||||
try {
|
||||
const imageResponse = await axios.get(imageUrl, {
|
||||
responseType: 'arraybuffer',
|
||||
validateStatus: () => true, // mmmmmm
|
||||
});
|
||||
|
||||
if (imageResponse.status === 404) {
|
||||
return null;
|
||||
}
|
||||
const imageBuffer = Buffer.from(imageResponse.data);
|
||||
|
||||
if (imageBuffer.length === 0) {
|
||||
console.error('[Album Art] Downloaded image is empty. Aborting upload.');
|
||||
console.error('[covers] downloaded image is empty :( aborting upload!');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -89,20 +85,20 @@ async function uploadImageToHost(imageUrl) {
|
||||
});
|
||||
|
||||
if (uploadResponse.status === 200 && uploadResponse.data.startsWith('http')) {
|
||||
console.log(`[Album Art] Uploaded to: ${uploadResponse.data}`);
|
||||
console.log(`[covers] success! uploaded to: ${uploadResponse.data}`);
|
||||
return uploadResponse.data;
|
||||
} else {
|
||||
console.error('[Album Art] Upload failed. Catbox.moe responded with:');
|
||||
console.error('[covers] upload failed!! catbox responded with:');
|
||||
console.error(` - Status: ${uploadResponse.status}, Data: ${uploadResponse.data}`);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[Album Art] A network error occurred during upload: ${error.message}`);
|
||||
console.error(`[covers] a network error occurred during upload!! ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getJellyfinSession() {
|
||||
async function getjellyfinSession() {
|
||||
try {
|
||||
const response = await axios.get(`${config.jellyfinServerUrl}/Sessions`, {
|
||||
headers: { 'X-Emby-Token': config.jellyfinApiKey }
|
||||
@@ -117,7 +113,7 @@ async function getJellyfinSession() {
|
||||
);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ECONNREFUSED') {
|
||||
console.error(`[Jellyfin] Error fetching session: ${error.message}`);
|
||||
console.error(`[jellyfin] error fetching session!! ${error.message}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -125,14 +121,15 @@ async function getJellyfinSession() {
|
||||
|
||||
async function getAlbumArtUrl(albumId, trackId) {
|
||||
const artId = albumId || trackId;
|
||||
if (!artId) return 'jellyfin_logo';
|
||||
if (!artId) return 'disc';
|
||||
|
||||
if (albumArtCache.has(artId)) {
|
||||
return albumArtCache.get(artId);
|
||||
}
|
||||
|
||||
// This log is important because it only happens once per new album/single.
|
||||
console.log(`[Album Art] New item detected (ID: ${artId}). Proceeding to upload...`);
|
||||
albumArtCache.set(artId, 'disc'); // fallback so it doesnt spam the console with "uplloading"
|
||||
|
||||
console.log(`[covers] new item detected!! (id: ${artId}) proceeding to upload...`);
|
||||
const localArtUrl = `${config.jellyfinServerUrl}/Items/${artId}/Images/Primary`;
|
||||
const publicUrl = await uploadImageToHost(localArtUrl);
|
||||
|
||||
@@ -140,13 +137,15 @@ async function getAlbumArtUrl(albumId, trackId) {
|
||||
albumArtCache.set(artId, publicUrl);
|
||||
await saveCache();
|
||||
return publicUrl;
|
||||
} else {
|
||||
console.log(`[covers] no art found for ${artId}, using fallback`);
|
||||
}
|
||||
|
||||
return 'jellyfin_logo';
|
||||
return 'disc';
|
||||
}
|
||||
|
||||
async function updatePresence() {
|
||||
const session = await getJellyfinSession();
|
||||
const session = await getjellyfinSession();
|
||||
|
||||
if (session && session.NowPlayingItem && session.PlayState && !session.PlayState.IsPaused) {
|
||||
const item = session.NowPlayingItem;
|
||||
@@ -157,19 +156,19 @@ async function updatePresence() {
|
||||
lastTrackId = currentId;
|
||||
|
||||
if (itemType === 'Audio') {
|
||||
console.log(`[Discord] Now listening to: ${(item.Artists || []).join(', ')} - ${item.Name}`);
|
||||
console.log(`[discord] now listening to: ${(item.Artists || []).join(', ')} - ${item.Name}`);
|
||||
} else if (itemType === 'Movie') {
|
||||
console.log(`[Discord] Now watching movie: ${item.Name}`);
|
||||
console.log(`[discord] now watching movie: ${item.Name}`);
|
||||
} else if (itemType === 'Episode') {
|
||||
console.log(`[Discord] Now watching TV: ${item.SeriesName || 'Unknown Show'} - ${item.Name}`);
|
||||
console.log(`[discord] now watching TV: ${item.SeriesName || 'Unknown Show'} - ${item.Name}`);
|
||||
} else {
|
||||
console.log(`[Discord] Now playing: ${item.Name || itemType}`);
|
||||
console.log(`[discord] now playing: ${item.Name || itemType}`);
|
||||
}
|
||||
}
|
||||
|
||||
let details = item.Name || 'Playing media';
|
||||
let state = '';
|
||||
let largeImageText = 'Jellyfin';
|
||||
let largeImageText = 'jellyfin';
|
||||
let artAlbumId = null;
|
||||
let artTrackId = item.Id;
|
||||
|
||||
@@ -193,14 +192,14 @@ async function updatePresence() {
|
||||
}
|
||||
|
||||
const largeImageUrl = await getAlbumArtUrl(artAlbumId, artTrackId);
|
||||
|
||||
// console.log('[discord] setting activity for user:', client.user?.username); sjhut up prick
|
||||
client.user?.setActivity({
|
||||
details,
|
||||
state,
|
||||
largeImageKey: largeImageUrl,
|
||||
largeImageText,
|
||||
smallImageKey: 'jellyfin_logo',
|
||||
smallImageText: 'Jellyfin',
|
||||
smallImageText: 'jellyfin',
|
||||
startTimestamp: Date.now() - Math.floor(session.PlayState.PositionTicks / 10000),
|
||||
endTimestamp: Date.now() + Math.floor((item.RunTimeTicks - session.PlayState.PositionTicks) / 10000),
|
||||
type: itemType === 'Audio' ? 2 : 3
|
||||
@@ -208,7 +207,7 @@ async function updatePresence() {
|
||||
|
||||
} else {
|
||||
if (lastTrackId !== null) {
|
||||
console.log('[Discord] Playback stopped or paused. Clearing presence.');
|
||||
console.log('[discord] playback stopped or paused... clearing presence');
|
||||
lastTrackId = null;
|
||||
client.user?.clearActivity();
|
||||
}
|
||||
@@ -216,35 +215,41 @@ async function updatePresence() {
|
||||
}
|
||||
|
||||
client.on('ready', () => {
|
||||
console.log(`[Discord] RPC connected for user ${client.user.username}`);
|
||||
console.log('[Jellyfin] Monitoring for listening activity...');
|
||||
console.log(`[discord] rpc connected for user ${client.user.username}`);
|
||||
console.log('[jellyfin] monitoring for listening activity...');
|
||||
updatePresence();
|
||||
setInterval(updatePresence, 2 * 1000);
|
||||
});
|
||||
|
||||
client.on('disconnected', () => {
|
||||
console.log('[Discord] RPC disconnected. Will try to reconnect if the script is restarted.');
|
||||
console.log('[discord] rpc disconnected. will try to reconnect if the script is restarted!');
|
||||
});
|
||||
|
||||
// --- NEW: Connection logic with a 15-second timeout ---
|
||||
async function connectToDiscord() {
|
||||
await loadCache(); // --- MODIFIED: Load cache before starting ---
|
||||
console.log('[Discord] Connecting to RPC...');
|
||||
await loadCache(); // IMPORTANT FOR covers DO NOT TOUCH!@!!!!!!!!@
|
||||
console.log('[discord] connecting to rpc...');
|
||||
try {
|
||||
// Create a timeout promise
|
||||
const timeout = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Connection timed out after 15 seconds.')), 15000)
|
||||
setTimeout(() => reject(new Error('connection timed out after 15 seconds!!')), 15000)
|
||||
);
|
||||
// Race the connection against the timeout
|
||||
await Promise.race([client.login(), timeout]);
|
||||
} catch (err) {
|
||||
console.error(`\n[Discord] Failed to connect: ${err.message}`);
|
||||
console.error("Please check the following:");
|
||||
console.error(" 1. Is the Discord desktop application running?");
|
||||
console.error(" 2. Is the 'discordClientId' in config.js correct?");
|
||||
console.error(" 3. Go to Discord Settings > Activity Privacy > 'Share your activity status by default' and ensure it's enabled.");
|
||||
process.exit(1); // Exit the script so it doesn't run in a broken state
|
||||
console.error(`\n[discord] failed to connect: ${err.message}`);
|
||||
console.error("please check the following");
|
||||
console.error(" 1. is the discord client running?");
|
||||
console.error(" 2. is the 'discordClientId' in config.js correct?");
|
||||
console.error(" 3. go to discord settings > 'Activity Privacy' > 'Share your activity status by default' and ensure it's enabled");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
connectToDiscord();
|
||||
connectToDiscord();
|
||||
|
||||
async function shutdown() {
|
||||
console.log('[discord] shutting down, clearing presence...');
|
||||
await client.user?.clearActivity();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.on('SIGINT', shutdown);
|
||||
process.on('SIGTERM', shutdown);
|
||||
Reference in new issue
Block a user