Add Discord Presence plugin (#298)

* Create discordPresence.yml

* Create discordPresence.js

* Update discordPresence.yml

* Update discordPresence.js

* Improved Discord video time tracking

* Fixed formatting

* Fixed formatting (line breaks)
This commit is contained in:
dopepump
2024-05-08 14:20:43 +01:00
committed by GitHub
parent 468e9df869
commit e2ce4b04de
2 changed files with 235 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
(async function () {
/**
* @typedef {{
* discordClientId?: string;
* discordLargeImageKey?: string;
* discordShowImage?: boolean;
* discordShowUrlButton?: boolean;
* discordUrlButtonText?: string;
* }} PluginConfig
*/
/**
* @typedef {{
* id, title, urls?: string[], date,
* files: {duration:number}[], studio?: {id, name}
* }} SceneData
*/
/**
* @typedef {{ data: { findScene: SceneData | null } }} GQLSceneDataResponse
*/
const SCENE_GQL_QUERY = `
query FindScene($id: ID!) {
findScene(id: $id) {
...SceneData
__typename
}
}
fragment SceneData on Scene {
id
title
urls
date
files {
duration
__typename
}
studio {
...SlimStudioData
__typename
}
__typename
}
fragment SlimStudioData on Studio {
id
name
__typename
}
`;
while (!window.stash) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
const PLUGIN_ID = "discordPresence";
let userConfig = await getPluginConfig();
console.debug("Discord Presence Plugin: user config", userConfig);
for (let [key, val] of Object.entries(userConfig)) {
if (val === "" || val === undefined || val === null) {
delete userConfig[key];
}
}
/** @type {Required<PluginConfig>} */
const CONFIG = {
discordClientId: "1236860180407521341",
discordLargeImageKey: "stashbox",
discordShowImage: true,
discordShowUrlButton: false,
discordUrlButtonText: "Watch",
...userConfig,
};
console.debug("Discord Presence Plugin: loaded config", CONFIG);
let SCENE_ID = null;
let INTERVAL_ID = null;
const doUpdatingPresence = (e) => {
clearInterval(INTERVAL_ID);
const pathname = e.detail.data.location.pathname;
if (!pathname.match(/\/scenes\/\d+/)) {
clearDiscordActivity();
return;
}
SCENE_ID = parseInt(pathname.split("/")[2], 10);
setDiscordActivity();
INTERVAL_ID = setInterval(setDiscordActivity, 5000);
};
// https://github.com/lolamtisch/Discord-RPC-Extension/releases
const ws = new WebSocket("ws://localhost:6969");
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();
});
/** @returns {Promise<PluginConfig>} */
async function getPluginConfig() {
const reqData = {
operationName: "Configuration",
variables: {},
query: `query Configuration { configuration { plugins } }`,
};
const data = await stash.callGQL(reqData);
return data.data.configuration.plugins[PLUGIN_ID];
}
async function getSceneData(sceneId) {
if (!sceneId) {
return { sceneData: null, duration: 0 };
}
const reqData = {
operationName: "FindScene",
variables: { id: sceneId },
query: SCENE_GQL_QUERY,
};
/** @type {GQLSceneDataResponse} */
const data = await stash.callGQL(reqData);
return {
sceneData: data.data.findScene,
duration: data.data.findScene?.files[0]?.duration ?? 0,
};
}
function clearDiscordActivity() {
if (!!SCENE_ID === false || ws.OPEN !== 1) {
return;
}
SCENE_ID = null;
ws.send(JSON.stringify({ action: "disconnect" }));
}
async function setDiscordActivity() {
const { sceneData, duration } = await getSceneData(SCENE_ID);
const url =
CONFIG.discordShowUrlButton && sceneData?.urls.length > 0
? sceneData?.urls[0]
: undefined;
const studio = sceneData?.studio?.name;
const currentTime = getCurrentVideoTime() ?? 0;
const endTimestamp = Date.now() + (duration - currentTime) * 1000;
let body = {};
if (sceneData !== null) {
body = {
details: sceneData.title,
state: studio ? `by ${studio}` : undefined,
largeImageKey: CONFIG.discordShowImage
? CONFIG.discordLargeImageKey
: undefined,
endTimestamp,
buttons: url
? [{ label: CONFIG.discordUrlButtonText, url }]
: undefined,
instance: true,
};
}
if (!ws.OPEN) {
return;
}
ws.send(
JSON.stringify({
clientId: CONFIG.discordClientId,
extId: "stash-discord-rpc-plugin",
presence: body,
})
);
}
function getCurrentVideoTime() {
const videoElem = document.querySelector("#VideoJsPlayer video");
if (!videoElem) {
return null;
}
return videoElem.currentTime;
}
})();

View File

@@ -0,0 +1,29 @@
name: Discord Presence
description: Sets currently playing scene data as your Discord status. You need to have the app running on your desktop (https://github.com/lolamtisch/Discord-RPC-Extension/releases). You DO NOT need any browser extensions. Requires Stash Userscript Library.
version: 0.1
settings:
discordClientId:
displayName: Custom Discord application ID
description: Set a custom client ID, found here https://discord.com/developers/applications
type: STRING
discordLargeImageKey:
displayName: Custom presence image key
description: If using a custom client ID, set a presence image key from https://discord.com/developers/applications/{clientId}/rich-presence/assets
type: STRING
discordShowImage:
displayName: Show presence image
description: Show the large presence image in your Discord profile (shows Stash logo when using default clientId/imageKey)
type: BOOLEAN
discordShowUrlButton:
displayName: Show scene URL button
description: Show a presence button which links to first scene URL
type: BOOLEAN
discordUrlButtonText:
displayName: Custom button text
description: Set the displayed text for the presence button (default; "Watch")
type: STRING
ui:
requires:
- StashUserscriptLibrary
javascript:
- discordPresence.js