mirror of
https://github.com/yhqe/jellycord.git
synced 2026-10-03 05:46:25 +00:00
f
This commit is contained in:
1 parent
616879127e
commit
281ccfb2fe
5 files changed
+133
-5
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
# Dependencies
|
# Dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
config.js
|
config.js
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"b0e14b847e862ba4872e85211fcc7734": "https://files.catbox.moe/w9sqhb.jpg",
|
||||||
|
"cebf9979b121c2a246b517b5bc0ade5e": "https://files.catbox.moe/44ta2m.jpg",
|
||||||
|
"6aeacb78f346ceaf130b3ee9672bc9ab": "https://files.catbox.moe/p9bk6r.jpg",
|
||||||
|
"159c2bdcb53fe7d19a0c03106ecd55f4": "https://files.catbox.moe/qoj4oi.jpg"
|
||||||
|
}
|
||||||
@@ -3,13 +3,105 @@ import rpc from '@xhayper/discord-rpc';
|
|||||||
const { Client, Presence } = rpc;
|
const { Client, Presence } = rpc;
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import config from './config.js';
|
import config from './config.js';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
clientId: config.discordClientId,
|
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
|
||||||
let lastTrackId = null;
|
let lastTrackId = null;
|
||||||
|
|
||||||
|
async function loadCache() {
|
||||||
|
try {
|
||||||
|
const data = await fs.readFile(CACHE_FILE, 'utf-8');
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
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.
|
||||||
|
albumArtCache.clear();
|
||||||
|
for (const [key, value] of Object.entries(parsed)) {
|
||||||
|
albumArtCache.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.`);
|
||||||
|
} else {
|
||||||
|
console.warn(`[Cache] Warning: Failed to read or parse ${CACHE_FILE}. Starting with a fresh cache. Error: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.`);
|
||||||
|
} catch (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',
|
||||||
|
});
|
||||||
|
const imageBuffer = Buffer.from(imageResponse.data);
|
||||||
|
|
||||||
|
if (imageBuffer.length === 0) {
|
||||||
|
console.error('[Album Art] Downloaded image is empty. Aborting upload.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = imageResponse.headers['content-type'] || 'image/jpeg';
|
||||||
|
const filename = 'albumart.jpg';
|
||||||
|
|
||||||
|
const boundary = `----WebKitFormBoundary${Math.random().toString(16).slice(2)}`;
|
||||||
|
const bodyParts = [
|
||||||
|
`--${boundary}`, 'Content-Disposition: form-data; name="reqtype"', '', 'fileupload',
|
||||||
|
`--${boundary}`, `Content-Disposition: form-data; name="fileToUpload"; filename="${filename}"`, `Content-Type: ${contentType}`, '', '',
|
||||||
|
];
|
||||||
|
const bodyPrefix = Buffer.from(bodyParts.join('\r\n'));
|
||||||
|
const bodySuffix = Buffer.from(`\r\n--${boundary}--`);
|
||||||
|
const requestBody = Buffer.concat([bodyPrefix, imageBuffer, bodySuffix]);
|
||||||
|
|
||||||
|
const uploadResponse = await axios.post('https://catbox.moe/user/api.php', requestBody, {
|
||||||
|
headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}`, 'Content-Length': requestBody.length, },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (uploadResponse.status === 200 && uploadResponse.data.startsWith('http')) {
|
||||||
|
console.log(`[Album Art] Uploaded to: ${uploadResponse.data}`);
|
||||||
|
return uploadResponse.data;
|
||||||
|
} else {
|
||||||
|
console.error('[Album Art] Upload failed. Catbox.moe 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}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getJellyfinSession() {
|
async function getJellyfinSession() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${config.jellyfinServerUrl}/Sessions`, {
|
const response = await axios.get(`${config.jellyfinServerUrl}/Sessions`, {
|
||||||
@@ -28,18 +120,44 @@ async function getJellyfinSession() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getAlbumArtUrl(albumId, trackId) {
|
||||||
|
const artId = albumId || trackId;
|
||||||
|
if (!artId) return 'jellyfin_logo';
|
||||||
|
|
||||||
|
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...`);
|
||||||
|
const localArtUrl = `${config.jellyfinServerUrl}/Items/${artId}/Images/Primary`;
|
||||||
|
const publicUrl = await uploadImageToHost(localArtUrl);
|
||||||
|
|
||||||
|
if (publicUrl) {
|
||||||
|
albumArtCache.set(artId, publicUrl);
|
||||||
|
await saveCache();
|
||||||
|
return publicUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'jellyfin_logo';
|
||||||
|
}
|
||||||
|
|
||||||
async function updatePresence() {
|
async function updatePresence() {
|
||||||
const session = await getJellyfinSession();
|
const session = await getJellyfinSession();
|
||||||
|
|
||||||
if (session && session.NowPlayingItem) {
|
if (session && session.NowPlayingItem) {
|
||||||
const track = session.NowPlayingItem;
|
const track = session.NowPlayingItem;
|
||||||
const trackId = track.Id;
|
const trackId = track.Id;
|
||||||
|
const albumId = track.AlbumId;
|
||||||
|
|
||||||
if (trackId !== lastTrackId) {
|
if (trackId !== lastTrackId) {
|
||||||
lastTrackId = trackId;
|
lastTrackId = trackId;
|
||||||
console.log(`[Discord] Now listening to: ${track.Artists.join(', ')} - ${track.Name}`);
|
console.log(`[Discord] Now listening to: ${track.Artists.join(', ')} - ${track.Name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pass trackid for singles
|
||||||
|
const largeImageUrl = await getAlbumArtUrl(albumId, trackId);
|
||||||
|
|
||||||
/*const presence = new Presence()
|
/*const presence = new Presence()
|
||||||
.setDetails(`${track.Name}`)
|
.setDetails(`${track.Name}`)
|
||||||
.setState(`by ${track.Artists.join(', ')}`)
|
.setState(`by ${track.Artists.join(', ')}`)
|
||||||
@@ -55,7 +173,7 @@ async function updatePresence() {
|
|||||||
client.user?.setActivity({
|
client.user?.setActivity({
|
||||||
details: `${track.Name}`,
|
details: `${track.Name}`,
|
||||||
state: `by ${track.Artists.join(', ')}`,
|
state: `by ${track.Artists.join(', ')}`,
|
||||||
largeImageKey: `${config.jellyfinServerUrl}/Items/${track.AlbumId}/Images/Primary`,
|
largeImageKey: largeImageUrl,
|
||||||
largeImageText: `on ${track.Album}`,
|
largeImageText: `on ${track.Album}`,
|
||||||
smallImageKey: 'jellyfin_logo',
|
smallImageKey: 'jellyfin_logo',
|
||||||
smallImageText: 'Jellyfin',
|
smallImageText: 'Jellyfin',
|
||||||
@@ -82,7 +200,7 @@ client.on('ready', () => {
|
|||||||
console.log(`[Discord] RPC connected for user ${client.user.username}`);
|
console.log(`[Discord] RPC connected for user ${client.user.username}`);
|
||||||
console.log('[Jellyfin] Monitoring for listening activity...');
|
console.log('[Jellyfin] Monitoring for listening activity...');
|
||||||
updatePresence();
|
updatePresence();
|
||||||
setInterval(updatePresence, 15 * 1000);
|
setInterval(updatePresence, 2 * 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('disconnected', () => {
|
client.on('disconnected', () => {
|
||||||
@@ -91,6 +209,7 @@ client.on('disconnected', () => {
|
|||||||
|
|
||||||
// --- NEW: Connection logic with a 15-second timeout ---
|
// --- NEW: Connection logic with a 15-second timeout ---
|
||||||
async function connectToDiscord() {
|
async function connectToDiscord() {
|
||||||
|
await loadCache(); // --- MODIFIED: Load cache before starting ---
|
||||||
console.log('[Discord] Connecting to RPC...');
|
console.log('[Discord] Connecting to RPC...');
|
||||||
try {
|
try {
|
||||||
// Create a timeout promise
|
// Create a timeout promise
|
||||||
|
|||||||
Generated
+2
-1
@@ -10,7 +10,8 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xhayper/discord-rpc": "^1.1.2",
|
"@xhayper/discord-rpc": "^1.1.2",
|
||||||
"axios": "^1.7.2"
|
"axios": "^1.7.2",
|
||||||
|
"form-data": "^4.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@discordjs/collection": {
|
"node_modules/@discordjs/collection": {
|
||||||
|
|||||||
+3
-2
@@ -16,6 +16,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xhayper/discord-rpc": "^1.1.2",
|
"@xhayper/discord-rpc": "^1.1.2",
|
||||||
"axios": "^1.7.2"
|
"axios": "^1.7.2",
|
||||||
|
"form-data": "^4.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user