diff --git a/art_cache.json b/art_cache.json index 014939c..18d2bf6 100644 --- a/art_cache.json +++ b/art_cache.json @@ -2,5 +2,23 @@ "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" + "159c2bdcb53fe7d19a0c03106ecd55f4": "https://files.catbox.moe/qoj4oi.jpg", + "c427b476da833ea38902de144a48a912": "https://files.catbox.moe/s07d8w.jpg", + "c27d831861ae709568da129758fa6f87": "https://files.catbox.moe/03920g.jpg", + "89c9572e94d5ba8d9edf4fcd37ef2ca6": "https://files.catbox.moe/5obxkn.jpg", + "866be98c60d1d3c064fd31fa6c2c2808": "https://files.catbox.moe/xibz7b.jpg", + "1b23c513692160e12b2827a00919955a": "https://files.catbox.moe/mprl5j.jpg", + "9f91be03ccecc1adc52775eba774cb51": "https://files.catbox.moe/nx5nt9.jpg", + "458f11d73f21c8cc56d7ab55e97698e6": "https://files.catbox.moe/osuuxz.jpg", + "6dea3d9fe1461e301b098a71c2a4208b": "https://files.catbox.moe/c1ydf3.jpg", + "55f17e2af726b06eeb6d1556cbd12689": "https://files.catbox.moe/dty54m.jpg", + "36a61e3055e47bb0c8dcb966ff844713": "https://files.catbox.moe/7wl59l.jpeg", + "44c7a3898d9c1b670444f33b93c7a232": "https://files.catbox.moe/k2csev.jpg", + "15d2dbcea3390ce409f4fc23eaf28101": "https://files.catbox.moe/wulmed.jpg", + "42bd871263c632d345a27c12f8fdeaf6": "https://files.catbox.moe/158uq1.jpg", + "799f4dce9d226e36d22d455dbb3b8f98": "https://files.catbox.moe/xibz7b.jpg", + "4427d655e68b30cae2c4e75dedcaed83": "https://files.catbox.moe/a7gko6.jpg", + "bc93b85e70b106af5ffd6528a1429973": "https://files.catbox.moe/sc59sk.jpg", + "e38972d494df546c474c06690c89b22b": "https://files.catbox.moe/zvc60u.jpg", + "0d8c38bdfe09949f08ddd112e146b057": "https://files.catbox.moe/1zda8q.jpg" } \ No newline at end of file diff --git a/config.js.example b/config.js.example deleted file mode 100644 index 48168b8..0000000 --- a/config.js.example +++ /dev/null @@ -1,16 +0,0 @@ -export default { - // 1. Discord Application Client ID - // Go to https://discord.com/developers/applications, create an application, - // and get the Client ID from the "OAuth2" page. - discordClientId: 'DISCORD_CLIENT_ID_HERE', - // 2. Jellyfin Server URL - // The public URL of your Jellyfin server. - jellyfinServerUrl: 'https://your.jellyfinserver.here', - // 3. Jellyfin API Key - // In Jellyfin, go to Dashboard > API Keys, and create a new key. - jellyfinApiKey: 'YOUR_JELLYFIN_API_KEY_HERE', - // 4. Jellyfin User ID - // In Jellyfin, go to Dashboard > Users, click on your user, - // and copy the user ID from the URL. - jellyfinUserId: 'YOUR_JELLYFIN_USER_ID_HERE', -}; \ No newline at end of file diff --git a/index.js b/index.js index 53e702b..602aff0 100644 --- a/index.js +++ b/index.js @@ -107,10 +107,13 @@ async function getJellyfinSession() { const response = await axios.get(`${config.jellyfinServerUrl}/Sessions`, { headers: { 'X-Emby-Token': config.jellyfinApiKey } }); + + const allowedTypes = new Set(['Audio', 'Movie', 'Episode', 'Video']); + return response.data.find(s => s.UserId === config.jellyfinUserId && s.NowPlayingItem && - s.NowPlayingItem.Type === 'Audio' + allowedTypes.has(s.NowPlayingItem.Type) ); } catch (error) { if (error.code !== 'ECONNREFUSED') { @@ -145,51 +148,67 @@ async function getAlbumArtUrl(albumId, trackId) { async function updatePresence() { const session = await getJellyfinSession(); - if (session && session.NowPlayingItem) { - const track = session.NowPlayingItem; - const trackId = track.Id; - const albumId = track.AlbumId; + if (session && session.NowPlayingItem && session.PlayState && !session.PlayState.IsPaused) { + const item = session.NowPlayingItem; + const itemType = item.Type; + const currentId = `${itemType}:${item.Id}`; - if (trackId !== lastTrackId) { - lastTrackId = trackId; - console.log(`[Discord] Now listening to: ${track.Artists.join(', ')} - ${track.Name}`); + if (currentId !== lastTrackId) { + lastTrackId = currentId; + + if (itemType === 'Audio') { + console.log(`[Discord] Now listening to: ${(item.Artists || []).join(', ')} - ${item.Name}`); + } else if (itemType === 'Movie') { + console.log(`[Discord] Now watching movie: ${item.Name}`); + } else if (itemType === 'Episode') { + console.log(`[Discord] Now watching TV: ${item.SeriesName || 'Unknown Show'} - ${item.Name}`); + } else { + console.log(`[Discord] Now playing: ${item.Name || itemType}`); + } } - // pass trackid for singles - const largeImageUrl = await getAlbumArtUrl(albumId, trackId); + let details = item.Name || 'Playing media'; + let state = ''; + let largeImageText = 'Jellyfin'; + let artAlbumId = null; + let artTrackId = item.Id; - /*const presence = new Presence() - .setDetails(`${track.Name}`) - .setState(`by ${track.Artists.join(', ')}`) - .setLargeImage(`${config.jellyfinServerUrl}/Items/${track.AlbumId}/Images/Primary`) - .setLargeText(`on ${track.Album}`) - .setSmallImage('jellyfin_logo') - .setSmallText('Jellyfin') - .setStartTimestamp(Date.now() - Math.floor(session.PlayState.PositionTicks / 10000)) - .addButton('Listen on Jellyfin', `${config.jellyfinServerUrl}/web/index.html#!/details?id=${trackId}`); + if (itemType === 'Audio') { + details = item.Name; + state = `by ${(item.Artists || []).join(', ') || 'Unknown Artist'}`; + largeImageText = `on ${item.Album || 'Unknown Album'}`; + artAlbumId = item.AlbumId; + } else if (itemType === 'Movie') { + details = item.Name; + state = `Watching movie`; + largeImageText = item.ProductionYear ? `${item.ProductionYear}` : 'Movie'; + artAlbumId = item.ParentId || null; + } else if (itemType === 'Episode') { + details = item.Name; + state = `${item.SeriesName || 'TV Show'}${item.ParentIndexNumber != null && item.IndexNumber != null + ? ` • S${String(item.ParentIndexNumber).padStart(2, '0')}E${String(item.IndexNumber).padStart(2, '0')}` + : ''}`; + largeImageText = item.SeriesName || 'TV Show'; + artAlbumId = item.SeriesId || item.SeasonId || null; + } - presence.data.type = 2; // Type 2 is for "Listening"*/ + const largeImageUrl = await getAlbumArtUrl(artAlbumId, artTrackId); client.user?.setActivity({ - details: `${track.Name}`, - state: `by ${track.Artists.join(', ')}`, + details, + state, largeImageKey: largeImageUrl, - largeImageText: `on ${track.Album}`, + largeImageText, smallImageKey: 'jellyfin_logo', smallImageText: 'Jellyfin', startTimestamp: Date.now() - Math.floor(session.PlayState.PositionTicks / 10000), - buttons: [ - { - label: 'Listen on Jellyfin', - url: `${config.jellyfinServerUrl}/web/index.html#!/details?id=${trackId}` - } - ], - type: 2 // Type 2 is for "Listening" + endTimestamp: Date.now() + Math.floor((item.RunTimeTicks - session.PlayState.PositionTicks) / 10000), + type: itemType === 'Audio' ? 2 : 3 }); } else { if (lastTrackId !== null) { - console.log('[Discord] Playback stopped. Clearing presence.'); + console.log('[Discord] Playback stopped or paused. Clearing presence.'); lastTrackId = null; client.user?.clearActivity(); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..fc53eec --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,308 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@xhayper/discord-rpc': + specifier: ^1.1.2 + version: 1.3.3 + axios: + specifier: ^1.7.2 + version: 1.14.0 + form-data: + specifier: ^4.0.4 + version: 4.0.5 + +packages: + + '@discordjs/collection@2.1.1': + resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} + engines: {node: '>=18'} + + '@discordjs/rest@2.6.1': + resolution: {integrity: sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg==} + engines: {node: '>=18'} + + '@discordjs/util@1.2.0': + resolution: {integrity: sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==} + engines: {node: '>=18'} + + '@sapphire/async-queue@1.5.5': + resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + + '@sapphire/snowflake@3.5.5': + resolution: {integrity: sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + + '@vladfrangu/async_event_emitter@2.4.7': + resolution: {integrity: sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + + '@xhayper/discord-rpc@1.3.3': + resolution: {integrity: sha512-Ih48GHiua7TtZgKO+f0uZPhCeQqb84fY2qUys/oMh8UbUfiUkUJLVCmd/v2AK0/pV33euh0aqSXo7+9LiPSwGw==} + engines: {node: '>=20'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.14.0: + resolution: {integrity: sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + discord-api-types@0.38.44: + resolution: {integrity: sha512-q91MgBzP/gRaCLIbQTaOrOhbD8uVIaPKxpgX2sfFB2nZ9nSiTYM9P3NFQ7cbO6NCxctI6ODttc67MI+YhIfILg==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + magic-bytes.js@1.13.0: + resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + undici@6.24.1: + resolution: {integrity: sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==} + engines: {node: '>=18.17'} + + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + +snapshots: + + '@discordjs/collection@2.1.1': {} + + '@discordjs/rest@2.6.1': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/util': 1.2.0 + '@sapphire/async-queue': 1.5.5 + '@sapphire/snowflake': 3.5.5 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.44 + magic-bytes.js: 1.13.0 + tslib: 2.8.1 + undici: 6.24.1 + + '@discordjs/util@1.2.0': + dependencies: + discord-api-types: 0.38.44 + + '@sapphire/async-queue@1.5.5': {} + + '@sapphire/snowflake@3.5.5': {} + + '@vladfrangu/async_event_emitter@2.4.7': {} + + '@xhayper/discord-rpc@1.3.3': + dependencies: + '@discordjs/rest': 2.6.1 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.44 + ws: 8.20.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + asynckit@0.4.0: {} + + axios@1.14.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + delayed-stream@1.0.0: {} + + discord-api-types@0.38.44: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + magic-bytes.js@1.13.0: {} + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + proxy-from-env@2.1.0: {} + + tslib@2.8.1: {} + + undici@6.24.1: {} + + ws@8.20.0: {}