diff --git a/plugins/sceneCoverCropper/sceneCoverCropper.js b/plugins/sceneCoverCropper/sceneCoverCropper.js
index 7d53c4e..3808681 100644
--- a/plugins/sceneCoverCropper/sceneCoverCropper.js
+++ b/plugins/sceneCoverCropper/sceneCoverCropper.js
@@ -139,7 +139,7 @@
cropBtnContainer.appendChild(cropInfo);
}
- stash.addEventListener('page:scene', function () {
+ stash.addEventListener('stash:page:scene', function () {
waitForElementId('scene-edit-details', setupCropper);
});
})();
diff --git a/plugins/stashUserscriptLibrary/custom.js b/plugins/stashUserscriptLibrary/custom.js
new file mode 100644
index 0000000..f13164c
--- /dev/null
+++ b/plugins/stashUserscriptLibrary/custom.js
@@ -0,0 +1,1434 @@
+const stashListener = new EventTarget();
+
+const {
+ fetch: originalFetch
+} = window;
+
+window.fetch = async (...args) => {
+ let [resource, config] = args;
+ // request interceptor here
+ const response = await originalFetch(resource, config);
+ // response interceptor here
+ const contentType = response.headers.get("content-type");
+ if (contentType && contentType.indexOf("application/json") !== -1 && resource.endsWith('/graphql')) {
+ try {
+ const data = await response.clone().json();
+ stashListener.dispatchEvent(new CustomEvent('response', {
+ 'detail': data
+ }));
+ } catch (e) {
+
+ }
+ }
+ return response;
+};
+
+class Logger {
+ constructor(enabled) {
+ this.enabled = enabled;
+ }
+ debug() {
+ if (this.enabled) return;
+ console.debug(...arguments);
+ }
+}
+
+
+class Stash extends EventTarget {
+ constructor({
+ pageUrlCheckInterval = 100,
+ detectReRenders = false, // detects if .main element is re-rendered. eg: When you are in scenes page and clicking the scenes nav tab the url wont change but the elements are re-rendered, So with this you can listen and alter the elements inside the .main node
+ logging = false
+ } = {}) {
+ super();
+ this.log = new Logger(logging);
+ this._pageUrlCheckInterval = pageUrlCheckInterval;
+ this._detectReRenders = detectReRenders;
+ this.fireOnHashChangesToo = true;
+ this._lastPathStr = "";
+ this._lastQueryStr = "";
+ this._lastHashStr = "";
+ this._lastHref = "";
+ this._lastStashPageEvent = "";
+ this.waitForElement(this._detectReRenders ? ".main > div" : "html").then(() => {
+ this._pageURLCheckTimerId = setInterval(() => {
+ // Loop every 100 ms
+ if (
+ this._lastPathStr !== location.pathname ||
+ this._lastQueryStr !== location.search ||
+ (this.fireOnHashChangesToo && this._lastHashStr !== location.hash) ||
+ this._lastHref !== location.href ||
+ (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)
+ ) {
+ this._dispatchPageEvent("stash:page", false)
+ this.gmMain({
+ lastPathStr: this._lastPathStr,
+ lastQueryStr: this._lastQueryStr,
+ lastHashStr: this._lastHashStr,
+ lastHref: this._lastHref,
+ lastStashPageEvent: this._lastStashPageEvent,
+ });
+ this._lastPathStr = location.pathname
+ this._lastQueryStr = location.search
+ this._lastHashStr = location.hash
+ this._lastHref = location.href
+ if (this._detectReRenders) {
+ this.waitForElement(".main > div", 10000).then((element) => {
+ element.setAttribute("stashUserscriptLibrary", "");
+ })
+ }
+ }
+ }, this._pageUrlCheckInterval);
+ })
+ stashListener.addEventListener('response', (evt) => {
+ if (evt.detail.data?.plugins) {
+ this.getPluginVersion(evt.detail);
+ }
+ this.processRemoteScenes(evt.detail);
+ this.processScene(evt.detail);
+ this.processScenes(evt.detail);
+ this.processStudios(evt.detail);
+ this.processPerformers(evt.detail);
+ this.processApiKey(evt.detail);
+ this.dispatchEvent(new CustomEvent('stash:response', {
+ 'detail': evt.detail
+ }));
+ });
+ stashListener.addEventListener('pluginVersion', (evt) => {
+ if (this.pluginVersion !== evt.detail) {
+ this.pluginVersion = evt.detail;
+ this.dispatchEvent(new CustomEvent('stash:pluginVersion', {
+ 'detail': evt.detail
+ }));
+ }
+ });
+ this.version = [0, 0, 0];
+ this.getVersion();
+ this.pluginVersion = null;
+ this.getPlugins().then(plugins => this.getPluginVersion(plugins));
+ this.visiblePluginTasks = ['Userscript Functions'];
+ this.settingsCallbacks = [];
+ this.settingsId = 'userscript-settings';
+ this.remoteScenes = {};
+ this.scenes = {};
+ this.studios = {};
+ this.performers = {};
+ this.userscripts = [];
+ this._pageListeners = {};
+ this.assignPageListeners()
+ }
+ async getVersion() {
+ const reqData = {
+ "operationName": "",
+ "variables": {},
+ "query": `query version {
+ version {
+ version
+ }
+ }`
+ };
+ const data = await this.callGQL(reqData);
+ const versionString = data.data.version.version;
+ this.version = versionString.substring(1).split('.').map(o => parseInt(o));
+ }
+ compareVersion(minVersion) {
+ let [currMajor, currMinor, currPatch = 0] = this.version;
+ let [minMajor, minMinor, minPatch = 0] = minVersion.split('.').map(i => parseInt(i));
+ if (currMajor > minMajor) return 1;
+ if (currMajor < minMajor) return -1;
+ if (currMinor > minMinor) return 1;
+ if (currMinor < minMinor) return -1;
+ return 0;
+
+ }
+ comparePluginVersion(minPluginVersion) {
+ if (!this.pluginVersion) return -1;
+ let [currMajor, currMinor, currPatch = 0] = this.pluginVersion.split('.').map(i => parseInt(i));
+ let [minMajor, minMinor, minPatch = 0] = minPluginVersion.split('.').map(i => parseInt(i));
+ if (currMajor > minMajor) return 1;
+ if (currMajor < minMajor) return -1;
+ if (currMinor > minMinor) return 1;
+ if (currMinor < minMinor) return -1;
+ return 0;
+
+ }
+ async runPluginTask(pluginId, taskName, args = []) {
+ const reqData = {
+ "operationName": "RunPluginTask",
+ "variables": {
+ "plugin_id": pluginId,
+ "task_name": taskName,
+ "args": args
+ },
+ "query": "mutation RunPluginTask($plugin_id: ID!, $task_name: String!, $args: [PluginArgInput!]) {\n runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)\n}\n"
+ };
+ return this.callGQL(reqData);
+ }
+ async callGQL(reqData) {
+ const options = {
+ method: 'POST',
+ body: JSON.stringify(reqData),
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }
+
+ try {
+ const res = await window.fetch('/graphql', options);
+ // this.log.debug(res);
+ return res.json();
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ async getFreeOnesStats(link) {
+ try {
+ const doc = await fetch(link)
+ .then(function(response) {
+ // When the page is loaded convert it to text
+ return response.text()
+ })
+ .then(function(html) {
+ // Initialize the DOM parser
+ var parser = new DOMParser();
+
+ // Parse the text
+ var doc = parser.parseFromString(html, "text/html");
+
+ // You can now even select part of that html as you would in the regular DOM
+ // Example:
+ // var docArticle = doc.querySelector('article').innerHTML;
+
+ console.log(doc);
+ return doc
+ })
+ .catch(function(err) {
+ console.log('Failed to fetch page: ', err);
+ });
+
+ var data = new Object();
+ data.rank = doc.querySelector('rank-chart-button');
+ console.log(data.rank);
+ data.views = doc.querySelector('.d-none.d-m-flex.flex-column.align-items-center.global-header > div.font-weight-bold').textContent;
+ data.votes = '0'
+ return JSON.stringify(data);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ async getPlugins() {
+ const reqData = {
+ "operationName": "Plugins",
+ "variables": {},
+ "query": `query Plugins {
+ plugins {
+ id
+ name
+ description
+ url
+ version
+ tasks {
+ name
+ description
+ __typename
+ }
+ hooks {
+ name
+ description
+ hooks
+ }
+ }
+ }
+ `
+ };
+ return this.callGQL(reqData);
+ }
+ async getPluginVersion(plugins) {
+ let version = null;
+ for (const plugin of plugins?.data?.plugins || []) {
+ if (plugin.id === 'userscript_functions') {
+ version = plugin.version;
+ }
+ }
+ stashListener.dispatchEvent(new CustomEvent('pluginVersion', {
+ 'detail': version
+ }));
+ }
+ async getStashBoxes() {
+ const reqData = {
+ "operationName": "Configuration",
+ "variables": {},
+ "query": `query Configuration {
+ configuration {
+ general {
+ stashBoxes {
+ endpoint
+ api_key
+ name
+ }
+ }
+ }
+ }`
+ };
+ return this.callGQL(reqData);
+ }
+ async getApiKey() {
+ const reqData = {
+ "operationName": "Configuration",
+ "variables": {},
+ "query": `query Configuration {
+ configuration {
+ general {
+ apiKey
+ }
+ }
+ }`
+ };
+ return this.callGQL(reqData);
+ }
+ matchUrl(href, fragment) {
+ const regexp = concatRegexp(new RegExp(window.location.origin), fragment);
+ // this.log.debug(regexp, location.href.match(regexp));
+ return href.match(regexp) != null;
+ }
+ createSettings() {
+ waitForElementId('configuration-tabs-tabpane-system', async (elementId, el) => {
+ let section;
+ if (!document.getElementById(this.settingsId)) {
+ section = document.createElement("div");
+ section.setAttribute('id', this.settingsId);
+ section.classList.add('setting-section');
+ section.innerHTML = `
Userscript Settings
`;
+ el.appendChild(section);
+
+ const expectedApiKey = (await this.getApiKey())?.data?.configuration?.general?.apiKey || '';
+ const expectedUrl = window.location.origin;
+
+ const serverUrlInput = await this.createSystemSettingTextbox(section, 'userscript-section-server-url', 'userscript-server-url', 'Stash Server URL', '', 'Server URL…', true);
+ serverUrlInput.addEventListener('change', () => {
+ const value = serverUrlInput.value || '';
+ if (value) {
+ this.updateConfigValueTask('STASH', 'url', value);
+ alert(`Userscripts plugin server URL set to ${value}`);
+ } else {
+ this.getConfigValueTask('STASH', 'url').then(value => {
+ serverUrlInput.value = value;
+ });
+ }
+ });
+ serverUrlInput.disabled = true;
+ serverUrlInput.value = expectedUrl;
+ this.getConfigValueTask('STASH', 'url').then(value => {
+ if (value !== expectedUrl) {
+ return this.updateConfigValueTask('STASH', 'url', expectedUrl);
+ }
+ });
+
+ const apiKeyInput = await this.createSystemSettingTextbox(section, 'userscript-section-server-apikey', 'userscript-server-apikey', 'Stash API Key', '', 'API Key…', true);
+ apiKeyInput.addEventListener('change', () => {
+ const value = apiKeyInput.value || '';
+ this.updateConfigValueTask('STASH', 'api_key', value);
+ if (value) {
+ alert(`Userscripts plugin server api key set to ${value}`);
+ } else {
+ alert(`Userscripts plugin server api key value cleared`);
+ }
+ });
+ apiKeyInput.disabled = true;
+ apiKeyInput.value = expectedApiKey;
+ this.getConfigValueTask('STASH', 'api_key').then(value => {
+ if (value !== expectedApiKey) {
+ return this.updateConfigValueTask('STASH', 'api_key', expectedApiKey);
+ }
+ });
+ } else {
+ section = document.getElementById(this.settingsId);
+ }
+
+ for (const callback of this.settingsCallbacks) {
+ callback(this.settingsId, section);
+ }
+
+ if (this.pluginVersion) {
+ this.dispatchEvent(new CustomEvent('stash:pluginVersion', {
+ 'detail': this.pluginVersion
+ }));
+ }
+
+ });
+ }
+ addSystemSetting(callback) {
+ const section = document.getElementById(this.settingsId);
+ if (section) {
+ callback(this.settingsId, section);
+ }
+ this.settingsCallbacks.push(callback);
+ }
+ async createSystemSettingCheckbox(containerEl, settingsId, inputId, settingsHeader, settingsSubheader) {
+ const section = document.createElement("div");
+ section.setAttribute('id', settingsId);
+ section.classList.add('card');
+ section.style.display = 'none';
+ section.innerHTML = `
+
+
${settingsHeader}
+
${settingsSubheader}
+
+
+
`;
+ containerEl.appendChild(section);
+ return document.getElementById(inputId);
+ }
+ async createSystemSettingTextbox(containerEl, settingsId, inputId, settingsHeader, settingsSubheader, placeholder, visible) {
+ const section = document.createElement("div");
+ section.setAttribute('id', settingsId);
+ section.classList.add('card');
+ section.style.display = visible ? 'flex' : 'none';
+ section.innerHTML = `
+
+
${settingsHeader}
+
${settingsSubheader}
+
+
+
`;
+ containerEl.appendChild(section);
+ return document.getElementById(inputId);
+ }
+ get serverUrl() {
+ return window.location.origin;
+ }
+ async waitForElement(selector, timeout = null, location = document.body, disconnectOnPageChange = false) {
+ return new Promise((resolve) => {
+ if (document.querySelector(selector)) {
+ return resolve(document.querySelector(selector))
+ }
+
+ const observer = new MutationObserver(async () => {
+ if (document.querySelector(selector)) {
+ resolve(document.querySelector(selector))
+ observer.disconnect()
+ } else {
+ if (timeout) {
+ async function timeOver() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ observer.disconnect()
+ resolve(false)
+ }, timeout)
+ })
+ }
+ resolve(await timeOver())
+ }
+ }
+ })
+
+ observer.observe(location, {
+ childList: true,
+ subtree: true,
+ })
+
+ const stash = this
+ if (disconnectOnPageChange) {
+ function disconnect() {
+ observer.disconnect()
+ stash.removeEventListener("stash:page", disconnect)
+ }
+ stash.addEventListener("stash:page", disconnect)
+ }
+ })
+ }
+ async waitForElementDeath(selector, location = document.body, disconnectOnPageChange = false) {
+ return new Promise((resolve) => {
+ const observer = new MutationObserver(async () => {
+ if (!document.querySelector(selector)) {
+ resolve(true)
+ observer.disconnect()
+ }
+ })
+
+ observer.observe(location, {
+ childList: true,
+ subtree: true,
+ })
+
+ const stash = this
+ if (disconnectOnPageChange) {
+ function disconnect() {
+ observer.disconnect()
+ stash.removeEventListener("stash:page", disconnect)
+ }
+ stash.addEventListener("stash:page", disconnect)
+ }
+ })
+ }
+ async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", recursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){
+ if (recursive) return
+ if (listenType === "tabs") {
+ const locationElement = await this.waitForElement(location, 10000, document.body, true)
+ const stash = this
+ let previousEvent = ""
+ function listenForTabClicks(domEvent) {
+ const clickedChild = domEvent.target ? domEvent.target : domEvent;
+ if(!clickedChild.classList?.contains("nav-link")) return
+ const tagName = clickedChild.getAttribute("data-rb-event-key")
+ const parentEvent = tagName.split("-")[0]
+ const childEvent = tagName.split("-").slice(1, -1).join("-")
+ event = `stash:page:${parentEvent}:${childEvent}`
+ if (previousEvent === event || !condition()) return
+ previousEvent = event
+ stash._dispatchPageEvent(`stash:page:any:${childEvent}`, false)
+ stash._dispatchPageEvent(event)
+ }
+ if (listenDefaultTab) listenForTabClicks(locationElement.querySelector(".nav-link.active"))
+ locationElement.addEventListener("click", listenForTabClicks);
+ function removeEventListenerOnPageChange() {
+ locationElement.removeEventListener("click", listenForTabClicks)
+ stash.removeEventListener("stash:page", removeEventListenerOnPageChange)
+ }
+ stash.addEventListener("stash:page", removeEventListenerOnPageChange)
+ } else if (await this.waitForElement(selector, null, location, true)) {
+ this._dispatchPageEvent(event)
+ if (await this.waitForElementDeath(selector, location, true)) {
+ if (this._lastPathStr === window.location.pathname && !reRunGmMain) {
+ await this._listenForNonPageChanges({selector: selector, event: event})
+ } else if (this._lastPathStr === window.location.pathname && reRunGmMain) {
+ this.gmMain({
+ recursive: true,
+ lastPathStr: this._lastPathStr,
+ lastQueryStr: this._lastQueryStr,
+ lastHashStr: this._lastHashStr,
+ lastHref: this._lastHref,
+ lastStashPageEvent: this._lastStashPageEvent,
+ });
+ }
+ }
+ }
+ callback()
+ }
+ _dispatchPageEvent(event, addToHistory = true) {
+ this.dispatchEvent(new CustomEvent(event, {
+ detail: {
+ event: event,
+ lastEventState: {
+ lastPathStr: this._lastPathStr,
+ lastQueryStr: this._lastQueryStr,
+ lastHashStr: this._lastHashStr,
+ lastHref: this._lastHref,
+ lastStashPageEvent: this._lastStashPageEvent,
+ }
+ }
+ }))
+ if (addToHistory) {
+ this.log.debug(`[Navigation] ${event}`);
+ if (event.startsWith("stash:")) {
+ this._lastStashPageEvent = event;
+ }
+ }
+ }
+ addPageListener(eventData) {
+ const {event, regex, callBack = () => {}, manuallyHandleDispatchEvent = false} = eventData
+ if (event && !event?.startsWith("stash:") && regex && this._pageListeners[event] === undefined){
+ this._pageListeners[event] = {
+ regex: regex,
+ callBack: callBack,
+ manuallyHandleDispatchEvent: manuallyHandleDispatchEvent
+ }
+ return event
+ } else {
+ if (this._pageListeners[event] !== undefined) {
+ console.error(`Can't add page listener: Event ${event} already exists`)
+ } else if (event?.startsWith("stash:")) {
+ console.error(`Can't add page listener: Event name can't start with "stash:"`)
+ } else {
+ console.error(`Can't add page listener: Missing required argument(s) "event", "regex"`)
+ }
+ return false
+ }
+ }
+ removePageListener(event) {
+ if(event && !event?.startsWith("stash:") && this._pageListeners[event]){
+ delete this._pageListeners[event]
+ return event
+ } else {
+ if (this._pageListeners[event] === undefined && event) {
+ console.error(`Can't remove page listener: Event ${event} doesn't exists`)
+ } else if (event?.startsWith("stash:")) {
+ console.error(`Can't remove page listener: Event ${event} is a built in event`)
+ } else {
+ console.error(`Can't remove page listener: Missing "event" argument`)
+ }
+ return false
+ }
+ }
+ stopPageListener() {
+ clearInterval(this._pageURLCheckTimerId)
+ }
+ assignPageListeners() {
+ this._pageListeners = {
+ // scenes tab
+ "stash:page:scenes": {
+ regex: /\/scenes\?/,
+ handleDisplayView: true,
+ callBack: () => this.processTagger()
+ },
+ "stash:page:scene:new": {
+ regex: /\/scenes\/new/
+ },
+ "stash:page:scene": {
+ regex: /\/scenes\/\d+/,
+ callBack: ({recursive = false}) => this._listenForNonPageChanges({
+ location: ".scene-tabs .nav-tabs",
+ listenType: "tabs",
+ recursive: recursive
+ })
+ },
+
+ // images tab
+ "stash:page:images": {
+ regex: /\/images\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:image": {
+ regex: /\/images\/\d+/,
+ callBack: ({recursive = false}) => this._listenForNonPageChanges({
+ location: ".image-tabs .nav-tabs",
+ listenType: "tabs",
+ recursive: recursive
+ })
+ },
+
+ // movies tab
+ "stash:page:movies": {
+ regex: /\/movies\?/,
+ },
+ "stash:page:movie": {
+ regex: /\/movies\/\d+/,
+ },
+ "stash:page:movie:scenes": {
+ regex: /\/movies\/\d+\?/,
+ callBack: () => this.processTagger()
+ },
+
+ // markers tab
+ "stash:page:markers": {
+ regex: /\/scenes\/markers/
+ },
+
+ // galleries tab
+ "stash:page:galleries": {
+ regex: /\/galleries\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:gallery:new": {
+ regex: /\/galleries\/new/,
+ },
+ "stash:page:gallery:images": {
+ regex: /\/galleries\/\d+\?/,
+ manuallyHandleDispatchEvent: true,
+ handleDisplayView: "ignoreDisplayViewCondition",
+ callBack: ({lastHref, recursive = false}, event) => {
+ if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){
+ this._dispatchPageEvent("stash:page:gallery");
+ this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"})
+ }
+
+ this._dispatchPageEvent(event);
+
+ this._listenForNonPageChanges({
+ location: ".gallery-tabs .nav-tabs",
+ listenType: "tabs",
+ recursive: recursive,
+ listenDefaultTab: false
+ })
+ }
+ },
+ "stash:page:gallery:add": {
+ regex: /\/galleries\/\d+\/add/,
+ manuallyHandleDispatchEvent: true,
+ handleDisplayView: "ignoreDisplayViewCondition",
+ callBack: ({lastHref, recursive = false}, event) => {
+ if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){
+ this._dispatchPageEvent("stash:page:gallery");
+ this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"})
+ }
+
+ this._dispatchPageEvent(event);
+
+ this._listenForNonPageChanges({
+ location: ".gallery-tabs .nav-tabs",
+ listenType: "tabs",
+ recursive: recursive,
+ listenDefaultTab: false
+ })
+ }
+ },
+
+ // performers tab
+ "stash:page:performers": {
+ regex: /\/performers\?/,
+ manuallyHandleDispatchEvent: true,
+ handleDisplayView: true,
+ callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/performers\?/) || this._detectReRenders ? this._dispatchPageEvent(event) : null
+ },
+ "stash:page:performer:new": {
+ regex: /\/performers\/new/
+ },
+ "stash:page:performer": {
+ regex: /\/performers\/\d+/,
+ manuallyHandleDispatchEvent: true,
+ callBack: ({lastHref}, event) => {
+ if(!this.matchUrl(lastHref, /\/performers\/\d+/)){
+ this._dispatchPageEvent(event);
+ this.processTagger();
+ }
+
+ this._listenForNonPageChanges({
+ selector: "#performer-edit",
+ event: "stash:page:performer:edit",
+ reRunGmMain: true,
+ callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null
+ })
+ }
+ },
+ "stash:page:performer:scenes": {
+ regex: /\/performers\/\d+\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:performer:galleries": {
+ regex: /\/performers\/\d+\/galleries/,
+ handleDisplayView: true
+ },
+ "stash:page:performer:images": {
+ regex: /\/performers\/\d+\/images/,
+ handleDisplayView: true
+ },
+ "stash:page:performer:movies": {
+ regex: /\/performers\/\d+\/movies/
+ },
+ "stash:page:performer:appearswith": {
+ regex: /\/performers\/\d+\/appearswith/,
+ handleDisplayView: true,
+ callBack: () => this.processTagger()
+ },
+
+ // studios tab
+ "stash:page:studios": {
+ regex: /\/studios\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:new": {
+ regex: /\/studios\/new/
+ },
+ "stash:page:studio": {
+ regex: /\/studios\/\d+/,
+ manuallyHandleDispatchEvent: true,
+ callBack: ({lastHref}, event) => {
+ if(!this.matchUrl(lastHref, /\/studios\/\d+/)){
+ this._dispatchPageEvent(event);
+ this.processTagger();
+ }
+
+ this._listenForNonPageChanges({
+ selector: "#studio-edit",
+ event: "stash:page:studio:edit",
+ reRunGmMain: true,
+ callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null
+ })
+ }
+ },
+ "stash:page:studio:scenes": {
+ regex: /\/studios\/\d+\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:galleries": {
+ regex: /\/studios\/\d+\/galleries/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:images": {
+ regex: /\/studios\/\d+\/images/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:performers": {
+ regex: /\/studios\/\d+\/performers/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:movies": {
+ regex: /\/studios\/\d+\/movies/
+ },
+ "stash:page:studio:childstudios": {
+ regex: /\/studios\/\d+\/childstudios/,
+ handleDisplayView: true,
+ },
+
+ // tags tab
+ "stash:page:tags": {
+ regex: /\/tags\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:tag:new": {
+ regex: /\/tags\/new/
+ },
+ "stash:page:tag": {
+ regex: /\/tags\/\d+/,
+ manuallyHandleDispatchEvent: true,
+ callBack: ({lastHref}, event) => {
+ if(!this.matchUrl(lastHref, /\/tags\/\d+/)){
+ this._dispatchPageEvent(event);
+ this.processTagger();
+ }
+
+ this._listenForNonPageChanges({
+ selector: "#tag-edit",
+ event: "stash:page:tag:edit",
+ reRunGmMain: true,
+ callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null
+ })
+ }
+ },
+ "stash:page:tag:scenes": {
+ regex: /\/tags\/\d+\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:tag:galleries": {
+ regex: /\/tags\/\d+\/galleries/,
+ handleDisplayView: true,
+ },
+ "stash:page:tag:images": {
+ regex: /\/tags\/\d+\/images/,
+ handleDisplayView: true,
+ },
+ "stash:page:tag:markers": {
+ regex: /\/tags\/\d+\/markers/
+ },
+ "stash:page:tag:performers": {
+ regex: /\/tags\/\d+\/performers/,
+ handleDisplayView: true,
+ },
+
+ // settings page
+ "stash:page:settings": {
+ regex: /\/settings/,
+ manuallyHandleDispatchEvent: true,
+ callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/settings/) ? this._dispatchPageEvent(event) : null
+ },
+ "stash:page:settings:tasks": {
+ regex: /\/settings\?tab=tasks/,
+ callback: () => this.hidePluginTasks()
+ },
+ "stash:page:settings:library": {
+ regex: /\/settings\?tab=library/
+ },
+ "stash:page:settings:interface": {
+ regex: /\/settings\?tab=interface/
+ },
+ "stash:page:settings:security": {
+ regex: /\/settings\?tab=security/
+ },
+ "stash:page:settings:metadata-providers": {
+ regex: /\/settings\?tab=metadata-providers/
+ },
+ "stash:page:settings:services": {
+ regex: /\/settings\?tab=services/
+ },
+ "stash:page:settings:system": {
+ regex: /\/settings\?tab=system/,
+ callBack: () => this.createSettings()
+ },
+ "stash:page:settings:plugins": {
+ regex: /\/settings\?tab=plugins/
+ },
+ "stash:page:settings:logs": {
+ regex: /\/settings\?tab=logs/
+ },
+ "stash:page:settings:tools": {
+ regex: /\/settings\?tab=tools/
+ },
+ "stash:page:settings:changelog": {
+ regex: /\/settings\?tab=changelog/
+ },
+ "stash:page:settings:about": {
+ regex: /\/settings\?tab=about/
+ },
+
+ // stats page
+ "stash:page:stats": {
+ regex: /\/stats/
+ },
+
+ // home page
+ "stash:page:home": {
+ regex: /\/$/,
+ callBack: () => this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "stash:page:home:edit", reRunGmMain: true})
+ },
+ }
+ }
+ gmMain(args) {
+ const events = Object.keys(this._pageListeners)
+
+ for (const event of events) {
+ const {regex, callBack = async () => {}, manuallyHandleDispatchEvent = false, handleDisplayView = false} = this._pageListeners[event]
+
+ let isDisplayViewPage = false
+ let isListPage, isWallPage, isTaggerPage
+
+ if (handleDisplayView) {
+ isListPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=1/))
+ isWallPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=2/))
+ isTaggerPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=3/))
+
+ if (isListPage || isWallPage || isTaggerPage) isDisplayViewPage = true
+ }
+
+ const handleDisplayViewCondition = handleDisplayView !== true || (handleDisplayView && (!isDisplayViewPage || args.lastHref === ""))
+
+ if (this.matchUrl(window.location.href, regex) && handleDisplayViewCondition) {
+ if (!manuallyHandleDispatchEvent) this._dispatchPageEvent(event)
+ callBack({...args, location: window.location}, event)
+ }
+
+ if (handleDisplayView) {
+ if (isListPage) {
+ this._dispatchPageEvent("stash:page:any:list", false);
+ this._dispatchPageEvent(event + ":list");
+ } else if (isWallPage) {
+ this._dispatchPageEvent("stash:page:any:wall", false);
+ this._dispatchPageEvent(event + ":wall");
+ } else if (isTaggerPage) {
+ this._dispatchPageEvent("stash:page:any:tagger", false);
+ this._dispatchPageEvent(event + ":tagger");
+ }
+ }
+ }
+ }
+ addEventListeners(events, callback, ...options) {
+ events.forEach((event) => {
+ this.addEventListener(event, callback, ...options);
+ });
+ }
+ hidePluginTasks() {
+ // hide userscript functions plugin tasks
+ waitForElementByXpath("//div[@id='tasks-panel']//h3[text()='Userscript Functions']/ancestor::div[contains(@class, 'setting-group')]", (elementId, el) => {
+ const tasks = el.querySelectorAll('.setting');
+ for (const task of tasks) {
+ const taskName = task.querySelector('h3').innerText;
+ task.classList.add(this.visiblePluginTasks.indexOf(taskName) === -1 ? 'd-none' : 'd-flex');
+ this.dispatchEvent(new CustomEvent('stash:plugin:task', {
+ 'detail': {
+ taskName,
+ task
+ }
+ }));
+ }
+ });
+ }
+ async updateConfigValueTask(sectionKey, propName, value) {
+ return this.runPluginTask("userscript_functions", "Update Config Value", [{
+ "key": "section_key",
+ "value": {
+ "str": sectionKey
+ }
+ }, {
+ "key": "prop_name",
+ "value": {
+ "str": propName
+ }
+ }, {
+ "key": "value",
+ "value": {
+ "str": value
+ }
+ }]);
+ }
+ async getConfigValueTask(sectionKey, propName) {
+ await this.runPluginTask("userscript_functions", "Get Config Value", [{
+ "key": "section_key",
+ "value": {
+ "str": sectionKey
+ }
+ }, {
+ "key": "prop_name",
+ "value": {
+ "str": propName
+ }
+ }]);
+
+ // poll logs until plugin task output appears
+ const prefix = `[Plugin / Userscript Functions] get_config_value: [${sectionKey}][${propName}] =`;
+ return this.pollLogsForMessage(prefix);
+ }
+ async pollLogsForMessage(prefix) {
+ const reqTime = Date.now();
+ const reqData = {
+ "variables": {},
+ "query": `query Logs {
+ logs {
+ time
+ level
+ message
+ }
+ }`
+ };
+ await new Promise(r => setTimeout(r, 500));
+ let retries = 0;
+ while (true) {
+ const delay = 2 ** retries * 100;
+ await new Promise(r => setTimeout(r, delay));
+ retries++;
+
+ const logs = await this.callGQL(reqData);
+ for (const log of logs.data.logs) {
+ const logTime = Date.parse(log.time);
+ if (logTime > reqTime && log.message.startsWith(prefix)) {
+ return log.message.replace(prefix, '').trim();
+ }
+ }
+
+ if (retries >= 5) {
+ throw `Poll logs failed for message: ${prefix}`;
+ }
+ }
+ }
+ processTagger() {
+ waitForElementByXpath("//button[text()='Scrape All']", (xpath, el) => {
+ this.dispatchEvent(new CustomEvent('tagger', {
+ 'detail': el
+ }));
+
+ const searchItemContainer = document.querySelector('.tagger-container').lastChild;
+
+ const observer = new MutationObserver(mutations => {
+ mutations.forEach(mutation => {
+ mutation.addedNodes.forEach(node => {
+ if (node?.classList?.contains('entity-name') && node.innerText.startsWith('Performer:')) {
+ this.dispatchEvent(new CustomEvent('tagger:mutation:add:remoteperformer', {
+ 'detail': {
+ node,
+ mutation
+ }
+ }));
+ } else if (node?.classList?.contains('entity-name') && node.innerText.startsWith('Studio:')) {
+ this.dispatchEvent(new CustomEvent('tagger:mutation:add:remotestudio', {
+ 'detail': {
+ node,
+ mutation
+ }
+ }));
+ } else if (node.tagName === 'SPAN' && node.innerText.startsWith('Matched:')) {
+ this.dispatchEvent(new CustomEvent('tagger:mutation:add:local', {
+ 'detail': {
+ node,
+ mutation
+ }
+ }));
+ } else if (node.tagName === 'UL') {
+ this.dispatchEvent(new CustomEvent('tagger:mutation:add:container', {
+ 'detail': {
+ node,
+ mutation
+ }
+ }));
+ } else if (node?.classList?.contains('col-lg-6')) {
+ this.dispatchEvent(new CustomEvent('tagger:mutation:add:subcontainer', {
+ 'detail': {
+ node,
+ mutation
+ }
+ }));
+ } else if (node.tagName === 'H5') { // scene date
+ this.dispatchEvent(new CustomEvent('tagger:mutation:add:date', {
+ 'detail': {
+ node,
+ mutation
+ }
+ }));
+ } else if (node.tagName === 'DIV' && node?.classList?.contains('d-flex') && node?.classList?.contains('flex-column')) { // scene stashid, url, details
+ this.dispatchEvent(new CustomEvent('tagger:mutation:add:detailscontainer', {
+ 'detail': {
+ node,
+ mutation
+ }
+ }));
+ } else {
+ this.dispatchEvent(new CustomEvent('tagger:mutation:add:other', {
+ 'detail': {
+ node,
+ mutation
+ }
+ }));
+ }
+ });
+ });
+ this.dispatchEvent(new CustomEvent('tagger:mutations:searchitems', {
+ 'detail': mutations
+ }));
+ });
+ observer.observe(searchItemContainer, {
+ childList: true,
+ subtree: true
+ });
+
+ const taggerContainerHeader = document.querySelector('.tagger-container-header');
+ const taggerContainerHeaderObserver = new MutationObserver(mutations => {
+ this.dispatchEvent(new CustomEvent('tagger:mutations:header', {
+ 'detail': mutations
+ }));
+ });
+ taggerContainerHeaderObserver.observe(taggerContainerHeader, {
+ childList: true,
+ subtree: true
+ });
+
+ for (const searchItem of document.querySelectorAll('.search-item')) {
+ this.dispatchEvent(new CustomEvent('tagger:searchitem', {
+ 'detail': searchItem
+ }));
+ }
+
+ if (!document.getElementById('progress-bar')) {
+ const progressBar = createElementFromHTML(``);
+ progressBar.classList.add('progress');
+ progressBar.style.display = 'none';
+ taggerContainerHeader.appendChild(progressBar);
+ }
+ });
+ waitForElementByXpath("//div[@class='tagger-container-header']/div/div[@class='row']/h4[text()='Configuration']", (xpath, el) => {
+ this.dispatchEvent(new CustomEvent('tagger:configuration', {
+ 'detail': el
+ }));
+ });
+ }
+ setProgress(value) {
+ const progressBar = document.getElementById('progress-bar');
+ if (progressBar) {
+ progressBar.firstChild.style.width = value + '%';
+ progressBar.style.display = (value <= 0 || value > 100) ? 'none' : 'flex';
+ }
+ }
+ processRemoteScenes(data) {
+ if (data.data?.scrapeMultiScenes) {
+ for (const matchResults of data.data.scrapeMultiScenes) {
+ for (const scene of matchResults) {
+ this.remoteScenes[scene.remote_site_id] = scene;
+ }
+ }
+ } else if (data.data?.scrapeSingleScene) {
+ for (const scene of data.data.scrapeSingleScene) {
+ this.remoteScenes[scene.remote_site_id] = scene;
+ }
+ }
+ }
+ processScene(data) {
+ if (data.data.findScene) {
+ this.scenes[data.data.findScene.id] = data.data.findScene;
+ }
+ }
+ processScenes(data) {
+ if (data.data.findScenes?.scenes) {
+ for (const scene of data.data.findScenes.scenes) {
+ this.scenes[scene.id] = scene;
+ }
+ }
+ }
+ processStudios(data) {
+ if (data.data.findStudios?.studios) {
+ for (const studio of data.data.findStudios.studios) {
+ this.studios[studio.id] = studio;
+ }
+ }
+ }
+ processPerformers(data) {
+ if (data.data.findPerformers?.performers) {
+ for (const performer of data.data.findPerformers.performers) {
+ this.performers[performer.id] = performer;
+ }
+ }
+ }
+ processApiKey(data) {
+ if (data.data.generateAPIKey != null && this.pluginVersion) {
+ this.updateConfigValueTask('STASH', 'api_key', data.data.generateAPIKey);
+ }
+ }
+ parseSearchItem(searchItem) {
+ const urlNode = searchItem.querySelector('a.scene-link');
+ const url = new URL(urlNode.href);
+ const id = url.pathname.replace('/scenes/', '');
+ const data = this.scenes[id];
+ const nameNode = searchItem.querySelector('a.scene-link > div.TruncatedText');
+ const name = nameNode.innerText;
+ const queryInput = searchItem.querySelector('input.text-input');
+ const performerNodes = searchItem.querySelectorAll('.performer-tag-container');
+
+ return {
+ urlNode,
+ url,
+ id,
+ data,
+ nameNode,
+ name,
+ queryInput,
+ performerNodes
+ }
+ }
+ parseSearchResultItem(searchResultItem) {
+ const remoteUrlNode = searchResultItem.querySelector('.scene-details .optional-field .optional-field-content a');
+ const remoteId = remoteUrlNode?.href.split('/').pop();
+ const remoteUrl = remoteUrlNode?.href ? new URL(remoteUrlNode.href) : null;
+ const remoteData = this.remoteScenes[remoteId];
+
+ const sceneDetailNodes = searchResultItem.querySelectorAll('.scene-details .optional-field .optional-field-content');
+ let urlNode = null;
+ let detailsNode = null;
+ for (const sceneDetailNode of sceneDetailNodes) {
+ if (sceneDetailNode.innerText.startsWith('http') && (remoteUrlNode?.href !== sceneDetailNode.innerText)) {
+ urlNode = sceneDetailNode;
+ } else if (!sceneDetailNode.innerText.startsWith('http')) {
+ detailsNode = sceneDetailNode;
+ }
+ }
+
+ const imageNode = searchResultItem.querySelector('.scene-image-container .optional-field .optional-field-content');
+
+ const metadataNode = searchResultItem.querySelector('.scene-metadata');
+ const titleNode = metadataNode.querySelector('h4 .optional-field .optional-field-content');
+ const codeAndDateNodes = metadataNode.querySelectorAll('h5 .optional-field .optional-field-content');
+ let codeNode = null;
+ let dateNode = null;
+ for (const node of codeAndDateNodes) {
+ if (node.textContent.includes('-')) {
+ dateNode = node;
+ } else {
+ codeNode = node;
+ }
+ }
+
+ const entityNodes = searchResultItem.querySelectorAll('.entity-name');
+ let studioNode = null;
+ const performerNodes = [];
+ for (const entityNode of entityNodes) {
+ if (entityNode.innerText.startsWith('Studio:')) {
+ studioNode = entityNode;
+ } else if (entityNode.innerText.startsWith('Performer:')) {
+ performerNodes.push(entityNode);
+ }
+ }
+
+ const matchNodes = searchResultItem.querySelectorAll('div.col-lg-6 div.mt-2 div.row.no-gutters.my-2 span.ml-auto');
+ const matches = []
+ for (const matchNode of matchNodes) {
+ let matchType = null;
+ const entityNode = matchNode.parentElement.querySelector('.entity-name');
+
+ const matchName = matchNode.querySelector('.optional-field-content b').innerText;
+ const remoteName = entityNode.querySelector('b').innerText;
+
+ let data;
+ if (entityNode.innerText.startsWith('Performer:')) {
+ matchType = 'performer';
+ if (remoteData) {
+ data = remoteData.performers.find(performer => performer.name === remoteName);
+ }
+ } else if (entityNode.innerText.startsWith('Studio:')) {
+ matchType = 'studio';
+ if (remoteData) {
+ data = remoteData.studio
+ }
+ }
+
+ matches.push({
+ matchType,
+ matchNode,
+ entityNode,
+ matchName,
+ remoteName,
+ data
+ });
+ }
+
+ return {
+ remoteUrlNode,
+ remoteId,
+ remoteUrl,
+ remoteData,
+ urlNode,
+ detailsNode,
+ imageNode,
+ titleNode,
+ codeNode,
+ dateNode,
+ studioNode,
+ performerNodes,
+ matches
+ }
+ }
+}
+
+window.stash = new Stash();
+
+function waitForElementQuerySelector(query, callBack, time) {
+ time = (typeof time !== 'undefined') ? time : 100;
+ window.setTimeout(() => {
+ const element = document.querySelector(query);
+ if (element) {
+ callBack(query, element);
+ } else {
+ waitForElementQuerySelector(query, callBack, time);
+ }
+ }, time);
+}
+
+function waitForElementClass(elementId, callBack, time) {
+ time = (typeof time !== 'undefined') ? time : 100;
+ window.setTimeout(() => {
+ const element = document.getElementsByClassName(elementId);
+ if (element.length > 0) {
+ callBack(elementId, element);
+ } else {
+ waitForElementClass(elementId, callBack, time);
+ }
+ }, time);
+}
+
+function waitForElementId(elementId, callBack, time) {
+ time = (typeof time !== 'undefined') ? time : 100;
+ window.setTimeout(() => {
+ const element = document.getElementById(elementId);
+ if (element != null) {
+ callBack(elementId, element);
+ } else {
+ waitForElementId(elementId, callBack, time);
+ }
+ }, time);
+}
+
+function waitForElementByXpath(xpath, callBack, time) {
+ time = (typeof time !== 'undefined') ? time : 100;
+ window.setTimeout(() => {
+ const element = getElementByXpath(xpath);
+ if (element) {
+ callBack(xpath, element);
+ } else {
+ waitForElementByXpath(xpath, callBack, time);
+ }
+ }, time);
+}
+
+function getElementByXpath(xpath, contextNode) {
+ return document.evaluate(xpath, contextNode || document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+}
+
+function createElementFromHTML(htmlString) {
+ const div = document.createElement('div');
+ div.innerHTML = htmlString.trim();
+
+ // Change this to div.childNodes to support multiple top-level nodes.
+ return div.firstChild;
+}
+
+function getElementByXpath(xpath, contextNode) {
+ return document.evaluate(xpath, contextNode || document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+}
+
+function getElementsByXpath(xpath, contextNode) {
+ return document.evaluate(xpath, contextNode || document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
+}
+
+function getClosestAncestor(el, selector, stopSelector) {
+ let retval = null;
+ while (el) {
+ if (el.matches(selector)) {
+ retval = el;
+ break
+ } else if (stopSelector && el.matches(stopSelector)) {
+ break
+ }
+ el = el.parentElement;
+ }
+ return retval;
+}
+
+function setNativeValue(element, value) {
+ const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
+ const prototype = Object.getPrototypeOf(element);
+ const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
+
+ if (valueSetter && valueSetter !== prototypeValueSetter) {
+ prototypeValueSetter.call(element, value);
+ } else {
+ valueSetter.call(element, value);
+ }
+}
+
+function updateTextInput(element, value) {
+ setNativeValue(element, value);
+ element.dispatchEvent(new Event('input', {
+ bubbles: true
+ }));
+}
+
+function concatRegexp(reg, exp) {
+ let flags = reg.flags + exp.flags;
+ flags = Array.from(new Set(flags.split(''))).join();
+ return new RegExp(reg.source + exp.source, flags);
+}
+
+function sortElementChildren(node) {
+ const items = node.childNodes;
+ const itemsArr = [];
+ for (const i in items) {
+ if (items[i].nodeType == Node.ELEMENT_NODE) { // get rid of the whitespace text nodes
+ itemsArr.push(items[i]);
+ }
+ }
+
+ itemsArr.sort((a, b) => {
+ return a.innerHTML == b.innerHTML ?
+ 0 :
+ (a.innerHTML > b.innerHTML ? 1 : -1);
+ });
+
+ for (let i = 0; i < itemsArr.length; i++) {
+ node.appendChild(itemsArr[i]);
+ }
+}
+
+function xPathResultToArray(result) {
+ let node = null;
+ const nodes = [];
+ while (node = result.iterateNext()) {
+ nodes.push(node);
+ }
+ return nodes;
+}
+
+function createStatElement(container, title, heading) {
+ const statEl = document.createElement('div');
+ statEl.classList.add('stats-element');
+ container.appendChild(statEl);
+
+ const statTitle = document.createElement('p');
+ statTitle.classList.add('title');
+ statTitle.innerText = title;
+ statEl.appendChild(statTitle);
+
+ const statHeading = document.createElement('p');
+ statHeading.classList.add('heading');
+ statHeading.innerText = heading;
+ statEl.appendChild(statHeading);
+}
+
+const reloadImg = url =>
+ fetch(url, {
+ cache: 'reload',
+ mode: 'no-cors'
+ })
+ .then(() => document.body.querySelectorAll(`img[src='${url}']`)
+ .forEach(img => img.src = url));
diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js
index 6e2ee88..7915210 100644
--- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js
+++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js
@@ -45,23 +45,41 @@ class Stash extends EventTarget {
this._pageUrlCheckInterval = pageUrlCheckInterval;
this._detectReRenders = detectReRenders;
this.fireOnHashChangesToo = true;
- waitForElementQuerySelector(this._detectReRenders ? ".main > div" : "html", () => {
- this.pageURLCheckTimer = setInterval(() => {
+ this._lastPathStr = "";
+ this._lastQueryStr = "";
+ this._lastHashStr = "";
+ this._lastHref = "";
+ this._lastStashPageEvent = "";
+ this.waitForElement(this._detectReRenders ? ".main > div" : "html").then(() => {
+ this._pageURLCheckTimerId = setInterval(() => {
// Loop every 100 ms
- if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash) || this.lastHref !== location.href || (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)) {
- this.log.debug('[Navigation] Page Changed');
- this.dispatchEvent(new Event('page'));
- this.gmMain(false, this.lastHref);
- this.lastPathStr = location.pathname;
- this.lastQueryStr = location.search;
- this.lastHashStr = location.hash;
- this.lastHref = location.href;
- if (this._detectReRenders) waitForElementQuerySelector(".main > div", (query, element) => {
- element.setAttribute("stashUserscriptLibrary", "");
- }, 10)
+ if (
+ this._lastPathStr !== location.pathname ||
+ this._lastQueryStr !== location.search ||
+ (this.fireOnHashChangesToo && this._lastHashStr !== location.hash) ||
+ this._lastHref !== location.href ||
+ (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)
+ ) {
+ this._dispatchPageEvent("stash:page", false)
+ this.gmMain({
+ lastPathStr: this._lastPathStr,
+ lastQueryStr: this._lastQueryStr,
+ lastHashStr: this._lastHashStr,
+ lastHref: this._lastHref,
+ lastStashPageEvent: this._lastStashPageEvent,
+ });
+ this._lastPathStr = location.pathname
+ this._lastQueryStr = location.search
+ this._lastHashStr = location.hash
+ this._lastHref = location.href
+ if (this._detectReRenders) {
+ this.waitForElement(".main > div", 10000).then((element) => {
+ element.setAttribute("stashUserscriptLibrary", "");
+ })
+ }
}
}, this._pageUrlCheckInterval);
- }, 1)
+ })
stashListener.addEventListener('response', (evt) => {
if (evt.detail.data?.plugins) {
this.getPluginVersion(evt.detail);
@@ -96,17 +114,18 @@ class Stash extends EventTarget {
this.studios = {};
this.performers = {};
this.userscripts = [];
+ this._pageListeners = {};
+ this.assignPageListeners()
}
async getVersion() {
const reqData = {
"operationName": "",
"variables": {},
"query": `query version {
- version {
- version
- }
-}
-`
+ version {
+ version
+ }
+ }`
};
const data = await this.callGQL(reqData);
const versionString = data.data.version.version;
@@ -267,10 +286,10 @@ class Stash extends EventTarget {
};
return this.callGQL(reqData);
}
- matchUrl(location, fragment) {
- const regexp = concatRegexp(new RegExp(location.origin), fragment);
+ matchUrl(href, fragment) {
+ const regexp = concatRegexp(new RegExp(window.location.origin), fragment);
this.log.debug(regexp, location.href.match(regexp));
- return location.href.match(regexp) != null;
+ return href.match(regexp) != null;
}
createSettings() {
waitForElementId('configuration-tabs-tabpane-system', async (elementId, el) => {
@@ -421,9 +440,9 @@ class Stash extends EventTarget {
if (disconnectOnPageChange) {
function disconnect() {
observer.disconnect()
- stash.removeEventListener("page", disconnect)
+ stash.removeEventListener("stash:page", disconnect)
}
- this.addEventListener("page", disconnect)
+ stash.addEventListener("stash:page", disconnect)
}
})
}
@@ -445,14 +464,14 @@ class Stash extends EventTarget {
if (disconnectOnPageChange) {
function disconnect() {
observer.disconnect()
- stash.removeEventListener("page", disconnect)
+ stash.removeEventListener("stash:page", disconnect)
}
- this.addEventListener("page", disconnect)
+ stash.addEventListener("stash:page", disconnect)
}
})
}
- async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", eventMessage = "", isRecursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){
- if (isRecursive) return
+ async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", recursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){
+ if (recursive) return
if (listenType === "tabs") {
const locationElement = await this.waitForElement(location, 10000, document.body, true)
const stash = this
@@ -463,386 +482,431 @@ class Stash extends EventTarget {
const tagName = clickedChild.getAttribute("data-rb-event-key")
const parentEvent = tagName.split("-")[0]
const childEvent = tagName.split("-").slice(1, -1).join("-")
- event = `page:${parentEvent}:${childEvent}`
+ event = `stash:page:${parentEvent}:${childEvent}`
if (previousEvent === event || !condition()) return
previousEvent = event
- stash.log.debug("[Navigation] " + `${parentEvent[0].toUpperCase() + parentEvent.slice(1)} Page - ${childEvent[0].toUpperCase() + childEvent.slice(1)}`);
- stash.dispatchEvent(new Event(event));
+ stash._dispatchPageEvent(`stash:page:any:${childEvent}`, false)
+ stash._dispatchPageEvent(event)
}
if (listenDefaultTab) listenForTabClicks(locationElement.querySelector(".nav-link.active"))
locationElement.addEventListener("click", listenForTabClicks);
function removeEventListenerOnPageChange() {
locationElement.removeEventListener("click", listenForTabClicks)
- stash.removeEventListener("page", removeEventListenerOnPageChange)
+ stash.removeEventListener("stash:page", removeEventListenerOnPageChange)
}
- stash.addEventListener("page", removeEventListenerOnPageChange)
+ stash.addEventListener("stash:page", removeEventListenerOnPageChange)
} else if (await this.waitForElement(selector, null, location, true)) {
- this.log.debug("[Navigation] " + eventMessage);
- this.dispatchEvent(new Event(event));
+ this._dispatchPageEvent(event)
if (await this.waitForElementDeath(selector, location, true)) {
- if (this.lastPathStr === window.location.pathname && !reRunGmMain) {
- this._listenForNonPageChanges({selector: selector, event: event, eventMessage: eventMessage})
- } else if (this.lastPathStr === window.location.pathname && reRunGmMain) {
- this.gmMain(true, this.lastHref)
+ if (this._lastPathStr === window.location.pathname && !reRunGmMain) {
+ await this._listenForNonPageChanges({selector: selector, event: event})
+ } else if (this._lastPathStr === window.location.pathname && reRunGmMain) {
+ this.gmMain({
+ recursive: true,
+ lastPathStr: this._lastPathStr,
+ lastQueryStr: this._lastQueryStr,
+ lastHashStr: this._lastHashStr,
+ lastHref: this._lastHref,
+ lastStashPageEvent: this._lastStashPageEvent,
+ });
}
}
}
callback()
}
- gmMain(isRecursive = false, lastHref) {
- const location = window.location;
- this.log.debug(URL, window.location);
-
- // Run parent page specific functions
- // detect performer edit page
- if (this.matchUrl(location, /\/performers\/\d+/)) {
- if(!new RegExp(/\/performers\/\d+/).test(lastHref)){
- this.log.debug('[Navigation] Performer Page');
- this.processTagger();
- this.dispatchEvent(new Event('page:performer'));
- }
-
- this._listenForNonPageChanges({selector: "#performer-edit", event: "page:performer:edit", eventMessage: "Performer Page - Edit", reRunGmMain: true, callback: () => {
- if (this._detectReRenders) {
- this.log.debug('[Navigation] Performer Page');
- this.dispatchEvent(new Event('page:performer'));
+ _dispatchPageEvent(event, addToHistory = true) {
+ this.dispatchEvent(new CustomEvent(event, {
+ detail: {
+event: event,
+ lastEventState: {
+ lastPathStr: this._lastPathStr,
+ lastQueryStr: this._lastQueryStr,
+ lastHashStr: this._lastHashStr,
+ lastHref: this._lastHref,
+ lastStashPageEvent: this._lastStashPageEvent,
}
- }})
- }
- // detect studio edit page
- else if (this.matchUrl(location, /\/studios\/\d+/)) {
- if(!new RegExp(/\/studios\/\d+/).test(lastHref)){
- this.log.debug('[Navigation] Studio Page');
- this.processTagger();
- this.dispatchEvent(new Event('page:studio'));
}
-
- this._listenForNonPageChanges({selector: "#studio-edit", event: "page:studio:edit", eventMessage: "Studio Page - Edit", reRunGmMain: true, callback: () => {
- if (this._detectReRenders) {
- this.log.debug('[Navigation] Studio Page');
- this.dispatchEvent(new Event('page:studio'));
- }
- }})
- }
- // detect tag edit page
- else if (this.matchUrl(location, /\/tags\/\d+/)) {
- if(!new RegExp(/\/tags\/\d+/).test(lastHref)){
- this.log.debug('[Navigation] Tag Page');
- this.processTagger();
- this.dispatchEvent(new Event('page:tag'));
+ }))
+ if (addToHistory) {
+ this.log.debug(`[Navigation] ${event}`);
+ if (event.startsWith("stash:")) {
+ this._lastStashPageEvent = event;
}
+ }
+ }
+ addPageListener(eventData) {
+ const {event, regex, callBack = () => {}, manuallyHandleDispatchEvent = false} = eventData
+ if (event && !event?.startsWith("stash:") && regex && this._pageListeners[event] === undefined){
+ this._pageListeners[event] = {
+ regex: regex,
+ callBack: callBack,
+ manuallyHandleDispatchEvent: manuallyHandleDispatchEvent
+ }
+ return event
+ } else {
+ if (this._pageListeners[event] !== undefined) {
+ console.error(`Can't add page listener: Event ${event} already exists`)
+ } else if (event?.startsWith("stash:")) {
+ console.error(`Can't add page listener: Event name can't start with "stash:"`)
+ } else {
+ console.error(`Can't add page listener: Missing required argument(s) "event", "regex"`)
+ }
+ return false
+ }
+ }
+ removePageListener(event) {
+ if(event && !event?.startsWith("stash:") && this._pageListeners[event]){
+ delete this._pageListeners[event]
+ return event
+ } else {
+ if (this._pageListeners[event] === undefined && event) {
+ console.error(`Can't remove page listener: Event ${event} doesn't exists`)
+ } else if (event?.startsWith("stash:")) {
+ console.error(`Can't remove page listener: Event ${event} is a built in event`)
+ } else {
+ console.error(`Can't remove page listener: Missing "event" argument`)
+ }
+ return false
+ }
+ }
+ stopPageListener() {
+ clearInterval(this._pageURLCheckTimerId)
+ }
+ assignPageListeners() {
+ this._pageListeners = {
+ // scenes tab
+ "stash:page:scenes": {
+ regex: /\/scenes\?/,
+ handleDisplayView: true,
+ callBack: () => this.processTagger()
+ },
+ "stash:page:scene:new": {
+ regex: /\/scenes\/new/
+ },
+ "stash:page:scene": {
+ regex: /\/scenes\/\d+/,
+ callBack: ({recursive = false}) => this._listenForNonPageChanges({
+ location: ".scene-tabs .nav-tabs",
+ listenType: "tabs",
+ recursive: recursive
+ })
+ },
- this._listenForNonPageChanges({selector: "#tag-edit", event: "page:tag:edit", eventMessage: "Tag Page - Edit", reRunGmMain: true, callback: () => {
- if (this._detectReRenders) {
- this.log.debug('[Navigation] Tag Page');
- this.dispatchEvent(new Event('page:tag'));
+ // images tab
+ "stash:page:images": {
+ regex: /\/images\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:image": {
+ regex: /\/images\/\d+/,
+ callBack: ({recursive = false}) => this._listenForNonPageChanges({
+ location: ".image-tabs .nav-tabs",
+ listenType: "tabs",
+ recursive: recursive
+ })
+ },
+
+ // movies tab
+ "stash:page:movies": {
+ regex: /\/movies\?/,
+ },
+ "stash:page:movie": {
+ regex: /\/movies\/\d+/,
+ },
+ "stash:page:movie:scenes": {
+ regex: /\/movies\/\d+\?/,
+ callBack: () => this.processTagger()
+ },
+
+ // markers tab
+ "stash:page:markers": {
+ regex: /\/scenes\/markers/
+ },
+
+ // galleries tab
+ "stash:page:galleries": {
+ regex: /\/galleries\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:gallery:new": {
+ regex: /\/galleries\/new/,
+ },
+ "stash:page:gallery:images": {
+ regex: /\/galleries\/\d+\?/,
+ manuallyHandleDispatchEvent: true,
+ handleDisplayView: "ignoreDisplayViewCondition",
+ callBack: ({lastHref, recursive = false}, event) => {
+ if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){
+ this._dispatchPageEvent("stash:page:gallery");
+ this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"})
+ }
+
+ this._dispatchPageEvent(event);
+
+ this._listenForNonPageChanges({
+ location: ".gallery-tabs .nav-tabs",
+ listenType: "tabs",
+ recursive: recursive,
+ listenDefaultTab: false
+ })
}
- }})
- }
+ },
+ "stash:page:gallery:add": {
+ regex: /\/galleries\/\d+\/add/,
+ manuallyHandleDispatchEvent: true,
+ handleDisplayView: "ignoreDisplayViewCondition",
+ callBack: ({lastHref, recursive = false}, event) => {
+ if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){
+ this._dispatchPageEvent("stash:page:gallery");
+ this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"})
+ }
+
+ this._dispatchPageEvent(event);
+
+ this._listenForNonPageChanges({
+ location: ".gallery-tabs .nav-tabs",
+ listenType: "tabs",
+ recursive: recursive,
+ listenDefaultTab: false
+ })
+ }
+ },
- // markers page
- if (this.matchUrl(location, /\/scenes\/markers/)) {
- this.log.debug('[Navigation] Markers Page');
- this.dispatchEvent(new Event('page:markers'));
+ // performers tab
+ "stash:page:performers": {
+ regex: /\/performers\?/,
+ manuallyHandleDispatchEvent: true,
+ handleDisplayView: true,
+ callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/performers\?/) || this._detectReRenders ? this._dispatchPageEvent(event) : null
+ },
+ "stash:page:performer:new": {
+ regex: /\/performers\/new/
+ },
+ "stash:page:performer": {
+ regex: /\/performers\/\d+/,
+ manuallyHandleDispatchEvent: true,
+ callBack: ({lastHref}, event) => {
+ if(!this.matchUrl(lastHref, /\/performers\/\d+/)){
+ this._dispatchPageEvent(event);
+ this.processTagger();
+ }
+
+ this._listenForNonPageChanges({
+ selector: "#performer-edit",
+ event: "stash:page:performer:edit",
+ reRunGmMain: true,
+ callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null
+ })
+ }
+ },
+ "stash:page:performer:scenes": {
+ regex: /\/performers\/\d+\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:performer:galleries": {
+ regex: /\/performers\/\d+\/galleries/,
+ handleDisplayView: true
+ },
+ "stash:page:performer:images": {
+ regex: /\/performers\/\d+\/images/,
+ handleDisplayView: true
+ },
+ "stash:page:performer:movies": {
+ regex: /\/performers\/\d+\/movies/
+ },
+ "stash:page:performer:appearswith": {
+ regex: /\/performers\/\d+\/appearswith/,
+ handleDisplayView: true,
+ callBack: () => this.processTagger()
+ },
+
+ // studios tab
+ "stash:page:studios": {
+ regex: /\/studios\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:new": {
+ regex: /\/studios\/new/
+ },
+ "stash:page:studio": {
+ regex: /\/studios\/\d+/,
+ manuallyHandleDispatchEvent: true,
+ callBack: ({lastHref}, event) => {
+ if(!this.matchUrl(lastHref, /\/studios\/\d+/)){
+ this._dispatchPageEvent(event);
+ this.processTagger();
+ }
+
+ this._listenForNonPageChanges({
+ selector: "#studio-edit",
+ event: "stash:page:studio:edit",
+ reRunGmMain: true,
+ callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null
+ })
+ }
+ },
+ "stash:page:studio:scenes": {
+ regex: /\/studios\/\d+\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:galleries": {
+ regex: /\/studios\/\d+\/galleries/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:images": {
+ regex: /\/studios\/\d+\/images/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:performers": {
+ regex: /\/studios\/\d+\/performers/,
+ handleDisplayView: true,
+ },
+ "stash:page:studio:movies": {
+ regex: /\/studios\/\d+\/movies/
+ },
+ "stash:page:studio:childstudios": {
+ regex: /\/studios\/\d+\/childstudios/,
+ handleDisplayView: true,
+ },
+
+ // tags tab
+ "stash:page:tags": {
+ regex: /\/tags\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:tag:new": {
+ regex: /\/tags\/new/
+ },
+ "stash:page:tag": {
+ regex: /\/tags\/\d+/,
+ manuallyHandleDispatchEvent: true,
+ callBack: ({lastHref}, event) => {
+ if(!this.matchUrl(lastHref, /\/tags\/\d+/)){
+ this._dispatchPageEvent(event);
+ this.processTagger();
+ }
+
+ this._listenForNonPageChanges({
+ selector: "#tag-edit",
+ event: "stash:page:tag:edit",
+ reRunGmMain: true,
+ callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null
+ })
+ }
+ },
+ "stash:page:tag:scenes": {
+ regex: /\/tags\/\d+\?/,
+ handleDisplayView: true,
+ },
+ "stash:page:tag:galleries": {
+ regex: /\/tags\/\d+\/galleries/,
+ handleDisplayView: true,
+ },
+ "stash:page:tag:images": {
+ regex: /\/tags\/\d+\/images/,
+ handleDisplayView: true,
+ },
+ "stash:page:tag:markers": {
+ regex: /\/tags\/\d+\/markers/
+ },
+ "stash:page:tag:performers": {
+ regex: /\/tags\/\d+\/performers/,
+ handleDisplayView: true,
+ },
+
+ // settings page
+ "stash:page:settings": {
+ regex: /\/settings/,
+ manuallyHandleDispatchEvent: true,
+ callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/settings/) ? this._dispatchPageEvent(event) : null
+ },
+ "stash:page:settings:tasks": {
+ regex: /\/settings\?tab=tasks/,
+ callback: () => this.hidePluginTasks()
+ },
+ "stash:page:settings:library": {
+ regex: /\/settings\?tab=library/
+ },
+ "stash:page:settings:interface": {
+ regex: /\/settings\?tab=interface/
+ },
+ "stash:page:settings:security": {
+ regex: /\/settings\?tab=security/
+ },
+ "stash:page:settings:metadata-providers": {
+ regex: /\/settings\?tab=metadata-providers/
+ },
+ "stash:page:settings:services": {
+ regex: /\/settings\?tab=services/
+ },
+ "stash:page:settings:system": {
+ regex: /\/settings\?tab=system/,
+ callBack: () => this.createSettings()
+ },
+ "stash:page:settings:plugins": {
+ regex: /\/settings\?tab=plugins/
+ },
+ "stash:page:settings:logs": {
+ regex: /\/settings\?tab=logs/
+ },
+ "stash:page:settings:tools": {
+ regex: /\/settings\?tab=tools/
+ },
+ "stash:page:settings:changelog": {
+ regex: /\/settings\?tab=changelog/
+ },
+ "stash:page:settings:about": {
+ regex: /\/settings\?tab=about/
+ },
+
+ // stats page
+ "stash:page:stats": {
+ regex: /\/stats/
+ },
+
+ // home page
+ "stash:page:home": {
+ regex: /\/$/,
+ callBack: () => this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "stash:page:home:edit", reRunGmMain: true})
+ },
}
- // create scene page
- else if (this.matchUrl(location, /\/scenes\/new/)) {
- this.log.debug('[Navigation] Create Scene Page');
- this.dispatchEvent(new Event('page:scene:new'));
- }
- // scene page
- else if (this.matchUrl(location, /\/scenes\/\d+/)) {
- this.log.debug('[Navigation] Scene Page');
- this.dispatchEvent(new Event('page:scene'));
+ }
+ gmMain(args) {
+ const events = Object.keys(this._pageListeners)
+
+ for (const event of events) {
+ const {regex, callBack = async () => {}, manuallyHandleDispatchEvent = false, handleDisplayView = false} = this._pageListeners[event]
- this._listenForNonPageChanges({
- location: ".scene-tabs .nav-tabs",
- listenType: "tabs",
- isRecursive: isRecursive
- })
- }
- // scenes page
- else if (this.matchUrl(location, /\/scenes\?/)) {
- this.log.debug('[Navigation] Scenes Page');
- this.processTagger();
- this.dispatchEvent(new Event('page:scenes'));
- }
-
- // image page
- else if (this.matchUrl(location, /\/images\/\d+/)) {
- this.log.debug('[Navigation] Image Page');
- this.dispatchEvent(new Event('page:image'));
-
- this._listenForNonPageChanges({
- location: ".image-tabs .nav-tabs",
- listenType: "tabs",
- isRecursive: isRecursive
- })
- }
- // images page
- else if (this.matchUrl(location, /\/images\?/)) {
- this.log.debug('[Navigation] Images Page');
- this.dispatchEvent(new Event('page:images'));
- }
-
- // movie scenes page
- else if (this.matchUrl(location, /\/movies\/\d+\?/)) {
- this.log.debug('[Navigation] Movie Page - Scenes');
- this.processTagger();
- this.dispatchEvent(new Event('page:movie:scenes'));
- }
- // movie page
- else if (this.matchUrl(location, /\/movies\/\d+/)) {
- this.log.debug('[Navigation] Movie Page');
- this.dispatchEvent(new Event('page:movie'));
- }
- // movies page
- else if (this.matchUrl(location, /\/movies\?/)) {
- this.log.debug('[Navigation] Movies Page');
- this.dispatchEvent(new Event('page:movies'));
- }
-
- // create gallery page
- else if (this.matchUrl(location, /\/galleries\/new/)) {
- this.log.debug('[Navigation] Create Gallery Page');
- this.dispatchEvent(new Event('page:gallery:new'));
- }
- // gallery add page
- else if (this.matchUrl(location, /\/galleries\/\d+\/add/)) {
- if(!new RegExp(/\/galleries\/\d+/).test(lastHref)){
- this.log.debug('[Navigation] Gallery Page');
- this.dispatchEvent(new Event('page:gallery'));
- this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "page:gallery:details", eventMessage: "Gallery Page - Details"})
- }
-
- this.log.debug('[Navigation] Gallery Page - Add');
- this.dispatchEvent(new Event('page:gallery:add'));
+ let isDisplayViewPage = false
+ let isListPage, isWallPage, isTaggerPage
- this._listenForNonPageChanges({
- location: ".gallery-tabs .nav-tabs",
- listenType: "tabs",
- isRecursive: isRecursive,
- listenDefaultTab: false
- })
- }
- // gallery page
- else if (this.matchUrl(location, /\/galleries\/\d+/)) {
- if(!new RegExp(/\/galleries\/\d+/).test(lastHref)){
- this.log.debug('[Navigation] Gallery Page');
- this.dispatchEvent(new Event('page:gallery'));
- this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "page:gallery:details", eventMessage: "Gallery Page - Details"})
+ if (handleDisplayView) {
+ isListPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=1/))
+ isWallPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=2/))
+ isTaggerPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=3/))
+
+ if (isListPage || isWallPage || isTaggerPage) isDisplayViewPage = true
}
- this.log.debug('[Navigation] Gallery Page - Images');
- this.dispatchEvent(new Event('page:gallery:images'));
+ const handleDisplayViewCondition = handleDisplayView !== true || (handleDisplayView && (!isDisplayViewPage || args.lastHref === ""))
- this._listenForNonPageChanges({
- location: ".gallery-tabs .nav-tabs",
- listenType: "tabs",
- isRecursive: isRecursive,
- listenDefaultTab: false
- })
- }
- // galleries page
- else if (this.matchUrl(location, /\/galleries\?/)) {
- this.log.debug('[Navigation] Galleries Page');
- this.dispatchEvent(new Event('page:galleries'));
- }
-
- // performer galleries page
- else if (this.matchUrl(location, /\/performers\/\d+\/galleries/)) {
- this.log.debug('[Navigation] Performer Page - Galleries');
- this.dispatchEvent(new Event('page:performer:galleries'));
- }
- // performer images page
- else if (this.matchUrl(location, /\/performers\/\d+\/images/)) {
- this.log.debug('[Navigation] Performer Page - Images');
- this.dispatchEvent(new Event('page:performer:images'));
- }
- // performer movies page
- else if (this.matchUrl(location, /\/performers\/\d+\/movies/)) {
- this.log.debug('[Navigation] Performer Page - Movies');
- this.dispatchEvent(new Event('page:performer:movies'));
- }
- // performer appearswith page
- else if (this.matchUrl(location, /\/performers\/\d+\/appearswith/)) {
- this.log.debug('[Navigation] Performer Page - Appears With');
- this.processTagger();
- this.dispatchEvent(new Event('page:performer:appearswith'));
- }
- // create performer page
- else if (this.matchUrl(location, /\/performers\/new/)) {
- this.log.debug('[Navigation] Create Performer Page');
- this.dispatchEvent(new Event('page:performer:new'));
- }
- // performer scenes page
- else if (this.matchUrl(location, /\/performers\/\d+\?/)) {
- this.log.debug('[Navigation] Performer Page - Scenes');
- this.dispatchEvent(new Event('page:performer:scenes'));
- }
- // performers page
- else if (this.matchUrl(location, /\/performers\?/)) {
- this.log.debug('[Navigation] Performers Page');
- this.dispatchEvent(new Event('page:performers'));
- }
-
- // studio galleries page
- else if (this.matchUrl(location, /\/studios\/\d+\/galleries/)) {
- this.log.debug('[Navigation] Studio Page - Galleries');
- this.dispatchEvent(new Event('page:studio:galleries'));
- }
- // studio images page
- else if (this.matchUrl(location, /\/studios\/\d+\/images/)) {
- this.log.debug('[Navigation] Studio Page - Images');
- this.dispatchEvent(new Event('page:studio:images'));
- }
- // studio performers page
- else if (this.matchUrl(location, /\/studios\/\d+\/performers/)) {
- this.log.debug('[Navigation] Studio Page - Performers');
- this.dispatchEvent(new Event('page:studio:performers'));
- }
- // studio movies page
- else if (this.matchUrl(location, /\/studios\/\d+\/movies/)) {
- this.log.debug('[Navigation] Studio Page - Movies');
- this.dispatchEvent(new Event('page:studio:movies'));
- }
- // studio childstudios page
- else if (this.matchUrl(location, /\/studios\/\d+\/childstudios/)) {
- this.log.debug('[Navigation] Studio Page - Child Studios');
- this.dispatchEvent(new Event('page:studio:childstudios'));
- }
- // create studio page
- else if (this.matchUrl(location, /\/studios\/new/)) {
- this.log.debug('[Navigation] Create Studio Page');
- this.dispatchEvent(new Event('page:studio:new'));
- }
- // studio scenes page
- else if (this.matchUrl(location, /\/studios\/\d+\?/)) {
- this.log.debug('[Navigation] Studio Page - Scenes');
- this.dispatchEvent(new Event('page:studio:scenes'));
- }
- // studios page
- else if (this.matchUrl(location, /\/studios\?/)) {
- this.log.debug('[Navigation] Studios Page');
- this.dispatchEvent(new Event('page:studios'));
- }
-
- // tag galleries page
- else if (this.matchUrl(location, /\/tags\/\d+\/galleries/)) {
- this.log.debug('[Navigation] Tag Page - Galleries');
- this.dispatchEvent(new Event('page:tag:galleries'));
- }
- // tag images page
- else if (this.matchUrl(location, /\/tags\/\d+\/images/)) {
- this.log.debug('[Navigation] Tag Page - Images');
- this.dispatchEvent(new Event('page:tag:images'));
- }
- // tag markers page
- else if (this.matchUrl(location, /\/tags\/\d+\/markers/)) {
- this.log.debug('[Navigation] Tag Page - Markers');
- this.dispatchEvent(new Event('page:tag:markers'));
- }
- // tag performers page
- else if (this.matchUrl(location, /\/tags\/\d+\/performers/)) {
- this.log.debug('[Navigation] Tag Page - Performers');
- this.dispatchEvent(new Event('page:tag:performers'));
- }
- // create tag page
- else if (this.matchUrl(location, /\/tags\/new/)) {
- this.log.debug('[Navigation] Create Tag Page');
- this.dispatchEvent(new Event('page:tag:new'));
- }
- // tag scenes page
- else if (this.matchUrl(location, /\/tags\/\d+\?/)) {
- this.log.debug('[Navigation] Tag Page - Scenes');
- this.dispatchEvent(new Event('page:tag:scenes'));
- }
- // tags page
- else if (this.matchUrl(location, /\/tags\?/)) {
- this.log.debug('[Navigation] Tags Page');
- this.dispatchEvent(new Event('page:tags'));
- }
-
- // settings page tasks tab
- else if (this.matchUrl(location, /\/settings\?tab=tasks/)) {
- if(!new RegExp(/\/settings\?/).test(lastHref)){
- this.log.debug('[Navigation] Settings Page');
- this.dispatchEvent(new Event('page:settings'));
- this.hidePluginTasks();
+ if (this.matchUrl(window.location.href, regex) && handleDisplayViewCondition) {
+ if (!manuallyHandleDispatchEvent) this._dispatchPageEvent(event)
+ callBack({...args, location: window.location}, event)
}
- this.log.debug('[Navigation] Settings Page Tasks Tab');
- this.dispatchEvent(new Event('page:settings:tasks'));
- }
- // settings page library tab
- else if (this.matchUrl(location, /\/settings\?tab=library/)) {
- this.log.debug('[Navigation] Settings Page Library Tab');
- this.dispatchEvent(new Event('page:settings:library'));
- }
- // settings page interface tab
- else if (this.matchUrl(location, /\/settings\?tab=interface/)) {
- this.log.debug('[Navigation] Settings Page Interface Tab');
- this.dispatchEvent(new Event('page:settings:interface'));
- }
- // settings page security tab
- else if (this.matchUrl(location, /\/settings\?tab=security/)) {
- this.log.debug('[Navigation] Settings Page Security Tab');
- this.dispatchEvent(new Event('page:settings:security'));
- }
- // settings page metadata providers tab
- else if (this.matchUrl(location, /\/settings\?tab=metadata-providers/)) {
- this.log.debug('[Navigation] Settings Page Metadata Providers Tab');
- this.dispatchEvent(new Event('page:settings:metadata-providers'));
- }
- // settings page services tab
- else if (this.matchUrl(location, /\/settings\?tab=services/)) {
- this.log.debug('[Navigation] Settings Page Services Tab');
- this.dispatchEvent(new Event('page:settings:services'));
- }
- // settings page system tab
- else if (this.matchUrl(location, /\/settings\?tab=system/)) {
- this.log.debug('[Navigation] Settings Page System Tab');
- this.createSettings();
- this.dispatchEvent(new Event('page:settings:system'));
- }
- // settings page plugins tab
- else if (this.matchUrl(location, /\/settings\?tab=plugins/)) {
- this.log.debug('[Navigation] Settings Page Plugins Tab');
- this.dispatchEvent(new Event('page:settings:plugins'));
- }
- // settings page logs tab
- else if (this.matchUrl(location, /\/settings\?tab=logs/)) {
- this.log.debug('[Navigation] Settings Page Logs Tab');
- this.dispatchEvent(new Event('page:settings:logs'));
- }
- // settings page tools tab
- else if (this.matchUrl(location, /\/settings\?tab=tools/)) {
- this.log.debug('[Navigation] Settings Page Tools Tab');
- this.dispatchEvent(new Event('page:settings:tools'));
- }
- // settings page changelog tab
- else if (this.matchUrl(location, /\/settings\?tab=changelog/)) {
- this.log.debug('[Navigation] Settings Page Changelog Tab');
- this.dispatchEvent(new Event('page:settings:changelog'));
- }
- // settings page about tab
- else if (this.matchUrl(location, /\/settings\?tab=about/)) {
- this.log.debug('[Navigation] Settings Page About Tab');
- this.dispatchEvent(new Event('page:settings:about'));
- }
-
- // stats page
- else if (this.matchUrl(location, /\/stats/)) {
- this.log.debug('[Navigation] Stats Page');
- this.dispatchEvent(new Event('page:stats'));
- }
-
- // home page
- else if (this.matchUrl(location, /\/$/)) {
- this.log.debug('[Navigation] Home Page');
- this.dispatchEvent(new Event('page:home'));
-
- this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "page:home:edit", eventMessage: "Home Page - Edit", reRunGmMain: true})
+ if (handleDisplayView) {
+ if (isListPage) {
+ this._dispatchPageEvent("stash:page:any:list", false);
+ this._dispatchPageEvent(event + ":list");
+ } else if (isWallPage) {
+ this._dispatchPageEvent("stash:page:any:wall", false);
+ this._dispatchPageEvent(event + ":wall");
+ } else if (isTaggerPage) {
+ this._dispatchPageEvent("stash:page:any:tagger", false);
+ this._dispatchPageEvent(event + ":tagger");
+ }
+ }
}
}
addEventListeners(events, callback, ...options) {
diff --git a/plugins/stats/stats.js b/plugins/stats/stats.js
index 65f30d5..3444a6f 100644
--- a/plugins/stats/stats.js
+++ b/plugins/stats/stats.js
@@ -113,7 +113,7 @@
createStatElement(row, totalCount, 'Markers');
}
- stash.addEventListener('page:stats', function() {
+ stash.addEventListener('stash:page:stats', function() {
waitForElementByXpath("//div[contains(@class, 'container-fluid')]/div[@class='mt-5']", function(xpath, el) {
if (!document.getElementById('custom-stats-row')) {
const changelog = el.querySelector('div.changelog');