generated from uwu/neptune-template
-
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"dependencies": { | ||
"@inrixia/lib": "../_lib", | ||
"discord-rpc": "^4.0.1" | ||
}, | ||
"devDependencies": { | ||
"@types/discord-rpc": "^4.0.8" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"name": "Discord RPC", | ||
"description": "Shows what song you're currently listening to on Discord.", | ||
"author": "toonlink", | ||
"main": "./src/index.js" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Client } from "discord-rpc"; | ||
const onCleanupErr = (err: Error) => console.warn("Encountered error while cleaning up DiscordRPC", err); | ||
export class DiscordRPC { | ||
public rpcClient?: Client; | ||
constructor(private readonly clientId: string) {} | ||
|
||
public isConnected() { | ||
// @ts-expect-error Types dont include internals like transport | ||
return !!this.rpcClient?.transport?.socket; | ||
} | ||
async ensureRPC() { | ||
if (this.isConnected()) return this.rpcClient!; | ||
return (this.rpcClient = await new Client({ transport: "ipc" }).login({ clientId: this.clientId })); | ||
} | ||
async cleanp(clearActivity?: false) { | ||
if (this.isConnected() && clearActivity) await this.rpcClient!.clearActivity().catch(onCleanupErr); | ||
await this.rpcClient?.destroy().catch(onCleanupErr); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { store, intercept } from "@neptune"; | ||
import { getMediaURLFromID } from "@neptune/utils"; | ||
import { Presence } from "discord-rpc"; | ||
import { html } from "@neptune/voby"; | ||
|
||
import { DiscordRPC } from "./DiscordRPC"; | ||
|
||
// @ts-expect-error Types dont include @plugin | ||
import { storage } from "@plugin"; | ||
|
||
import { MediaItem } from "neptune-types/tidal"; | ||
import { SwitchSetting } from "@inrixia/lib/components/SwitchSetting"; | ||
import getPlaybackControl from "@inrixia/lib/getPlaybackControl"; | ||
import { TrackItemCache } from "@inrixia/lib/Caches/TrackItemCache"; | ||
|
||
const rpcClient = new DiscordRPC("1130698654987067493"); | ||
|
||
const STR_MAX_LEN = 127; | ||
const formatLongString = (s?: string) => { | ||
if (s === undefined) return ""; | ||
return s.length >= STR_MAX_LEN ? s.slice(0, STR_MAX_LEN - 3) + "..." : s; | ||
}; | ||
|
||
enum AudioQuality { | ||
HiRes = "HI_RES_LOSSLESS", | ||
MQA = "HI_RES", | ||
High = "LOSSLESS", | ||
Low = "HIGH", | ||
Lowest = "LOW", | ||
} | ||
interface PlaybackContext { | ||
actualAssetPresentation: string; | ||
actualAudioMode: string; | ||
actualAudioQuality: AudioQuality; | ||
actualDuration: number; | ||
actualProductId: string; | ||
actualStreamType: unknown; | ||
actualVideoQuality: unknown; | ||
assetPosition: number; | ||
bitDepth: number | null; | ||
codec: string; | ||
playbackSessionId: string; | ||
sampleRate: number | null; | ||
} | ||
const unloadTimeUpdate = intercept("playbackControls/TIME_UPDATE", ([current]) => { | ||
updateRPC(current); | ||
}); | ||
|
||
const updateRPC = async (currentTime?: number) => { | ||
const { playbackContext, playbackState, latestCurrentTime } = getPlaybackControl(); | ||
currentTime ??= latestCurrentTime; | ||
|
||
const mediaItemId = (<PlaybackContext | null | undefined>playbackContext)?.actualProductId; | ||
if (mediaItemId === undefined) return; | ||
|
||
const currentlyPlaying = await TrackItemCache.ensure(mediaItemId); | ||
if (currentlyPlaying === undefined) return; | ||
|
||
const _rpcClient = await rpcClient.ensureRPC().catch((err) => console.error("Failed to connect to DiscordRPC", err)); | ||
if (_rpcClient === undefined) return; | ||
|
||
const activityState: Presence = { | ||
buttons: [], | ||
}; | ||
if (currentlyPlaying.url) activityState.buttons?.push({ url: currentlyPlaying.url, label: "Play on Tidal" }); | ||
|
||
// Pause indicator | ||
if (playbackState === "NOT_PLAYING") { | ||
if (storage.keepRpcOnPause === false) return _rpcClient.clearActivity(); | ||
activityState.smallImageKey = "paused-icon"; | ||
activityState.smallImageText = "Paused"; | ||
} else if (currentlyPlaying.duration !== undefined && currentTime !== undefined) { | ||
// Playback/Time | ||
activityState.startTimestamp = Math.floor(Date.now() / 1000); | ||
activityState.endTimestamp = Math.floor((Date.now() + (currentlyPlaying.duration - currentTime) * 1000) / 1000); | ||
} | ||
|
||
// Album | ||
if (currentlyPlaying.album !== undefined) { | ||
activityState.largeImageKey = getMediaURLFromID(currentlyPlaying.album.cover); | ||
activityState.largeImageText = formatLongString(currentlyPlaying.album.title); | ||
} | ||
|
||
// Title/Artist | ||
const artist = `by ${currentlyPlaying?.artist?.name ?? currentlyPlaying.artists?.[0]?.name ?? "Unknown Artist"}`; | ||
const desc = `${currentlyPlaying.title} ${artist}`; | ||
if (desc.length >= 32) { | ||
activityState.details = formatLongString(currentlyPlaying.title); | ||
activityState.state = formatLongString(artist); | ||
} else { | ||
activityState.details = formatLongString(desc); | ||
} | ||
|
||
return _rpcClient.setActivity(activityState); | ||
}; | ||
|
||
export async function onUnload() { | ||
unloadTimeUpdate(); | ||
await rpcClient.cleanp(); | ||
} | ||
updateRPC(); | ||
|
||
storage.keepRpcOnPause ??= false; | ||
export function Settings() { | ||
return html` <${SwitchSetting} checked=${storage.keepRpcOnPause} onClick=${() => (storage.keepRpcOnPause = !storage.keepRpcOnPause)} title="Keep RPC on pause" /> `; | ||
} |