diff --git a/plugins/AITagger/ai_tagger.py b/plugins/AITagger/ai_tagger.py index 918e0ce..0b097c4 100644 --- a/plugins/AITagger/ai_tagger.py +++ b/plugins/AITagger/ai_tagger.py @@ -182,9 +182,6 @@ async def __tag_images(images): async def __tag_scene(scene): async with semaphore: scenePath = scene['files'][0]['path'] - mutated_path = scenePath - for key, value in config.path_mutation.items(): - mutated_path = mutated_path.replace(key, value) sceneId = scene['id'] log.debug("files result:" + str(scene['files'][0])) phash = scene['files'][0].get('fingerprint', None) diff --git a/plugins/discordPresence/README.md b/plugins/discordPresence/README.md index 7a88842..fae394d 100644 --- a/plugins/discordPresence/README.md +++ b/plugins/discordPresence/README.md @@ -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? diff --git a/plugins/discordPresence/discordPresence.js b/plugins/discordPresence/discordPresence.js index f9aa956..3e4e5f0 100644 --- a/plugins/discordPresence/discordPresence.js +++ b/plugins/discordPresence/discordPresence.js @@ -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} */ 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, @@ -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; @@ -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 @@ -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); })(); diff --git a/plugins/discordPresence/discordPresence.yml b/plugins/discordPresence/discordPresence.yml index 1889e3c..1c2ffb8 100644 --- a/plugins/discordPresence/discordPresence.yml +++ b/plugins/discordPresence/discordPresence.yml @@ -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 diff --git a/plugins/hotCards/README.md b/plugins/hotCards/README.md index 8f737c8..67a3a47 100644 --- a/plugins/hotCards/README.md +++ b/plugins/hotCards/README.md @@ -1,19 +1,154 @@ # Hot Cards -Hot Cards is a Stash CommunityScript plugin that offers a visual aid by applying custom CSS to card elements based on a tag ID or a rating threshold. You can use this plugin to remind yourself of certain performers, scenes, studios, movies, images or galleries. +Hot Cards is a Stash CommunityScript plugin designed to enhance your visual experience by applying custom styling to card elements based on a Tag ID or a Rating Threshold. This plugin is perfect for highlighting certain performers or scenes and making sure you don't forget them! ## Features -- Adds custom CSS to card elements that match a specified tag ID or rating threshold. +- Custom styling to card elements that match a specified Tag ID or Rating Threshold. - Enable or disable Hot Cards on various sections like home, scenes, images, movies, galleries, performers, and studios. +- Specify Hot Cards to be tag-based or rating-based for each card type, as desired. +- Customizable Hot Cards. ## Installation -1. Go to Settings > Plugins. -2. Under Available Plugins expand the Community (stable) option. -3. Search for Hot Cards. -4. Select the plugin and click Install. +1. Go to **Settings** > **Plugins**. +2. Under **Available Plugins** expand the **Community (stable)** option. +3. Search for **Hot Cards**. +4. Select the plugin and click **Install**. ## Usage -Once installed, you can configure the plugin. Set a desire tag ID or a rating threshold and enable the sections where you want the hot cards to be displayed. +After installation, you can configure the plugin to suit your needs. Set a desired Tag ID or Rating Threshold and enable Hot Cards for the card types you want. Customize the appearance of Hot Cards for each type of card (scene, image, movie, gallery, performer, studio) using the format provided or leave the fields empty to apply the default style. + +### Configure the field format: + +_[criterion]\_[value]\_[style]\_[gradient-opts]\_[hover-opts]\_[card-opts]_ + +**Important**: If you have previously installed the plugin, after updating to `1.1.0`, be sure to update your settings from the old boolean format to the new string format. Refresh the page for the changes to take effect. + +| Parameter | Description | Details | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `` | Defines the basis for applying styles. Use `t` for tag-based criteria, `r` for rating-based criteria, or `d` to disable. | If left empty, it will default to the global **Tag ID** or **Rating Threshold** configuration. If both options are enabled and unspecified, the Tag ID will be used by default. | +| `` | Specifies the exact value for the Tag ID or Rating Threshold to be used. | +| `