mirror of
https://github.com/stashapp/CommunityScripts.git
synced 2026-04-20 22:12:32 -05:00
Delete plugins/discordPresence directory (#635)
This commit is contained in:
@@ -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}`
|
||||
@@ -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);
|
||||
};
|
||||
})();
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user