diff --git a/package-lock.json b/package-lock.json index 67ba00ea0..039b3fda2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "dependencies": { "@electron/remote": "^2.1.2", "@types/swagger-jsdoc": "^6.0.4", + "@xhayper/discord-rpc": "^1.2.0", "axios": "^1.7.7", "cors": "^2.8.5", - "discord-rpc": "^4.0.1", "electron-store": "^8.2.0", "express": "^4.21.1", "hotkeys-js": "^3.13.7", @@ -25,7 +25,6 @@ "devDependencies": { "@mastermindzh/prettier-config": "^1.0.0", "@types/cors": "^2.8.17", - "@types/discord-rpc": "^4.0.8", "@types/express": "^4.17.21", "@types/node": "^20.14.10", "@types/request": "^2.48.12", @@ -402,6 +401,59 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.0.tgz", + "integrity": "sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "0.37.97", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.19.8" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/discord-api-types": { + "version": "0.37.97", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.97.tgz", + "integrity": "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA==", + "license": "MIT" + }, + "node_modules/@discordjs/util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", + "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, "node_modules/@dual-bundle/import-meta-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", @@ -1115,6 +1167,26 @@ "node": ">=14" } }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.3.tgz", + "integrity": "sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -1230,21 +1302,6 @@ "@types/ms": "*" } }, - "node_modules/@types/discord-rpc": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@types/discord-rpc/-/discord-rpc-4.0.8.tgz", - "integrity": "sha512-1tZf217Natkj+TziNXRRLwNmdm5GNa1bnrQr8VWowquo/Su5hMjdhobj8URxW1COMk2da28XCU1ahsYCAlxirA==", - "dev": true, - "dependencies": { - "@types/events": "*" - } - }, - "node_modules/@types/events": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz", - "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==", - "dev": true - }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -1818,6 +1875,52 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", + "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@xhayper/discord-rpc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xhayper/discord-rpc/-/discord-rpc-1.2.0.tgz", + "integrity": "sha512-cKjs9TKzN/7JoozijjszQjUEK1qnLHpEvcJQ2OGFBZjymUzIOH7l14KUu7TQtaIEk0Aw9Bx2w7TfQ0O6tp5mCw==", + "license": "ISC", + "dependencies": { + "@discordjs/rest": "^2.3.0", + "@vladfrangu/async_event_emitter": "^2.4.4", + "discord-api-types": "^0.37.93", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@xhayper/discord-rpc/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "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 + } + } + }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -3380,17 +3483,11 @@ "node": ">=8" } }, - "node_modules/discord-rpc": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/discord-rpc/-/discord-rpc-4.0.1.tgz", - "integrity": "sha512-HOvHpbq5STRZJjQIBzwoKnQ0jHplbEWFWlPDwXXKm/bILh4nzjcg7mNqll0UY7RsjFoaXA7e/oYb/4lvpda2zA==", - "dependencies": { - "node-fetch": "^2.6.1", - "ws": "^7.3.1" - }, - "optionalDependencies": { - "register-scheme": "github:devsnek/node-register-scheme" - } + "node_modules/discord-api-types": { + "version": "0.37.103", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.103.tgz", + "integrity": "sha512-r+qitxXKe2l6KFw5odPdZSSqdEou+7eNC7BfbZ7mny5Me/K06wCTeKUMVeH/YsI9+4QQudskeQ307kr/7ppQ1A==", + "license": "MIT" }, "node_modules/dmg-builder": { "version": "24.9.4", @@ -5774,6 +5871,12 @@ "node": ">=10" } }, + "node_modules/magic-bytes.js": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", + "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", + "license": "MIT" + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -6140,6 +6243,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, "optional": true }, "node_modules/node-cleanup": { @@ -6148,25 +6252,6 @@ "integrity": "sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==", "dev": true }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/nodemon": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", @@ -7134,17 +7219,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/register-scheme": { - "version": "0.0.2", - "resolved": "git+ssh://git@github.com/devsnek/node-register-scheme.git#e7cc9a63a1f512565da44cb57316d9fb10750e17", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "bindings": "^1.3.0", - "node-addon-api": "^1.3.0" - } - }, "node_modules/remarkable": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz", @@ -8529,11 +8603,6 @@ "node": ">=0.8" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -8623,6 +8692,12 @@ "typescript": "*" } }, + "node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -8700,6 +8775,15 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/undici": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", + "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -8816,20 +8900,6 @@ "license": "MIT", "optional": true }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8899,26 +8969,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", diff --git a/package.json b/package.json index 2aa8b87cd..0ca96ffed 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,9 @@ "dependencies": { "@electron/remote": "^2.1.2", "@types/swagger-jsdoc": "^6.0.4", + "@xhayper/discord-rpc": "^1.2.0", "axios": "^1.7.7", "cors": "^2.8.5", - "discord-rpc": "^4.0.1", "electron-store": "^8.2.0", "express": "^4.21.1", "hotkeys-js": "^3.13.7", @@ -56,7 +56,6 @@ "devDependencies": { "@mastermindzh/prettier-config": "^1.0.0", "@types/cors": "^2.8.17", - "@types/discord-rpc": "^4.0.8", "@types/express": "^4.17.21", "@types/node": "^20.14.10", "@types/request": "^2.48.12", diff --git a/src/scripts/discord.ts b/src/scripts/discord.ts index 4a162a6dc..9da1387d2 100644 --- a/src/scripts/discord.ts +++ b/src/scripts/discord.ts @@ -1,4 +1,4 @@ -import { Client, Presence } from "discord-rpc"; +import { Client, SetActivity } from "@xhayper/discord-rpc"; import { app, ipcMain } from "electron"; import { globalEvents } from "../constants/globalEvents"; import { settings } from "../constants/settings"; @@ -12,6 +12,10 @@ const clientId = "833617820704440341"; export let rpc: Client; +const ACTIVITY_LISTENING = 2; +const MAX_RETRIES = 5; +const RETRY_DELAY = 10000; + const observer = () => { if (rpc) { updateActivity(); @@ -22,19 +26,20 @@ const defaultPresence = { largeImageKey: "tidal-hifi-icon", largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`, instance: false, + type: ACTIVITY_LISTENING }; const updateActivity = () => { const showIdle = settingsStore.get(settings.discord.showIdle) ?? true; if (mediaInfo.status === MediaStatus.paused && !showIdle) { - rpc.clearActivity(); + rpc.user?.clearActivity(); } else { - rpc.setActivity(getActivity()); + rpc.user?.setActivity(getActivity()); } }; -const getActivity = (): Presence => { - const presence: Presence = { ...defaultPresence }; +const getActivity = (): SetActivity => { + const presence: SetActivity = { ...defaultPresence }; if (mediaInfo.status === MediaStatus.paused) { presence.details = @@ -50,6 +55,7 @@ const getActivity = (): Presence => { settingsStore.get(settings.discord.usingText) ?? "Playing media on TIDAL"; } } + return presence; function getFromStore() { @@ -98,10 +104,29 @@ const getActivity = (): Presence => { const currentSeconds = convertDurationToSeconds(mediaInfo.current); const durationSeconds = convertDurationToSeconds(mediaInfo.duration); const date = new Date(); - const now = (date.getTime() / 1000) | 0; - const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); - presence.startTimestamp = now; - presence.endTimestamp = remaining; + const now = Math.floor(date.getTime() / 1000); + presence.startTimestamp = now - currentSeconds; + presence.endTimestamp = presence.startTimestamp + durationSeconds; + } + } +}; + +/** + * Try to login to RPC and retry if it errors + * @param retryCount Max retry count + */ +const connectWithRetry = async (retryCount = 0) => { + try { + await rpc.login(); + Logger.log('Connected to Discord'); + rpc.on("ready", updateActivity); + Object.values(globalEvents).forEach(event => ipcMain.on(event, observer)); + } catch (error) { + if (retryCount < MAX_RETRIES) { + Logger.log(`Failed to connect to Discord, retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`); + setTimeout(() => connectWithRetry(retryCount + 1), RETRY_DELAY); + } else { + Logger.log('Failed to connect to Discord after maximum retry attempts'); } } }; @@ -110,22 +135,8 @@ const getActivity = (): Presence => { * Set up the discord rpc and listen on globalEvents.updateInfo */ export const initRPC = () => { - rpc = new Client({ transport: "ipc" }); - rpc.login({ clientId }).then( - () => { - rpc.on("ready", () => { - updateActivity(); - }); - - const { updateInfo, play, pause, playPause } = globalEvents; - [updateInfo, play, pause, playPause].forEach((status) => { - ipcMain.on(status, observer); - }); - }, - () => { - Logger.log("Can't connect to Discord, is it running?"); - } - ); + rpc = new Client({ transport: {type: "ipc"}, clientId }); + connectWithRetry(); }; /** @@ -133,7 +144,7 @@ export const initRPC = () => { */ export const unRPC = () => { if (rpc) { - rpc.clearActivity(); + rpc.user?.clearActivity(); rpc.destroy(); rpc = null; ipcMain.removeListener(globalEvents.updateInfo, observer);