Skip to content

Commit

Permalink
[discordPresence] refactor with new server app (#358)
Browse files Browse the repository at this point in the history
* Refactor discordPresence for auto-reconnect

* Add prompt for Discord presence server
  • Loading branch information
NotForMyCV authored Jul 8, 2024
1 parent e1a1106 commit 2e5f0f3
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 69 deletions.
4 changes: 2 additions & 2 deletions plugins/discordPresence/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ A plugin which shows the metadata of the currently playing Stash scene as your D

## Setup
### Prerequisites to get the plugin working
- Download and run [Discord RPC Server](https://github.com/lolamtisch/Discord-RPC-Extension/releases). You **do not** need any browser extensions.
- Install [`StashUserscriptLibrary`](https://github.com/stashapp/CommunityScripts/tree/main/plugins/stashUserscriptLibrary) from your Stash plugin menu.
- Download and run [Discord Presence Server](https://github.com/NotForMyCV/discord-presence-server/releases). You **do not** need any browser extensions.
- Ensure you have CommunityScriptsUILibrary installed in your Stash plugins, if it isn't automatically installed

#### Why the desktop app?
<sub>
Expand Down
143 changes: 77 additions & 66 deletions plugins/discordPresence/discordPresence.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,59 +82,85 @@

console.debug("Discord Presence Plugin: loaded config", CONFIG);

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const player = () => document.querySelector("#VideoJsPlayer video");

let SCENE_ID = null;
let INTERVAL_ID = null;
let WS_ALIVE = false;
/** @type {FlattenedSceneData?} */ let cachedSceneData;

const doUpdatingPresence = (e) => {
clearInterval(INTERVAL_ID);
/** @type {WebSocket} */ let ws;
const wsAlive = () => ws && ws.readyState === 1;

const pathname = e.detail.data.location.pathname;
const videoListener = (video) => {
SCENE_ID = parseInt(location.pathname.split("/")[2]);
video.addEventListener("playing", setDiscordActivity);
video.addEventListener("play", setDiscordActivity);
video.addEventListener("timeupdate", setDiscordActivity);
video.addEventListener("seeked", setDiscordActivity);
video.addEventListener("ended", clearDiscordActivity);
};

if (!pathname.match(/\/scenes\/\d+/)) {
clearDiscordActivity();
const unbindVideoListener = (video) => {
video.removeEventListener("playing", setDiscordActivity);
video.removeEventListener("play", setDiscordActivity);
video.removeEventListener("timeupdate", setDiscordActivity);
video.removeEventListener("seeked", setDiscordActivity);
video.removeEventListener("ended", clearDiscordActivity);
};

// Start ws connection to RPC server and add video listener
// Will retry on disconnection/error after 10s
async function start() {
if (ws && ws.readyState <= 1) {
return;
}

SCENE_ID = parseInt(pathname.split("/")[2], 10);
// https://github.com/NotForMyCV/discord-presence-server/releases
ws = new WebSocket("ws://localhost:6969");

setDiscordActivity();
INTERVAL_ID = setInterval(setDiscordActivity, 5000);
};
ws.addEventListener("open", () => {
csLib.PathElementListener("/scenes/", "video", videoListener);
});

// https://github.com/lolamtisch/Discord-RPC-Extension/releases
const ws = new WebSocket("ws://localhost:6969");
ws.addEventListener("message", () => (WS_ALIVE = true));
ws.addEventListener("open", () =>
PluginApi.Event.addEventListener("stash:location", doUpdatingPresence)
);
ws.addEventListener("close", () => {
clearInterval(INTERVAL_ID);
PluginApi.Event.removeEventListener("stash:location", doUpdatingPresence);
});
ws.addEventListener("error", () => {
PluginApi.Event.removeEventListener("stash:location", doUpdatingPresence);
});
window.addEventListener("beforeunload", () => {
clearDiscordActivity();
});
// set timeout for checking liveliness
const checkLiveliness = () => {
if (!WS_ALIVE) {
unbindVideoListener(document.querySelector("#VideoJsPlayer video"));
clearInterval(INTERVAL_ID);
throw new Error(`Discord Presence Plugin: Discord RPC Extension not running
Please consult the README on how to set up the Discord RPC Extension
(https://github.com/stashapp/CommunityScripts/tree/main/plugins/discordPresence)`);
}
};
setTimeout(checkLiveliness, 2000);
window.addEventListener("beforeunload", () => {
clearDiscordActivity();
});

// If failed during video playback, remove the listeners
ws.addEventListener("close", async () => {
if (player()) {
unbindVideoListener(player());
}

await sleep(10000);
start();
});

ws.addEventListener("error", async () => {
if (player()) {
unbindVideoListener(player());
}

console.error(
`Discord Presence Plugin: Could not connect to Discord Rich Presence Server.
Consult the README on how to setup the Rich Presence Server:
https://github.com/stashapp/CommunityScripts/tree/main/plugins/discordPresence`
);
await sleep(10000);
start();
});
}

start();

/** @return {Promise<FlattenedSceneData | null>} */
async function getSceneData(sceneId) {
if (!sceneId) {
return { sceneData: null, duration: 0 };
if (!sceneId) return null;

if (Number(sceneId).toString() === Number(cachedSceneData?.id).toString()) {
return cachedSceneData;
}

const reqData = {
variables: { id: sceneId },
query: SCENE_GQL_QUERY,
Expand All @@ -157,23 +183,29 @@
delete sceneData.studio;
delete sceneData.files;

return { ...sceneData, ...newProps };
cachedSceneData = { ...sceneData, ...newProps };
return cachedSceneData;
}

function clearDiscordActivity() {
if (!!SCENE_ID === false || ws.OPEN !== 1) {
if (!!SCENE_ID === false || !wsAlive()) {
return;
}

SCENE_ID = null;
ws.send(JSON.stringify({ action: "disconnect" }));
ws.send(
JSON.stringify({
clientId: CONFIG.discordClientId,
clearActivity: true,
})
);
}

async function setDiscordActivity() {
const sceneData = await getSceneData(SCENE_ID);
if (!sceneData) return;

const currentTime = getCurrentVideoTime() ?? 0;
const currentTime = player()?.currentTime ?? 0;
const endTimestamp =
Date.now() + (sceneData.file_duration - currentTime) * 1000;

Expand All @@ -197,22 +229,18 @@
instance: true,
};

if (!ws.OPEN) {
if (!wsAlive()) {
return;
}

ws.send(
JSON.stringify({
clientId: CONFIG.discordClientId,
extId: "stash-discord-rpc-plugin",
presence: body,
})
);
}

const getCurrentVideoTime = () =>
document.querySelector("#VideoJsPlayer video")?.currentTime;

/**
* Performs string replacement on templated config vars with scene data
* @param {string} templateStr
Expand All @@ -222,21 +250,4 @@
const pattern = /{\s*(\w+?)\s*}/g;
return templateStr.replace(pattern, (_, token) => sceneData[token] ?? "");
}

// add listener for video events
const videoListener = (video) => {
SCENE_ID = parseInt(location.pathname.split("/")[2]);
video.addEventListener("playing", setDiscordActivity);
video.addEventListener("play", setDiscordActivity);
video.addEventListener("seeked", setDiscordActivity);
// end on video end
video.addEventListener("ended", clearDiscordActivity);
};
const unbindVideoListener = (video) => {
video.removeEventListener("playing", setDiscordActivity);
video.removeEventListener("play", setDiscordActivity);
video.removeEventListener("seeked", setDiscordActivity);
video.removeEventListener("ended", clearDiscordActivity);
};
csLib.PathElementListener("/scenes/", "video", videoListener);
})();
2 changes: 1 addition & 1 deletion plugins/discordPresence/discordPresence.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Discord Presence
description: Sets currently playing scene data as your Discord status. See README for prerequisites and config options (blue hyperlink next to enable/disable button)
url: https://github.com/stashapp/CommunityScripts/tree/main/plugins/discordPresence
# requires: CommunityScriptsUILibrary
version: 1.0
version: 1.1
settings:
discordClientId:
displayName: Custom Discord application ID
Expand Down

0 comments on commit 2e5f0f3

Please sign in to comment.