Delete plugins/discordPresence directory (#635)

This commit is contained in:
DogmaDragon
2025-11-26 05:26:44 +02:00
committed by GitHub
parent 48b5c7560f
commit 621f640c41
3 changed files with 0 additions and 388 deletions

View File

@@ -1,55 +0,0 @@
# Discord Presence
https://discourse.stashapp.cc/t/discord-presence/1374
A plugin which shows the metadata of the currently playing Stash scene as your Discord presence
## Setup
### Prerequisites to get the plugin working
- 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>
This plugin relies on a separate desktop app (Discord RPC Server) running in the background. This is required because only a local app can talk to your Discord client to set a custom presence. The ability to do so from a website/browser is whitelisted by Discord (otherwise any website you visit could change your Discord client presence). Discord RPC Server is an open source application which exposes a websocket connection, so that other browser scripts and extensions (i.e. this plugin) can send presence updates to it, which it then forwards to your Discord client.
</sub>
## Configuration
You can customize almost any part of the activity presence with the plugin options.
| Presence element | Plugin setting name | Default value (if empty; reverts to: ) | Configuration |
|-----------------------|-------------------------------|------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Activity name | Custom Discord application ID | `1236860180407521341` (displays "Stash") | Create a new application under your [Discord developer portal](https://discord.com/developers/applications). <sub>The name of the application will be the name of the activity being shown as "Playing". Copy the `APPLICATION ID` (20 digit number) from the Developer Portal and set it in the plugin options.</sub> |
| Details (first line) | Presence details text | `{title}` | Custom text and variables |
| State (second line) | Presence state text | `from {studio_name}` | Custom text and variables |
| Show activity image | Show presence image | Off | Toggle switch |
| Custom activity image | Custom presence image key | `stashbox` | After creating a Discord app (see first config option) go to your application settings > Rich Presence > Art Assets. Upload your custom image, give it a key name, and put this in the plugin option (takes a short while for the asset to appear after uploading). |
| Activity hover text | Custom image text | Empty | Custom text and variables |
| Show URL button | Show scene URL button | Off | Toggle switch |
| Custom button text | Custom button text | `Watch` | Custom text and variables |
## String variables
You can insert metadata from the currently playing scene into configurable elements, by enclosing variables in curly braces.
For example, if you were watching a scene called "Kittens" and wanted to display "Watching Kittens" under the presence details, you would set the config option to `Watching {title}`.
Below are a list of available variable names:
- `{id}`
- `{title}`
- `{code}`
- `{details}`
- `{director}`
- `{date}`
- `{rating100}`
- `{o_counter}`
- `{organized}`
- `{interactive}`
- `{interactive_speed}`
- `{created_at}`
- `{updated_at}`
- `{resume_time}`
- `{last_played_at}`
- `{play_duration}`
- `{play_count}`
- `{url}`
- `{studio_name}`
- `{file_duration}`
- `{performers}`

View File

@@ -1,287 +0,0 @@
(async function () {
/**
* @typedef {{
* discordClientId?: string;
* discordDetailsText?: string;
* discordStateText?: string;
* discordShowImage?: boolean;
* discordLargeImageKey?: string;
* discordLargeImageText?: string;
* discordShowUrlButton?: boolean;
* discordUrlButtonText?: string;
* }} PluginConfig
*/
/**
* @typedef {{
* id, title, code, details, director, urls?: string[], date, rating100, o_counter,
* organized, interactive, interactive_speed, created_at, updated_at, resume_time,
* last_played_at, play_duration, play_count, files: {duration:number}[],
* studio?: {id, name}, performers: {name, gender}[]
* }} SceneData
*/
/**
* @typedef {{ studio_name: string, url: string, file_duration: string, performers: string }
* & Omit<SceneData, ['urls', 'files', 'studio', 'performers']>
* } FlattenedSceneData
*/
const SCENE_GQL_QUERY = `
query FindScene($id: ID!) {
findScene(id: $id) {
...SceneData
}
}
fragment SceneData on Scene {
id
title
code
details
director
urls
date
rating100
o_counter
organized
interactive
interactive_speed
created_at
updated_at
resume_time
last_played_at
play_duration
play_count
files { duration }
studio { name }
performers { name, gender }
}
`;
const PLUGIN_ID = "discordPresence";
const userConfig = await csLib.getConfiguration(PLUGIN_ID, {});
console.debug("Discord Presence Plugin: user config", userConfig);
/** @type {Required<PluginConfig>} */
const CONFIG = {
// DEFAULTS
discordClientId: "1236860180407521341",
discordDetailsText: "{title}",
discordStateText: "from {studio_name}",
discordShowImage: false,
discordLargeImageKey: "stashbox",
discordLargeImageText: "Stashapp",
discordShowUrlButton: false,
discordUrlButtonText: "Watch",
...userConfig,
};
console.debug("Discord Presence Plugin: loaded config", CONFIG);
function throttle(mainFunction, delay) {
let timerFlag = null;
return (...args) => {
if (timerFlag === null) {
mainFunction(...args);
timerFlag = setTimeout(() => {
timerFlag = null;
}, delay);
}
};
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const player = () => document.querySelector("#VideoJsPlayer video");
let WAITING_FOR_REFRESH = true;
let SCENE_ID = null;
/** @type {FlattenedSceneData?} */ let cachedSceneData;
/** @type {WebSocket} */ let ws;
const wsAlive = () => ws && ws.readyState === 1;
// 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;
}
// https://github.com/NotForMyCV/discord-presence-server/releases
ws = new WebSocket("ws://localhost:6969");
ws.addEventListener("open", () => {
csLib.PathElementListener("/scenes/", "video", videoListener);
});
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 null;
if (Number(sceneId).toString() === Number(cachedSceneData?.id).toString()) {
return cachedSceneData;
}
const reqData = {
variables: { id: sceneId },
query: SCENE_GQL_QUERY,
};
/** @type {SceneData} */
const sceneData = await csLib
.callGQL(reqData)
.then((data) => data.findScene);
if (!sceneData) return null;
const newProps = {
studio_name: sceneData.studio?.name ?? "Unknown Studio",
url: sceneData.urls?.length ? sceneData.urls[0] : "",
file_duration: sceneData.files?.length ? sceneData.files[0].duration : 0,
performers: sceneData.performers.length
? sceneData.performers.map((performer) => performer.name).join(", ")
: "Unlisted Performer(s)",
};
delete sceneData.urls;
delete sceneData.studio;
delete sceneData.files;
delete sceneData.performers;
cachedSceneData = { ...sceneData, ...newProps };
return cachedSceneData;
}
const clearDiscordActivity = () => {
if (!!SCENE_ID === false || !wsAlive()) {
return;
}
SCENE_ID = null;
ws.send(
JSON.stringify({
clientId: CONFIG.discordClientId,
clearActivity: true,
})
);
};
const setDiscordActivity = throttle(async (event) => {
if (event?.type === "timeupdate") {
if (!WAITING_FOR_REFRESH) {
return;
}
WAITING_FOR_REFRESH = false;
setTimeout(() => (WAITING_FOR_REFRESH = true), 5000);
}
const sceneData = await getSceneData(SCENE_ID);
if (!sceneData) return;
const currentTime = player()?.currentTime ?? 0;
const endTimestamp =
Date.now() + (sceneData.file_duration - currentTime) * 1000;
let body = {
details: replaceVars(CONFIG.discordDetailsText, sceneData),
state: replaceVars(CONFIG.discordStateText, sceneData),
largeImageKey: CONFIG.discordShowImage
? CONFIG.discordLargeImageKey
: undefined,
largeImageText: replaceVars(CONFIG.discordLargeImageText, sceneData),
endTimestamp: sceneData.file_duration > 0 ? endTimestamp : undefined,
buttons:
CONFIG.discordShowUrlButton && URL.canParse(sceneData.url)
? [
{
label: replaceVars(CONFIG.discordUrlButtonText, sceneData),
url: sceneData.url,
},
]
: undefined,
instance: true,
};
if (!wsAlive()) {
return;
}
ws.send(
JSON.stringify({
clientId: CONFIG.discordClientId,
presence: body,
})
);
}, 1000);
/**
* Performs string replacement on templated config vars with scene data
* @param {string} templateStr
* @param {FlattenedSceneData} sceneData
*/
function replaceVars(templateStr, sceneData) {
const pattern = /{\s*(\w+?)\s*}/g;
const replacedStr = templateStr
.replace(pattern, (_, token) => sceneData[token] ?? "")
.trim();
if (replacedStr.length <= 128) {
return replacedStr;
}
return replacedStr.substring(0, 125) + "...";
}
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);
};
const unbindVideoListener = (video) => {
video.removeEventListener("playing", setDiscordActivity);
video.removeEventListener("play", setDiscordActivity);
video.removeEventListener("timeupdate", setDiscordActivity);
video.removeEventListener("seeked", setDiscordActivity);
video.removeEventListener("ended", clearDiscordActivity);
};
})();

View File

@@ -1,46 +0,0 @@
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.3
settings:
discordClientId:
displayName: Custom Discord application ID
description: Set a custom client ID
type: STRING
discordDetailsText:
displayName: Presence details text
description: Format the first line of your presence text
type: STRING
discordStateText:
displayName: Presence state text
description: Format the second line of your presence text
type: STRING
discordShowImage:
displayName: Show presence image
description: Show the large presence activity image
type: BOOLEAN
discordLargeImageKey:
displayName: Custom presence image key
description: Set a presence image key (requires custom application ID and art asset, see README)
type: STRING
discordLargeImageText:
displayName: Custom image text
description: Format the hover text for the activity image
type: STRING
discordShowUrlButton:
displayName: Show scene URL button
description: Show a presence button which links to the first scene URL
type: BOOLEAN
discordUrlButtonText:
displayName: Custom button text
description: Format the text for the presence button
type: STRING
ui:
requires:
- CommunityScriptsUILibrary
javascript:
- discordPresence.js
csp:
connect-src:
- ws://localhost:6969