Skip to content

Commit

Permalink
Add DiscordRPC
Browse files Browse the repository at this point in the history
  • Loading branch information
Inrixia committed Jun 24, 2024
1 parent e08836e commit 3a1ed79
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 0 deletions.
154 changes: 154 additions & 0 deletions plugins/DiscordRPC/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions plugins/DiscordRPC/package.json
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"
}
}
6 changes: 6 additions & 0 deletions plugins/DiscordRPC/plugin.json
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"
}
19 changes: 19 additions & 0 deletions plugins/DiscordRPC/src/DiscordRPC.ts
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);
}
}
106 changes: 106 additions & 0 deletions plugins/DiscordRPC/src/index.ts
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" /> `;
}

0 comments on commit 3a1ed79

Please sign in to comment.